5
5
#![ deny( clippy:: large_futures) ]
6
6
7
7
use std:: {
8
- num:: NonZeroUsize ,
8
+ num:: { NonZeroU16 , NonZeroUsize } ,
9
9
path:: { Path , PathBuf } ,
10
10
time:: Duration ,
11
11
} ;
12
12
13
- use anyhow:: bail;
13
+ use anyhow:: { bail, Context } ;
14
14
use async_trait:: async_trait;
15
15
use futures:: { stream:: FuturesUnordered , FutureExt as _, StreamExt , TryFutureExt as _} ;
16
16
use linera_base:: crypto:: { CryptoRng , KeyPair } ;
@@ -394,6 +394,40 @@ enum ServerCommand {
394
394
#[ arg( long, default_value = "1000" ) ]
395
395
cache_size : usize ,
396
396
} ,
397
+
398
+ /// Replaces the configurations of the shards by following the given template.
399
+ #[ command( name = "edit-shards" ) ]
400
+ EditShards {
401
+ /// Path to the file containing the server configuration of this Linera validator.
402
+ #[ arg( long = "server" ) ]
403
+ server_config_path : PathBuf ,
404
+
405
+ /// The number N of shard configs to generate, possibly starting with zeroes. If
406
+ /// `N` was written using `D` digits, we will replace the first occurrence of the
407
+ /// string `"%" * D` (`%` repeated D times) by the shard number.
408
+ #[ arg( long) ]
409
+ num_shards : String ,
410
+
411
+ /// The host of the validator (IP address or hostname), possibly containing `%`
412
+ /// for digits of the shard number.
413
+ #[ arg( long) ]
414
+ host : String ,
415
+
416
+ /// The port of the main endpoint, possibly containing `%` for digits of the shard
417
+ /// number.
418
+ #[ arg( long) ]
419
+ port : String ,
420
+
421
+ /// The host for the metrics endpoint, possibly containing `%` for digits of the
422
+ /// shard number.
423
+ #[ arg( long) ]
424
+ metrics_host : String ,
425
+
426
+ /// The port for the metrics endpoint, possibly containing `%` for digits of the
427
+ /// shard number.
428
+ #[ arg( long) ]
429
+ metrics_port : Option < String > ,
430
+ } ,
397
431
}
398
432
399
433
fn main ( ) {
@@ -421,8 +455,6 @@ fn main() {
421
455
}
422
456
423
457
async fn run ( options : ServerOptions ) {
424
- linera_version:: VERSION_INFO . log ( ) ;
425
-
426
458
match options. command {
427
459
ServerCommand :: Run {
428
460
server_config_path,
@@ -437,10 +469,12 @@ async fn run(options: ServerOptions) {
437
469
max_stream_queries,
438
470
cache_size,
439
471
} => {
472
+ linera_version:: VERSION_INFO . log ( ) ;
473
+
440
474
let genesis_config: GenesisConfig =
441
- util:: read_json ( & genesis_config_path) . expect ( "Fail to read initial chain config" ) ;
475
+ util:: read_json ( & genesis_config_path) . expect ( "Failed to read initial chain config" ) ;
442
476
let server_config: ValidatorServerConfig =
443
- util:: read_json ( & server_config_path) . expect ( "Fail to read server config" ) ;
477
+ util:: read_json ( & server_config_path) . expect ( "Failed to read server config" ) ;
444
478
445
479
#[ cfg( feature = "rocksdb" ) ]
446
480
if server_config. internal_network . shards . len ( ) > 1
@@ -497,17 +531,16 @@ async fn run(options: ServerOptions) {
497
531
config_validators. push ( Persist :: into_value ( server) . validator ) ;
498
532
}
499
533
if let Some ( committee) = committee {
500
- Persist :: persist (
501
- & mut persistent:: File :: new (
502
- & committee,
503
- CommitteeConfig {
504
- validators : config_validators,
505
- } ,
506
- )
507
- . expect ( "Unable to open committee configuration" ) ,
534
+ let mut config = persistent:: File :: new (
535
+ & committee,
536
+ CommitteeConfig {
537
+ validators : config_validators,
538
+ } ,
508
539
)
509
- . await
510
- . expect ( "Unable to write committee description" ) ;
540
+ . expect ( "Unable to open committee configuration" ) ;
541
+ Persist :: persist ( & mut config)
542
+ . await
543
+ . expect ( "Unable to write committee description" ) ;
511
544
info ! ( "Wrote committee config {}" , committee. to_str( ) . unwrap( ) ) ;
512
545
}
513
546
}
@@ -520,7 +553,7 @@ async fn run(options: ServerOptions) {
520
553
cache_size,
521
554
} => {
522
555
let genesis_config: GenesisConfig =
523
- util:: read_json ( & genesis_config_path) . expect ( "Fail to read initial chain config" ) ;
556
+ util:: read_json ( & genesis_config_path) . expect ( "Failed to read initial chain config" ) ;
524
557
let common_config = CommonStoreConfig {
525
558
max_concurrent_queries,
526
559
max_stream_queries,
@@ -534,9 +567,69 @@ async fn run(options: ServerOptions) {
534
567
. await
535
568
. unwrap ( ) ;
536
569
}
570
+
571
+ ServerCommand :: EditShards {
572
+ server_config_path,
573
+ num_shards,
574
+ host,
575
+ port,
576
+ metrics_host,
577
+ metrics_port,
578
+ } => {
579
+ let mut server_config =
580
+ persistent:: File :: < ValidatorServerConfig > :: read ( & server_config_path)
581
+ . expect ( "Failed to read server config" ) ;
582
+ let shards = generate_shard_configs ( num_shards, host, port, metrics_host, metrics_port)
583
+ . expect ( "Failed to generate shard configs" ) ;
584
+ server_config. internal_network . shards = shards;
585
+ Persist :: persist ( & mut server_config)
586
+ . await
587
+ . expect ( "Failed to write updated server config" ) ;
588
+ }
537
589
}
538
590
}
539
591
592
+ fn generate_shard_configs (
593
+ num_shards : String ,
594
+ host : String ,
595
+ port : String ,
596
+ metrics_host : String ,
597
+ metrics_port : Option < String > ,
598
+ ) -> anyhow:: Result < Vec < ShardConfig > > {
599
+ let mut shards = Vec :: new ( ) ;
600
+ let len = num_shards. len ( ) ;
601
+ let num_shards = num_shards
602
+ . parse :: < NonZeroU16 > ( )
603
+ . context ( "Failed to parse the number of shards" ) ?;
604
+ let pattern = "%" . repeat ( len) ;
605
+
606
+ for i in 1u16 ..=num_shards. into ( ) {
607
+ let index = format ! ( "{i:0len$}" ) ;
608
+ let host = host. replacen ( & pattern, & index, 1 ) ;
609
+ let port = port
610
+ . replacen ( & pattern, & index, 1 )
611
+ . parse ( )
612
+ . context ( "Failed to decode port into an integers" ) ?;
613
+ let metrics_host = metrics_host. replacen ( & pattern, & index, 1 ) ;
614
+ let metrics_port = metrics_port
615
+ . as_ref ( )
616
+ . map ( |port| {
617
+ port. replacen ( & pattern, & index, 1 )
618
+ . parse ( )
619
+ . context ( "Failed to decode metrics port into an integers" )
620
+ } )
621
+ . transpose ( ) ?;
622
+ let shard = ShardConfig {
623
+ host,
624
+ port,
625
+ metrics_host,
626
+ metrics_port,
627
+ } ;
628
+ shards. push ( shard) ;
629
+ }
630
+ Ok ( shards)
631
+ }
632
+
540
633
#[ cfg( test) ]
541
634
mod test {
542
635
use linera_rpc:: simple:: TransportProtocol ;
@@ -598,4 +691,41 @@ mod test {
598
691
}
599
692
) ;
600
693
}
694
+
695
+ #[ test]
696
+ fn test_generate_shard_configs ( ) {
697
+ assert_eq ! (
698
+ generate_shard_configs(
699
+ "02" . into( ) ,
700
+ "host%%" . into( ) ,
701
+ "10%%" . into( ) ,
702
+ "metrics_host%%" . into( ) ,
703
+ Some ( "11%%" . into( ) )
704
+ )
705
+ . unwrap( ) ,
706
+ vec![
707
+ ShardConfig {
708
+ host: "host01" . into( ) ,
709
+ port: 1001 ,
710
+ metrics_host: "metrics_host01" . into( ) ,
711
+ metrics_port: Some ( 1101 ) ,
712
+ } ,
713
+ ShardConfig {
714
+ host: "host02" . into( ) ,
715
+ port: 1002 ,
716
+ metrics_host: "metrics_host02" . into( ) ,
717
+ metrics_port: Some ( 1102 ) ,
718
+ } ,
719
+ ] ,
720
+ ) ;
721
+
722
+ assert ! ( generate_shard_configs(
723
+ "2" . into( ) ,
724
+ "host%%" . into( ) ,
725
+ "10%%" . into( ) ,
726
+ "metrics_host%%" . into( ) ,
727
+ Some ( "11%%" . into( ) )
728
+ )
729
+ . is_err( ) ) ;
730
+ }
601
731
}
0 commit comments