@@ -17,7 +17,7 @@ use slog::error;
17
17
use slog:: info;
18
18
use slog:: warn;
19
19
use slog_error_chain:: InlineErrorChain ;
20
- use std:: collections:: BTreeMap ;
20
+ use std:: collections:: BTreeSet ;
21
21
use std:: convert:: Infallible ;
22
22
use std:: ops:: Deref as _;
23
23
use tokio:: sync:: mpsc;
@@ -98,7 +98,7 @@ pub enum LedgerArtifactConfigError {
98
98
"Artifacts in use by ledgered sled config are not present \
99
99
in new artifact config: {0:?}"
100
100
) ]
101
- InUseArtifactedMissing ( BTreeMap < ArtifactHash , & ' static str > ) ,
101
+ InUseArtifactedMissing ( BTreeSet < ArtifactHash > ) ,
102
102
}
103
103
104
104
#[ derive( Debug , Clone , PartialEq , Eq ) ]
@@ -442,10 +442,7 @@ impl<T: SledAgentArtifactStore> LedgerTask<T> {
442
442
// thing we confirm is that any referenced artifacts are present in the
443
443
// artifact store.
444
444
let mut artifact_validation_errors = Vec :: new ( ) ;
445
- for zone in & new_config. zones {
446
- let Some ( artifact_hash) = zone. image_source . artifact_hash ( ) else {
447
- continue ;
448
- } ;
445
+ for artifact_hash in config_artifact_hashes ( new_config) {
449
446
match self
450
447
. artifact_store
451
448
. validate_artifact_exists_in_storage ( artifact_hash)
@@ -491,12 +488,10 @@ impl<T: SledAgentArtifactStore> LedgerTask<T> {
491
488
CurrentSledConfig :: Ledgered ( sled_config) => sled_config,
492
489
} ;
493
490
494
- let mut missing = BTreeMap :: new ( ) ;
495
- for zone in & sled_config. zones {
496
- if let Some ( hash) = zone. image_source . artifact_hash ( ) {
497
- if !new_config. artifacts . contains ( & hash) {
498
- missing. insert ( hash, "current zone configuration" ) ;
499
- }
491
+ let mut missing = BTreeSet :: new ( ) ;
492
+ for artifact_hash in config_artifact_hashes ( sled_config) {
493
+ if !new_config. artifacts . contains ( & artifact_hash) {
494
+ missing. insert ( artifact_hash) ;
500
495
}
501
496
}
502
497
@@ -589,6 +584,18 @@ impl<T: SledAgentArtifactStore> LedgerTask<T> {
589
584
}
590
585
}
591
586
587
+ // Helper to return all the `ArtifactHash`es contained in a config.
588
+ fn config_artifact_hashes (
589
+ config : & OmicronSledConfig ,
590
+ ) -> impl Iterator < Item = ArtifactHash > + ' _ {
591
+ config
592
+ . zones
593
+ . iter ( )
594
+ . filter_map ( |zone| zone. image_source . artifact_hash ( ) )
595
+ . chain ( config. host_phase_2 . slot_a . artifact_hash ( ) )
596
+ . chain ( config. host_phase_2 . slot_b . artifact_hash ( ) )
597
+ }
598
+
592
599
async fn load_sled_config (
593
600
config_datasets : & [ Utf8PathBuf ] ,
594
601
log : & Logger ,
@@ -648,6 +655,7 @@ mod tests {
648
655
use camino_tempfile:: Utf8TempDir ;
649
656
use id_map:: IdMap ;
650
657
use illumos_utils:: zpool:: ZpoolName ;
658
+ use nexus_sled_agent_shared:: inventory:: HostPhase2DesiredContents ;
651
659
use nexus_sled_agent_shared:: inventory:: HostPhase2DesiredSlots ;
652
660
use nexus_sled_agent_shared:: inventory:: OmicronZoneConfig ;
653
661
use nexus_sled_agent_shared:: inventory:: OmicronZoneImageSource ;
@@ -1054,7 +1062,7 @@ mod tests {
1054
1062
1055
1063
// Create a config that references a zone with a different artifact
1056
1064
// hash.
1057
- let config = OmicronSledConfig {
1065
+ let mut config = OmicronSledConfig {
1058
1066
generation : Generation :: new ( ) . next ( ) ,
1059
1067
disks : IdMap :: new ( ) ,
1060
1068
datasets : IdMap :: new ( ) ,
@@ -1070,7 +1078,7 @@ mod tests {
1070
1078
// The ledger task should reject this config due to a missing artifact.
1071
1079
let err = test_harness
1072
1080
. task_handle
1073
- . set_new_config ( config)
1081
+ . set_new_config ( config. clone ( ) )
1074
1082
. await
1075
1083
. expect ( "can communicate with task" )
1076
1084
. expect_err ( "config should fail" ) ;
@@ -1081,20 +1089,48 @@ mod tests {
1081
1089
1082
1090
// Change the config to reference the artifact that does exist; this one
1083
1091
// should be accepted.
1084
- let config = OmicronSledConfig {
1085
- generation : Generation :: new ( ) . next ( ) ,
1086
- disks : IdMap :: new ( ) ,
1087
- datasets : IdMap :: new ( ) ,
1088
- zones : [ make_dummy_zone_config_using_artifact_hash (
1089
- existing_artifact_hash,
1090
- ) ]
1091
- . into_iter ( )
1092
- . collect ( ) ,
1093
- remove_mupdate_override : None ,
1094
- host_phase_2 : HostPhase2DesiredSlots :: current_contents ( ) ,
1092
+ config. zones = [ make_dummy_zone_config_using_artifact_hash (
1093
+ existing_artifact_hash,
1094
+ ) ]
1095
+ . into_iter ( )
1096
+ . collect ( ) ;
1097
+
1098
+ test_harness
1099
+ . task_handle
1100
+ . set_new_config ( config. clone ( ) )
1101
+ . await
1102
+ . expect ( "can communicate with task" )
1103
+ . expect ( "config should be ledgered" ) ;
1104
+
1105
+ // Try a config that references a host phase 2 artifact that isn't in
1106
+ // the store; this should be rejected.
1107
+ config. generation = config. generation . next ( ) ;
1108
+ config. zones = IdMap :: new ( ) ;
1109
+ config. host_phase_2 = HostPhase2DesiredSlots {
1110
+ slot_a : HostPhase2DesiredContents :: CurrentContents ,
1111
+ slot_b : HostPhase2DesiredContents :: Artifact {
1112
+ hash : nonexisting_artifact_hash,
1113
+ } ,
1095
1114
} ;
1115
+ let err = test_harness
1116
+ . task_handle
1117
+ . set_new_config ( config. clone ( ) )
1118
+ . await
1119
+ . expect ( "can communicate with task" )
1120
+ . expect_err ( "config should fail" ) ;
1121
+ match err {
1122
+ LedgerNewConfigError :: ArtifactStoreValidationFailed ( _) => ( ) ,
1123
+ _ => panic ! ( "unexpected error {}" , InlineErrorChain :: new( & err) ) ,
1124
+ }
1096
1125
1097
- // TODO-john also test host phase 2 artifacts!
1126
+ // Change the config to reference the artifact that does exist; this one
1127
+ // should be accepted.
1128
+ config. host_phase_2 = HostPhase2DesiredSlots {
1129
+ slot_a : HostPhase2DesiredContents :: CurrentContents ,
1130
+ slot_b : HostPhase2DesiredContents :: Artifact {
1131
+ hash : existing_artifact_hash,
1132
+ } ,
1133
+ } ;
1098
1134
1099
1135
test_harness
1100
1136
. task_handle
@@ -1112,54 +1148,84 @@ mod tests {
1112
1148
"reject_artifact_configs_removing_referenced_artifacts" ,
1113
1149
) ;
1114
1150
1115
- // Claim we have a zone using the all-zeroes hash.
1116
- let used_artifact_hash = ArtifactHash ( [ 0 ; 32 ] ) ;
1117
- let unused_artifact_hash = ArtifactHash ( [ 1 ; 32 ] ) ;
1151
+ // Construct some artifacts hashes we'll claim to be using and one we
1152
+ // won't.
1153
+ let used_zone_artifact_hash = ArtifactHash ( [ 0 ; 32 ] ) ;
1154
+ let used_host_artifact_hash = ArtifactHash ( [ 1 ; 32 ] ) ;
1155
+ let unused_artifact_hash = ArtifactHash ( [ 2 ; 32 ] ) ;
1118
1156
1157
+ // Claim we have a host phase 2
1119
1158
// Set up the ledger task with an initial config.
1120
1159
let mut sled_config = make_nonempty_sled_config ( ) ;
1121
1160
sled_config. zones . insert ( make_dummy_zone_config_using_artifact_hash (
1122
- used_artifact_hash ,
1161
+ used_zone_artifact_hash ,
1123
1162
) ) ;
1163
+ sled_config. host_phase_2 . slot_a = HostPhase2DesiredContents :: Artifact {
1164
+ hash : used_host_artifact_hash,
1165
+ } ;
1166
+
1124
1167
let test_harness =
1125
1168
TestHarness :: with_initial_config ( logctx. log . clone ( ) , & sled_config)
1126
1169
. await ;
1127
1170
1128
- // Construct an `ArtifactConfig` that doesn't contain
1129
- // `used_artifact_hash`; this should be rejected.
1130
1171
let mut artifact_config = ArtifactConfig {
1131
1172
generation : Generation :: new ( ) ,
1132
- artifacts : [ unused_artifact_hash ] . into_iter ( ) . collect ( ) ,
1173
+ artifacts : BTreeSet :: new ( ) ,
1133
1174
} ;
1134
- let err = test_harness
1135
- . task_handle
1136
- . validate_artifact_config ( artifact_config. clone ( ) )
1137
- . await
1138
- . expect ( "no ledger task error" )
1139
- . expect_err ( "config should be rejected" ) ;
1140
1175
1141
- match err {
1142
- LedgerArtifactConfigError :: InUseArtifactedMissing ( artifacts) => {
1143
- assert ! (
1144
- artifacts. contains_key( & used_artifact_hash) ,
1145
- "unexpected error contents: {artifacts:?}"
1146
- ) ;
1176
+ // Try some ArtifactConfigs that don't contain both `used_*` artifact
1177
+ // hashes; all of these should be rejected.
1178
+ for incomplete_artifacts in [
1179
+ & [ used_zone_artifact_hash] as & [ ArtifactHash ] ,
1180
+ & [ used_host_artifact_hash] ,
1181
+ & [ unused_artifact_hash] ,
1182
+ & [ used_zone_artifact_hash, unused_artifact_hash] ,
1183
+ & [ used_host_artifact_hash, unused_artifact_hash] ,
1184
+ ] {
1185
+ artifact_config. artifacts =
1186
+ incomplete_artifacts. iter ( ) . cloned ( ) . collect ( ) ;
1187
+
1188
+ let err = test_harness
1189
+ . task_handle
1190
+ . validate_artifact_config ( artifact_config. clone ( ) )
1191
+ . await
1192
+ . expect ( "no ledger task error" )
1193
+ . expect_err ( "config should be rejected" ) ;
1194
+
1195
+ match err {
1196
+ LedgerArtifactConfigError :: InUseArtifactedMissing (
1197
+ artifacts,
1198
+ ) => {
1199
+ if !incomplete_artifacts. contains ( & used_zone_artifact_hash)
1200
+ {
1201
+ assert ! (
1202
+ artifacts. contains( & used_zone_artifact_hash) ,
1203
+ "unexpected error contents: {artifacts:?}"
1204
+ ) ;
1205
+ }
1206
+ if !incomplete_artifacts. contains ( & used_host_artifact_hash)
1207
+ {
1208
+ assert ! (
1209
+ artifacts. contains( & used_host_artifact_hash) ,
1210
+ "unexpected error contents: {artifacts:?}"
1211
+ ) ;
1212
+ }
1213
+ }
1214
+ _ => panic ! ( "unexpected error {}" , InlineErrorChain :: new( & err) ) ,
1147
1215
}
1148
- _ => panic ! ( "unexpected error {}" , InlineErrorChain :: new( & err) ) ,
1149
1216
}
1150
1217
1151
- // Put the used artifact hash into the artifact config; now it should
1218
+ // Put both used artifact hashes into the artifact config; now it should
1152
1219
// pass validation.
1153
- artifact_config. artifacts . insert ( used_artifact_hash) ;
1220
+ artifact_config. artifacts . insert ( used_zone_artifact_hash) ;
1221
+ artifact_config. artifacts . insert ( used_host_artifact_hash) ;
1154
1222
test_harness
1155
1223
. task_handle
1156
1224
. validate_artifact_config ( artifact_config)
1157
1225
. await
1158
1226
. expect ( "no ledger task error" )
1159
1227
. expect ( "config is valid" ) ;
1160
1228
1161
- // TODO-john also test host phase 2 artifacts!
1162
-
1163
1229
logctx. cleanup_successful ( ) ;
1164
1230
}
1165
1231
0 commit comments