Skip to content

Commit 652cc02

Browse files
committed
Shard editing command (#2732)
Allow editing the number of shard in a validator internal config file. Create a command `linera-server edit-shards --server file.json --num-shards 4 --host 'shard-%' --port 8000 --metrics-host 'shard-%' --metrics-port 8001` Because of port numbers, I figured we should use `%` when `num_shards` is written with 1 digits, `%%` for 2 digits, etc. ``` linera-server generate --validators configuration/local/validator_*.toml --committee committee.json --testing-prng-seed 1 linera-server edit-shards --server server_1.json --num-shards 02 --host 'shard-%%' --port '10000' --metrics-host 'shard-%%' --metrics-port '110%%' ```
1 parent 60dc472 commit 652cc02

File tree

1 file changed

+147
-17
lines changed

1 file changed

+147
-17
lines changed

linera-service/src/server.rs

+147-17
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
#![deny(clippy::large_futures)]
66

77
use std::{
8-
num::NonZeroUsize,
8+
num::{NonZeroU16, NonZeroUsize},
99
path::{Path, PathBuf},
1010
time::Duration,
1111
};
1212

13-
use anyhow::bail;
13+
use anyhow::{bail, Context};
1414
use async_trait::async_trait;
1515
use futures::{stream::FuturesUnordered, FutureExt as _, StreamExt, TryFutureExt as _};
1616
use linera_base::crypto::{CryptoRng, KeyPair};
@@ -394,6 +394,40 @@ enum ServerCommand {
394394
#[arg(long, default_value = "1000")]
395395
cache_size: usize,
396396
},
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+
},
397431
}
398432

399433
fn main() {
@@ -421,8 +455,6 @@ fn main() {
421455
}
422456

423457
async fn run(options: ServerOptions) {
424-
linera_version::VERSION_INFO.log();
425-
426458
match options.command {
427459
ServerCommand::Run {
428460
server_config_path,
@@ -437,10 +469,12 @@ async fn run(options: ServerOptions) {
437469
max_stream_queries,
438470
cache_size,
439471
} => {
472+
linera_version::VERSION_INFO.log();
473+
440474
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");
442476
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");
444478

445479
#[cfg(feature = "rocksdb")]
446480
if server_config.internal_network.shards.len() > 1
@@ -497,17 +531,16 @@ async fn run(options: ServerOptions) {
497531
config_validators.push(Persist::into_value(server).validator);
498532
}
499533
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+
},
508539
)
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");
511544
info!("Wrote committee config {}", committee.to_str().unwrap());
512545
}
513546
}
@@ -520,7 +553,7 @@ async fn run(options: ServerOptions) {
520553
cache_size,
521554
} => {
522555
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");
524557
let common_config = CommonStoreConfig {
525558
max_concurrent_queries,
526559
max_stream_queries,
@@ -534,9 +567,69 @@ async fn run(options: ServerOptions) {
534567
.await
535568
.unwrap();
536569
}
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+
}
537589
}
538590
}
539591

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+
540633
#[cfg(test)]
541634
mod test {
542635
use linera_rpc::simple::TransportProtocol;
@@ -598,4 +691,41 @@ mod test {
598691
}
599692
);
600693
}
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+
}
601731
}

0 commit comments

Comments
 (0)