@@ -9,6 +9,9 @@ use chrono::SecondsFormat;
9
9
use chrono:: Utc ;
10
10
use futures:: future:: ready;
11
11
use futures:: stream:: StreamExt ;
12
+ use iddqd:: IdOrdItem ;
13
+ use iddqd:: IdOrdMap ;
14
+ use iddqd:: id_upcast;
12
15
use nexus_sled_agent_shared:: inventory:: ConfigReconcilerInventory ;
13
16
use nexus_sled_agent_shared:: inventory:: ConfigReconcilerInventoryResult ;
14
17
use nexus_sled_agent_shared:: inventory:: OmicronZoneImageSource ;
@@ -343,11 +346,13 @@ pub struct Ipv4NatEntryView {
343
346
344
347
/// Status of ongoing update attempts, recently completed attempts, and update
345
348
/// requests that are waiting for retry.
346
- #[ derive( Clone , Debug , Default , Deserialize , Serialize , JsonSchema ) ]
349
+ #[ derive(
350
+ Clone , Debug , Default , Eq , PartialEq , Deserialize , Serialize , JsonSchema ,
351
+ ) ]
347
352
pub struct MgsUpdateDriverStatus {
348
353
pub recent : VecDeque < CompletedAttempt > ,
349
- pub in_progress : BTreeMap < Arc < BaseboardId > , InProgressUpdateStatus > ,
350
- pub waiting : BTreeMap < Arc < BaseboardId > , WaitingStatus > ,
354
+ pub in_progress : IdOrdMap < InProgressUpdateStatus > ,
355
+ pub waiting : IdOrdMap < WaitingStatus > ,
351
356
}
352
357
353
358
impl MgsUpdateDriverStatus {
@@ -384,7 +389,7 @@ impl Display for MgsUpdateDriverStatusDisplay<'_> {
384
389
}
385
390
386
391
writeln ! ( f, "\n currently in progress:" ) ?;
387
- for ( baseboard_id , status) in & status. in_progress {
392
+ for status in & status. in_progress {
388
393
// Ignore units smaller than a millisecond.
389
394
let elapsed = Duration :: from_millis (
390
395
u64:: try_from (
@@ -398,19 +403,19 @@ impl Display for MgsUpdateDriverStatusDisplay<'_> {
398
403
status
399
404
. time_started
400
405
. to_rfc3339_opts( SecondsFormat :: Millis , true ) ,
401
- baseboard_id. serial_number,
406
+ status . baseboard_id. serial_number,
402
407
status. status,
403
408
status. nattempts_done + 1 ,
404
409
humantime:: format_duration( elapsed) ,
405
410
) ?;
406
411
}
407
412
408
413
writeln ! ( f, "\n waiting for retry:" ) ?;
409
- for ( baseboard_id , wait_info) in & status. waiting {
414
+ for wait_info in & status. waiting {
410
415
writeln ! (
411
416
f,
412
417
" serial {}: will try again at {} (attempt {})" ,
413
- baseboard_id. serial_number,
418
+ wait_info . baseboard_id. serial_number,
414
419
wait_info. next_attempt_time,
415
420
wait_info. nattempts_done + 1 ,
416
421
) ?;
@@ -421,13 +426,13 @@ impl Display for MgsUpdateDriverStatusDisplay<'_> {
421
426
}
422
427
423
428
/// externally-exposed status for a completed attempt
424
- #[ derive( Debug , Clone , Deserialize , Serialize , JsonSchema ) ]
429
+ #[ derive( Debug , Clone , Deserialize , Eq , PartialEq , Serialize , JsonSchema ) ]
425
430
pub struct CompletedAttempt {
426
431
pub time_started : DateTime < Utc > ,
427
432
pub time_done : DateTime < Utc > ,
428
433
pub elapsed : Duration ,
429
434
pub request : PendingMgsUpdate ,
430
- #[ serde( serialize_with = "snake_case_result::serialize " ) ]
435
+ #[ serde( with = "snake_case_result" ) ]
431
436
#[ schemars(
432
437
schema_with = "SnakeCaseResult::<UpdateCompletedHow, String>::json_schema"
433
438
) ]
@@ -447,13 +452,24 @@ pub enum UpdateCompletedHow {
447
452
}
448
453
449
454
/// externally-exposed status for each in-progress update
450
- #[ derive( Clone , Debug , Deserialize , Serialize , JsonSchema ) ]
455
+ #[ derive( Clone , Debug , Deserialize , Eq , PartialEq , Serialize , JsonSchema ) ]
451
456
pub struct InProgressUpdateStatus {
457
+ pub baseboard_id : Arc < BaseboardId > ,
452
458
pub time_started : DateTime < Utc > ,
453
459
pub status : UpdateAttemptStatus ,
454
460
pub nattempts_done : u32 ,
455
461
}
456
462
463
+ impl IdOrdItem for InProgressUpdateStatus {
464
+ type Key < ' a > = & ' a Arc < BaseboardId > ;
465
+
466
+ fn key ( & self ) -> Self :: Key < ' _ > {
467
+ & self . baseboard_id
468
+ }
469
+
470
+ id_upcast ! ( ) ;
471
+ }
472
+
457
473
/// status of a single update attempt
458
474
#[ derive(
459
475
Clone , Copy , Debug , Deserialize , Eq , PartialEq , Serialize , JsonSchema ,
@@ -471,12 +487,23 @@ pub enum UpdateAttemptStatus {
471
487
}
472
488
473
489
/// externally-exposed status for waiting updates
474
- #[ derive( Clone , Debug , Deserialize , Serialize , JsonSchema ) ]
490
+ #[ derive( Clone , Debug , Deserialize , Eq , PartialEq , Serialize , JsonSchema ) ]
475
491
pub struct WaitingStatus {
492
+ pub baseboard_id : Arc < BaseboardId > ,
476
493
pub next_attempt_time : DateTime < Utc > ,
477
494
pub nattempts_done : u32 ,
478
495
}
479
496
497
+ impl IdOrdItem for WaitingStatus {
498
+ type Key < ' a > = & ' a Arc < BaseboardId > ;
499
+
500
+ fn key ( & self ) -> Self :: Key < ' _ > {
501
+ & self . baseboard_id
502
+ }
503
+
504
+ id_upcast ! ( ) ;
505
+ }
506
+
480
507
#[ derive(
481
508
Debug ,
482
509
Clone ,
@@ -594,3 +621,78 @@ impl UpdateStatus {
594
621
ZoneStatusVersion :: Unknown
595
622
}
596
623
}
624
+
625
+ #[ cfg( test) ]
626
+ mod test {
627
+ use super :: CompletedAttempt ;
628
+ use super :: InProgressUpdateStatus ;
629
+ use super :: MgsUpdateDriverStatus ;
630
+ use super :: UpdateCompletedHow ;
631
+ use super :: WaitingStatus ;
632
+ use crate :: deployment:: ExpectedVersion ;
633
+ use crate :: deployment:: PendingMgsUpdate ;
634
+ use crate :: deployment:: PendingMgsUpdateDetails ;
635
+ use crate :: internal_api:: views:: UpdateAttemptStatus ;
636
+ use crate :: inventory:: BaseboardId ;
637
+ use chrono:: Utc ;
638
+ use gateway_client:: types:: SpType ;
639
+ use std:: collections:: VecDeque ;
640
+ use std:: sync:: Arc ;
641
+ use std:: time:: Instant ;
642
+ use tufaceous_artifact:: ArtifactHash ;
643
+
644
+ #[ test]
645
+ fn test_can_serialize_mgs_updates ( ) {
646
+ let start = Instant :: now ( ) ;
647
+ let artifact_hash: ArtifactHash =
648
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
649
+ . parse ( )
650
+ . unwrap ( ) ;
651
+ let baseboard_id = Arc :: new ( BaseboardId {
652
+ part_number : String :: from ( "a_port" ) ,
653
+ serial_number : String :: from ( "a_serial" ) ,
654
+ } ) ;
655
+ let waiting = WaitingStatus {
656
+ baseboard_id : baseboard_id. clone ( ) ,
657
+ next_attempt_time : Utc :: now ( ) ,
658
+ nattempts_done : 2 ,
659
+ } ;
660
+ let in_progress = InProgressUpdateStatus {
661
+ baseboard_id : baseboard_id. clone ( ) ,
662
+ time_started : Utc :: now ( ) ,
663
+ status : UpdateAttemptStatus :: Updating ,
664
+ nattempts_done : 3 ,
665
+ } ;
666
+ let completed = CompletedAttempt {
667
+ time_started : Utc :: now ( ) ,
668
+ time_done : Utc :: now ( ) ,
669
+ elapsed : start. elapsed ( ) ,
670
+ request : PendingMgsUpdate {
671
+ baseboard_id : baseboard_id. clone ( ) ,
672
+ sp_type : SpType :: Sled ,
673
+ slot_id : 12 ,
674
+ details : PendingMgsUpdateDetails :: Sp {
675
+ expected_active_version : "1.0.0" . parse ( ) . unwrap ( ) ,
676
+ expected_inactive_version : ExpectedVersion :: NoValidVersion ,
677
+ } ,
678
+ artifact_hash,
679
+ artifact_version : "2.0.0" . parse ( ) . unwrap ( ) ,
680
+ } ,
681
+ result : Ok ( UpdateCompletedHow :: FoundNoChangesNeeded ) ,
682
+ nattempts_done : 8 ,
683
+ } ;
684
+
685
+ let status = MgsUpdateDriverStatus {
686
+ recent : VecDeque :: from ( [ completed] ) ,
687
+ in_progress : std:: iter:: once ( in_progress) . collect ( ) ,
688
+ waiting : std:: iter:: once ( waiting) . collect ( ) ,
689
+ } ;
690
+
691
+ let serialized =
692
+ serde_json:: to_string ( & status) . expect ( "failed to serialize value" ) ;
693
+ let deserialized: MgsUpdateDriverStatus =
694
+ serde_json:: from_str ( & serialized)
695
+ . expect ( "failed to deserialize value" ) ;
696
+ assert_eq ! ( deserialized, status) ;
697
+ }
698
+ }
0 commit comments