5
5
//! developer REPL for driving blueprint planning
6
6
7
7
use anyhow:: { Context , anyhow, bail} ;
8
- use camino:: Utf8PathBuf ;
8
+ use camino:: { Utf8Path , Utf8PathBuf } ;
9
9
use clap:: ValueEnum ;
10
10
use clap:: { Args , Parser , Subcommand } ;
11
11
use iddqd:: IdOrdMap ;
@@ -20,9 +20,9 @@ use nexus_reconfigurator_planning::blueprint_builder::BlueprintBuilder;
20
20
use nexus_reconfigurator_planning:: example:: ExampleSystemBuilder ;
21
21
use nexus_reconfigurator_planning:: planner:: Planner ;
22
22
use nexus_reconfigurator_planning:: system:: { SledBuilder , SystemDescription } ;
23
- use nexus_reconfigurator_simulation:: SimStateBuilder ;
24
- use nexus_reconfigurator_simulation:: Simulator ;
25
23
use nexus_reconfigurator_simulation:: { BlueprintId , SimState } ;
24
+ use nexus_reconfigurator_simulation:: { SimStateBuilder , SimTufRepoSource } ;
25
+ use nexus_reconfigurator_simulation:: { SimTufRepoDescription , Simulator } ;
26
26
use nexus_sled_agent_shared:: inventory:: ZoneKind ;
27
27
use nexus_types:: deployment:: PlanningInput ;
28
28
use nexus_types:: deployment:: SledFilter ;
@@ -39,18 +39,19 @@ use nexus_types::deployment::{OmicronZoneNic, TargetReleaseDescription};
39
39
use nexus_types:: external_api:: views:: SledPolicy ;
40
40
use nexus_types:: external_api:: views:: SledProvisionPolicy ;
41
41
use omicron_common:: address:: REPO_DEPOT_PORT ;
42
- use omicron_common:: api:: external:: Generation ;
43
42
use omicron_common:: api:: external:: Name ;
43
+ use omicron_common:: api:: external:: { Generation , TufRepoDescription } ;
44
44
use omicron_common:: policy:: NEXUS_REDUNDANCY ;
45
+ use omicron_common:: update:: OmicronZoneManifestSource ;
45
46
use omicron_repl_utils:: run_repl_from_file;
46
47
use omicron_repl_utils:: run_repl_on_stdin;
47
- use omicron_uuid_kinds:: CollectionUuid ;
48
48
use omicron_uuid_kinds:: GenericUuid ;
49
49
use omicron_uuid_kinds:: OmicronZoneUuid ;
50
50
use omicron_uuid_kinds:: ReconfiguratorSimUuid ;
51
51
use omicron_uuid_kinds:: SledUuid ;
52
52
use omicron_uuid_kinds:: VnicUuid ;
53
53
use omicron_uuid_kinds:: { BlueprintUuid , MupdateOverrideUuid } ;
54
+ use omicron_uuid_kinds:: { CollectionUuid , MupdateUuid } ;
54
55
use std:: borrow:: Cow ;
55
56
use std:: convert:: Infallible ;
56
57
use std:: fmt:: { self , Write } ;
@@ -220,6 +221,9 @@ fn process_command(
220
221
Commands :: SledRemove ( args) => cmd_sled_remove ( sim, args) ,
221
222
Commands :: SledShow ( args) => cmd_sled_show ( sim, args) ,
222
223
Commands :: SledSetPolicy ( args) => cmd_sled_set_policy ( sim, args) ,
224
+ Commands :: SledUpdateInstallDataset ( args) => {
225
+ cmd_sled_update_install_dataset ( sim, args)
226
+ }
223
227
Commands :: SledUpdateSp ( args) => cmd_sled_update_sp ( sim, args) ,
224
228
Commands :: SiloList => cmd_silo_list ( sim) ,
225
229
Commands :: SiloAdd ( args) => cmd_silo_add ( sim, args) ,
@@ -275,6 +279,8 @@ enum Commands {
275
279
SledShow ( SledArgs ) ,
276
280
/// set a sled's policy
277
281
SledSetPolicy ( SledSetPolicyArgs ) ,
282
+ /// update the install dataset on a sled, simulating a mupdate
283
+ SledUpdateInstallDataset ( SledUpdateInstallDatasetArgs ) ,
278
284
/// simulate updating the sled's SP versions
279
285
SledUpdateSp ( SledUpdateSpArgs ) ,
280
286
@@ -395,6 +401,52 @@ impl From<SledPolicyOpt> for SledPolicy {
395
401
}
396
402
}
397
403
404
+ #[ derive( Debug , Args ) ]
405
+ struct SledUpdateInstallDatasetArgs {
406
+ /// id of the sled
407
+ sled_id : SledOpt ,
408
+
409
+ #[ clap( flatten) ]
410
+ source : SledMupdateSource ,
411
+ }
412
+
413
+ #[ derive( Debug , Args ) ]
414
+ // This makes it so that only one source can be specified.
415
+ struct SledMupdateSource {
416
+ #[ clap( flatten) ]
417
+ valid : SledMupdateValidSource ,
418
+
419
+ /// set the mupdate source to Installinator with the given ID
420
+ #[ clap( long, requires = "sled-mupdate-valid-source" ) ]
421
+ mupdate_id : Option < MupdateUuid > ,
422
+
423
+ /// simulate an error reading the zone manifest
424
+ #[ clap( long, conflicts_with = "sled-mupdate-valid-source" ) ]
425
+ with_manifest_error : bool ,
426
+
427
+ /// simulate an error validating zones by this artifact ID name
428
+ ///
429
+ /// This uses the `artifact_id_name` representation of a zone kind.
430
+ #[ clap(
431
+ long,
432
+ value_name = "ARTIFACT_ID_NAME" ,
433
+ requires = "sled-mupdate-valid-source"
434
+ ) ]
435
+ with_zone_error : Vec < String > ,
436
+ }
437
+
438
+ #[ derive( Debug , Args ) ]
439
+ #[ group( id = "sled-mupdate-valid-source" , multiple = false ) ]
440
+ struct SledMupdateValidSource {
441
+ /// the TUF repo.zip to simulate the mupdate from
442
+ #[ clap( long) ]
443
+ from_repo : Option < Utf8PathBuf > ,
444
+
445
+ /// simulate a mupdate to the target release
446
+ #[ clap( long) ]
447
+ to_target_release : bool ,
448
+ }
449
+
398
450
#[ derive( Debug , Args ) ]
399
451
struct SledUpdateSpArgs {
400
452
/// id of the sled
@@ -879,6 +931,10 @@ struct TufAssembleArgs {
879
931
/// The tufaceous manifest path (relative to this crate's root)
880
932
manifest_path : Utf8PathBuf ,
881
933
934
+ /// Allow non-semver artifact versions.
935
+ #[ clap( long) ]
936
+ allow_non_semver : bool ,
937
+
882
938
#[ clap(
883
939
long,
884
940
// Use help here rather than a doc comment because rustdoc doesn't like
@@ -1156,6 +1212,32 @@ fn cmd_sled_set_policy(
1156
1212
Ok ( Some ( format ! ( "set sled {} policy to {}" , sled_id, args. policy) ) )
1157
1213
}
1158
1214
1215
+ fn cmd_sled_update_install_dataset (
1216
+ sim : & mut ReconfiguratorSim ,
1217
+ args : SledUpdateInstallDatasetArgs ,
1218
+ ) -> anyhow:: Result < Option < String > > {
1219
+ let description = mupdate_source_to_description ( sim, & args. source ) ?;
1220
+
1221
+ let mut state = sim. current_state ( ) . to_mut ( ) ;
1222
+ let system = state. system_mut ( ) ;
1223
+ let sled_id = args. sled_id . to_sled_id ( system. description ( ) ) ?;
1224
+ system
1225
+ . description_mut ( )
1226
+ . sled_set_zone_manifest ( sled_id, description. to_boot_inventory ( ) ) ?;
1227
+
1228
+ sim. commit_and_bump (
1229
+ format ! (
1230
+ "reconfigurator-cli sled-update-install-dataset: {}" ,
1231
+ description. message,
1232
+ ) ,
1233
+ state,
1234
+ ) ;
1235
+ Ok ( Some ( format ! (
1236
+ "sled {}: install dataset updated: {}" ,
1237
+ sled_id, description. message
1238
+ ) ) )
1239
+ }
1240
+
1159
1241
fn cmd_sled_update_sp (
1160
1242
sim : & mut ReconfiguratorSim ,
1161
1243
args : SledUpdateSpArgs ,
@@ -1955,26 +2037,8 @@ fn cmd_set(
1955
2037
rv
1956
2038
}
1957
2039
SetArgs :: TargetRelease { filename } => {
1958
- let file = std:: fs:: File :: open ( & filename)
1959
- . with_context ( || format ! ( "open {:?}" , filename) ) ?;
1960
- let buf = std:: io:: BufReader :: new ( file) ;
1961
- let rt = tokio:: runtime:: Runtime :: new ( )
1962
- . context ( "creating tokio runtime" ) ?;
1963
- // We're not using the repo hash here. Make one up.
1964
- let repo_hash = ArtifactHash ( [ 0 ; 32 ] ) ;
1965
- let artifacts_with_plan = rt. block_on ( async {
1966
- ArtifactsWithPlan :: from_zip (
1967
- buf,
1968
- None ,
1969
- repo_hash,
1970
- ControlPlaneZonesMode :: Split ,
1971
- & sim. log ,
1972
- )
1973
- . await
1974
- . with_context ( || format ! ( "unpacking {:?}" , filename) )
1975
- } ) ?;
1976
- let description = artifacts_with_plan. description ( ) . clone ( ) ;
1977
- drop ( artifacts_with_plan) ;
2040
+ let description =
2041
+ extract_tuf_repo_description ( & sim. log , & filename) ?;
1978
2042
state. system_mut ( ) . description_mut ( ) . set_target_release (
1979
2043
TargetReleaseDescription :: TufRepo ( description) ,
1980
2044
) ;
@@ -1986,6 +2050,84 @@ fn cmd_set(
1986
2050
Ok ( Some ( rv) )
1987
2051
}
1988
2052
2053
+ /// Converts a mupdate source to a TUF repo description.
2054
+ fn mupdate_source_to_description (
2055
+ sim : & ReconfiguratorSim ,
2056
+ source : & SledMupdateSource ,
2057
+ ) -> anyhow:: Result < SimTufRepoDescription > {
2058
+ let manifest_source = match source. mupdate_id {
2059
+ Some ( mupdate_id) => {
2060
+ OmicronZoneManifestSource :: Installinator { mupdate_id }
2061
+ }
2062
+ None => OmicronZoneManifestSource :: SledAgent ,
2063
+ } ;
2064
+ if let Some ( repo_path) = & source. valid . from_repo {
2065
+ let description = extract_tuf_repo_description ( & sim. log , repo_path) ?;
2066
+ let mut sim_source = SimTufRepoSource :: new (
2067
+ description,
2068
+ manifest_source,
2069
+ format ! ( "from repo at {repo_path}" ) ,
2070
+ ) ?;
2071
+ sim_source. simulate_zone_errors ( & source. with_zone_error ) ?;
2072
+ Ok ( SimTufRepoDescription :: new ( sim_source) )
2073
+ } else if source. valid . to_target_release {
2074
+ let description = sim
2075
+ . current_state ( )
2076
+ . system ( )
2077
+ . description ( )
2078
+ . target_release ( )
2079
+ . description ( ) ;
2080
+ match description {
2081
+ TargetReleaseDescription :: Initial => {
2082
+ bail ! (
2083
+ "cannot mupdate zones without a target release \
2084
+ (use `set target-release` or --from-repo)"
2085
+ )
2086
+ }
2087
+ TargetReleaseDescription :: TufRepo ( desc) => {
2088
+ let mut sim_source = SimTufRepoSource :: new (
2089
+ desc. clone ( ) ,
2090
+ manifest_source,
2091
+ "to target release" . to_owned ( ) ,
2092
+ ) ?;
2093
+ sim_source. simulate_zone_errors ( & source. with_zone_error ) ?;
2094
+ Ok ( SimTufRepoDescription :: new ( sim_source) )
2095
+ }
2096
+ }
2097
+ } else if source. with_manifest_error {
2098
+ Ok ( SimTufRepoDescription :: new_error (
2099
+ "simulated error obtaining zone manifest" . to_owned ( ) ,
2100
+ ) )
2101
+ } else {
2102
+ bail ! ( "an update source must be specified" )
2103
+ }
2104
+ }
2105
+
2106
+ fn extract_tuf_repo_description (
2107
+ log : & slog:: Logger ,
2108
+ filename : & Utf8Path ,
2109
+ ) -> anyhow:: Result < TufRepoDescription > {
2110
+ let file = std:: fs:: File :: open ( filename)
2111
+ . with_context ( || format ! ( "open {:?}" , filename) ) ?;
2112
+ let buf = std:: io:: BufReader :: new ( file) ;
2113
+ let rt =
2114
+ tokio:: runtime:: Runtime :: new ( ) . context ( "creating tokio runtime" ) ?;
2115
+ let repo_hash = ArtifactHash ( [ 0 ; 32 ] ) ;
2116
+ let artifacts_with_plan = rt. block_on ( async {
2117
+ ArtifactsWithPlan :: from_zip (
2118
+ buf,
2119
+ None ,
2120
+ repo_hash,
2121
+ ControlPlaneZonesMode :: Split ,
2122
+ log,
2123
+ )
2124
+ . await
2125
+ . with_context ( || format ! ( "unpacking {:?}" , filename) )
2126
+ } ) ?;
2127
+ let description = artifacts_with_plan. description ( ) . clone ( ) ;
2128
+ Ok ( description)
2129
+ }
2130
+
1989
2131
fn cmd_tuf_assemble (
1990
2132
sim : & ReconfiguratorSim ,
1991
2133
args : TufAssembleArgs ,
@@ -2016,18 +2158,26 @@ fn cmd_tuf_assemble(
2016
2158
Utf8PathBuf :: from ( format ! ( "repo-{}.zip" , manifest. system_version) )
2017
2159
} ;
2018
2160
2161
+ if output_path. exists ( ) {
2162
+ bail ! ( "output path `{output_path}` already exists" ) ;
2163
+ }
2164
+
2019
2165
// Just use a fixed key for now.
2020
2166
//
2021
2167
// In the future we may want to test changing the TUF key.
2022
- let args = tufaceous :: Args :: try_parse_from ( [
2168
+ let mut tufaceous_args = vec ! [
2023
2169
"tufaceous" ,
2024
2170
"--key" ,
2025
2171
DEFAULT_TUFACEOUS_KEY ,
2026
2172
"assemble" ,
2027
2173
manifest_path. as_str( ) ,
2028
2174
output_path. as_str( ) ,
2029
- ] )
2030
- . expect ( "args are valid so this shouldn't fail" ) ;
2175
+ ] ;
2176
+ if args. allow_non_semver {
2177
+ tufaceous_args. push ( "--allow-non-semver" ) ;
2178
+ }
2179
+ let args = tufaceous:: Args :: try_parse_from ( tufaceous_args)
2180
+ . expect ( "args are valid so this shouldn't fail" ) ;
2031
2181
let rt =
2032
2182
tokio:: runtime:: Runtime :: new ( ) . context ( "creating tokio runtime" ) ?;
2033
2183
rt. block_on ( async move { args. exec ( & sim. log ) . await } )
0 commit comments