17
17
//! Operations that list or modify artifacts or the configuration are called by
18
18
//! Nexus and handled by the Sled Agent API.
19
19
20
+ use std:: collections:: BTreeMap ;
20
21
use std:: future:: Future ;
21
22
use std:: io:: { ErrorKind , Write } ;
22
23
use std:: net:: SocketAddrV6 ;
@@ -48,6 +49,8 @@ use tokio::sync::{mpsc, oneshot, watch};
48
49
use tokio:: task:: JoinSet ;
49
50
use tufaceous_artifact:: ArtifactHash ;
50
51
52
+ use crate :: services:: ServiceManager ;
53
+
51
54
// These paths are defined under the artifact storage dataset. They
52
55
// cannot conflict with any artifact paths because all artifact paths are
53
56
// hexadecimal-encoded SHA-256 checksums.
@@ -87,7 +90,11 @@ pub(crate) struct ArtifactStore<T: DatasetsManager> {
87
90
}
88
91
89
92
impl < T : DatasetsManager > ArtifactStore < T > {
90
- pub ( crate ) async fn new ( log : & Logger , storage : T ) -> ArtifactStore < T > {
93
+ pub ( crate ) async fn new (
94
+ log : & Logger ,
95
+ storage : T ,
96
+ services : Option < ServiceManager > ,
97
+ ) -> ArtifactStore < T > {
91
98
let log = log. new ( slog:: o!( "component" => "ArtifactStore" ) ) ;
92
99
93
100
let mut ledger_paths = Vec :: new ( ) ;
@@ -126,6 +133,7 @@ impl<T: DatasetsManager> ArtifactStore<T> {
126
133
tokio:: task:: spawn ( ledger_manager (
127
134
log. clone ( ) ,
128
135
ledger_paths,
136
+ services,
129
137
ledger_rx,
130
138
config_tx,
131
139
) ) ;
@@ -244,15 +252,27 @@ impl<T: DatasetsManager> ArtifactStore<T> {
244
252
pub ( crate ) async fn get (
245
253
& self ,
246
254
sha256 : ArtifactHash ,
255
+ ) -> Result < File , Error > {
256
+ Self :: get_from_storage ( & self . storage , & self . log , sha256) . await
257
+ }
258
+
259
+ /// Open an artifact file by hash from a storage handle.
260
+ ///
261
+ /// This is the same as [ArtifactStore::get], but can be called with only
262
+ /// a [StorageHandle].
263
+ pub ( crate ) async fn get_from_storage (
264
+ storage : & T ,
265
+ log : & Logger ,
266
+ sha256 : ArtifactHash ,
247
267
) -> Result < File , Error > {
248
268
let sha256_str = sha256. to_string ( ) ;
249
269
let mut last_error = None ;
250
- for mountpoint in self . storage . artifact_storage_paths ( ) . await {
270
+ for mountpoint in storage. artifact_storage_paths ( ) . await {
251
271
let path = mountpoint. join ( & sha256_str) ;
252
272
match File :: open ( & path) . await {
253
273
Ok ( file) => {
254
274
info ! (
255
- & self . log,
275
+ & log,
256
276
"Retrieved artifact" ;
257
277
"sha256" => & sha256_str,
258
278
"path" => path. as_str( ) ,
@@ -261,7 +281,7 @@ impl<T: DatasetsManager> ArtifactStore<T> {
261
281
}
262
282
Err ( err) if err. kind ( ) == ErrorKind :: NotFound => { }
263
283
Err ( err) => {
264
- log_and_store ! ( last_error, & self . log, "open" , path, err) ;
284
+ log_and_store ! ( last_error, & log, "open" , path, err) ;
265
285
}
266
286
}
267
287
}
@@ -430,9 +450,11 @@ type LedgerManagerRequest =
430
450
async fn ledger_manager (
431
451
log : Logger ,
432
452
ledger_paths : Vec < Utf8PathBuf > ,
453
+ services : Option < ServiceManager > ,
433
454
mut rx : mpsc:: Receiver < LedgerManagerRequest > ,
434
455
config_channel : watch:: Sender < Option < ArtifactConfig > > ,
435
456
) {
457
+ let services = services. as_ref ( ) ;
436
458
let handle_request = async |new_config : ArtifactConfig | {
437
459
if ledger_paths. is_empty ( ) {
438
460
return Err ( Error :: NoUpdateDataset ) ;
@@ -441,7 +463,25 @@ async fn ledger_manager(
441
463
Ledger :: < ArtifactConfig > :: new ( & log, ledger_paths. clone ( ) ) . await
442
464
{
443
465
if new_config. generation > ledger. data ( ) . generation {
444
- // New config generation; update the ledger.
466
+ // New config generation. First check that the configuration
467
+ // contains all artifacts that are presently in use.
468
+ let mut missing = BTreeMap :: new ( ) ;
469
+ // Check artifacts from the current zone configuration.
470
+ if let Some ( services) = services {
471
+ for zone in services. omicron_zones_list ( ) . await . zones {
472
+ if let Some ( hash) = zone. image_source . artifact_hash ( ) {
473
+ if !new_config. artifacts . contains ( & hash) {
474
+ missing
475
+ . insert ( hash, "current zone configuration" ) ;
476
+ }
477
+ }
478
+ }
479
+ }
480
+ if !missing. is_empty ( ) {
481
+ return Err ( Error :: InUseArtifactsMissing ( missing) ) ;
482
+ }
483
+
484
+ // Everything looks okay; update the ledger.
445
485
* ledger. data_mut ( ) = new_config;
446
486
ledger
447
487
} else if new_config == * ledger. data ( ) {
@@ -740,7 +780,7 @@ impl RepoDepotApi for RepoDepotImpl {
740
780
}
741
781
742
782
#[ derive( Debug , thiserror:: Error , SlogInlineError ) ]
743
- pub ( crate ) enum Error {
783
+ pub enum Error {
744
784
#[ error( "Error while reading request body" ) ]
745
785
Body ( dropshot:: HttpError ) ,
746
786
@@ -784,6 +824,9 @@ pub(crate) enum Error {
784
824
#[ error( "Digest mismatch: expected {expected}, actual {actual}" ) ]
785
825
HashMismatch { expected : ArtifactHash , actual : ArtifactHash } ,
786
826
827
+ #[ error( "Artifacts in use are not present in new config: {0:?}" ) ]
828
+ InUseArtifactsMissing ( BTreeMap < ArtifactHash , & ' static str > ) ,
829
+
787
830
#[ error( "Blocking task failed" ) ]
788
831
Join ( #[ source] tokio:: task:: JoinError ) ,
789
832
@@ -813,6 +856,7 @@ impl From<Error> for HttpError {
813
856
match err {
814
857
// 4xx errors
815
858
Error :: HashMismatch { .. }
859
+ | Error :: InUseArtifactsMissing { .. }
816
860
| Error :: NoConfig
817
861
| Error :: NotInConfig { .. } => {
818
862
HttpError :: for_bad_request ( None , err. to_string ( ) )
@@ -951,7 +995,7 @@ mod test {
951
995
952
996
let log = test_setup_log ( "generations" ) ;
953
997
let backend = TestBackend :: new ( 2 ) ;
954
- let store = ArtifactStore :: new ( & log. log , backend) . await ;
998
+ let store = ArtifactStore :: new ( & log. log , backend, None ) . await ;
955
999
956
1000
// get_config returns None
957
1001
assert ! ( store. get_config( ) . is_none( ) ) ;
@@ -1004,7 +1048,7 @@ mod test {
1004
1048
async fn list_get_put ( ) {
1005
1049
let log = test_setup_log ( "list_get_put" ) ;
1006
1050
let backend = TestBackend :: new ( 2 ) ;
1007
- let mut store = ArtifactStore :: new ( & log. log , backend) . await ;
1051
+ let mut store = ArtifactStore :: new ( & log. log , backend, None ) . await ;
1008
1052
1009
1053
// get fails, because it doesn't exist yet
1010
1054
assert ! ( matches!(
@@ -1126,7 +1170,7 @@ mod test {
1126
1170
1127
1171
let log = test_setup_log ( "no_dataset" ) ;
1128
1172
let backend = TestBackend :: new ( 0 ) ;
1129
- let store = ArtifactStore :: new ( & log. log , backend) . await ;
1173
+ let store = ArtifactStore :: new ( & log. log , backend, None ) . await ;
1130
1174
1131
1175
assert ! ( matches!(
1132
1176
store. get( TEST_HASH ) . await ,
@@ -1154,7 +1198,7 @@ mod test {
1154
1198
1155
1199
let log = test_setup_log ( "wrong_hash" ) ;
1156
1200
let backend = TestBackend :: new ( 2 ) ;
1157
- let store = ArtifactStore :: new ( & log. log , backend) . await ;
1201
+ let store = ArtifactStore :: new ( & log. log , backend, None ) . await ;
1158
1202
let mut config = ArtifactConfig {
1159
1203
generation : 1u32 . into ( ) ,
1160
1204
artifacts : BTreeSet :: new ( ) ,
@@ -1214,7 +1258,7 @@ mod test {
1214
1258
1215
1259
let log = test_setup_log ( "issue_7796" ) ;
1216
1260
let backend = TestBackend :: new ( 2 ) ;
1217
- let store = ArtifactStore :: new ( & log. log , backend) . await ;
1261
+ let store = ArtifactStore :: new ( & log. log , backend, None ) . await ;
1218
1262
1219
1263
let mut config = ArtifactConfig {
1220
1264
generation : 1u32 . into ( ) ,
0 commit comments