@@ -29,10 +29,6 @@ use sha3::Digest;
29
29
use sha3:: Sha3_256 ;
30
30
use tokio:: sync:: mpsc;
31
31
32
- // How long do we take to hash host flash? Real SPs take a handful of seconds;
33
- // we'll pick something similar.
34
- const TIME_TO_HASH_HOST_PHASE_1 : Duration = Duration :: from_secs ( 5 ) ;
35
-
36
32
pub ( crate ) struct SimSpUpdate {
37
33
/// tracks the state of any ongoing simulated update
38
34
///
@@ -53,7 +49,7 @@ pub(crate) struct SimSpUpdate {
53
49
/// us to default to `TIME_TO_HASH_HOST_PHASE_1` (e.g., for running sp-sim
54
50
/// as a part of `omicron-dev`) while giving tests that want explicit
55
51
/// control the ability to precisely trigger completion of hashing.
56
- phase1_hash_policy : HostFlashHashPolicy ,
52
+ phase1_hash_policy : HostFlashHashPolicyInner ,
57
53
58
54
/// records whether a change to the stage0 "active slot" has been requested
59
55
pending_stage0_update : bool ,
@@ -78,6 +74,7 @@ impl SimSpUpdate {
78
74
pub ( crate ) fn new (
79
75
baseboard_kind : BaseboardKind ,
80
76
no_stage0_caboose : bool ,
77
+ phase1_hash_policy : HostFlashHashPolicy ,
81
78
) -> Self {
82
79
const SP_GITC0 : & str = "ffffffff" ;
83
80
const SP_GITC1 : & str = "fefefefe" ;
@@ -194,9 +191,7 @@ impl SimSpUpdate {
194
191
last_rot_update_data : None ,
195
192
last_host_phase1_update_data : BTreeMap :: new ( ) ,
196
193
phase1_hash_state : BTreeMap :: new ( ) ,
197
- phase1_hash_policy : HostFlashHashPolicy :: Timer (
198
- TIME_TO_HASH_HOST_PHASE_1 ,
199
- ) ,
194
+ phase1_hash_policy : phase1_hash_policy. 0 ,
200
195
201
196
pending_stage0_update : false ,
202
197
@@ -211,14 +206,11 @@ impl SimSpUpdate {
211
206
}
212
207
}
213
208
214
- /// Instead of host phase 1 hashing completing after a few seconds, return a
215
- /// handle that can be used to explicitly trigger completion.
216
- pub ( crate ) fn set_phase1_hash_policy_explicit_control (
209
+ pub ( crate ) fn set_phase1_hash_policy (
217
210
& mut self ,
218
- ) -> HostFlashHashCompletionSender {
219
- let ( tx, rx) = mpsc:: unbounded_channel ( ) ;
220
- self . phase1_hash_policy = HostFlashHashPolicy :: Channel ( rx) ;
221
- HostFlashHashCompletionSender ( tx)
211
+ policy : HostFlashHashPolicy ,
212
+ ) {
213
+ self . phase1_hash_policy = policy. 0 ;
222
214
}
223
215
224
216
pub ( crate ) fn sp_update_prepare (
@@ -493,28 +485,23 @@ impl SimSpUpdate {
493
485
& mut self ,
494
486
slot : u16 ,
495
487
) -> Result < ( ) , SpError > {
488
+ self . check_host_flash_state_and_policy ( slot) ;
496
489
match self
497
490
. phase1_hash_state
498
- . entry ( slot)
499
- . or_insert ( HostFlashHashState :: NeverHashed )
491
+ . get_mut ( & slot)
492
+ . expect ( "check_host_flash_state_and_policy always inserts" )
500
493
{
501
- // No current hash; record our start time so we can emulate hashing
502
- // taking a few seconds.
494
+ // Already hashed; this is a no-op.
495
+ HostFlashHashState :: Hashed ( _) => Ok ( ( ) ) ,
496
+ // No current hash; record our start time.
503
497
state @ ( HostFlashHashState :: NeverHashed
504
498
| HostFlashHashState :: HashInvalidated ) => {
505
499
* state = HostFlashHashState :: HashStarted ( Instant :: now ( ) ) ;
506
500
Ok ( ( ) )
507
501
}
508
- // Already hashed; this is a no-op.
509
- HostFlashHashState :: Hashed ( _) => Ok ( ( ) ) ,
510
- // Still hashing; check and see if it's done. This is either an
511
- // error (if we're still hashing) or a no-op (if we're done).
512
- HostFlashHashState :: HashStarted ( started) => {
513
- let started = * started;
514
- self . finalize_host_flash_hash_if_sufficient_time_elapsed (
515
- slot, started,
516
- ) ?;
517
- Ok ( ( ) )
502
+ // Still hashing; this is an error.
503
+ HostFlashHashState :: HashStarted ( _) => {
504
+ Err ( SpError :: Hf ( HfError :: HashInProgress ) )
518
505
}
519
506
}
520
507
}
@@ -523,51 +510,68 @@ impl SimSpUpdate {
523
510
& mut self ,
524
511
slot : u16 ,
525
512
) -> Result < [ u8 ; 32 ] , SpError > {
513
+ self . check_host_flash_state_and_policy ( slot) ;
526
514
match self
527
515
. phase1_hash_state
528
- . entry ( slot)
529
- . or_insert ( HostFlashHashState :: NeverHashed )
516
+ . get_mut ( & slot)
517
+ . expect ( "check_host_flash_state_and_policy always inserts" )
530
518
{
519
+ HostFlashHashState :: Hashed ( hash) => Ok ( * hash) ,
531
520
HostFlashHashState :: NeverHashed => {
532
521
Err ( SpError :: Hf ( HfError :: HashUncalculated ) )
533
522
}
534
- HostFlashHashState :: HashStarted ( started) => {
535
- let started = * started;
536
- self . finalize_host_flash_hash_if_sufficient_time_elapsed (
537
- slot, started,
538
- )
523
+ HostFlashHashState :: HashStarted ( _) => {
524
+ Err ( SpError :: Hf ( HfError :: HashInProgress ) )
539
525
}
540
- HostFlashHashState :: Hashed ( hash) => Ok ( * hash) ,
541
526
HostFlashHashState :: HashInvalidated => {
542
527
Err ( SpError :: Hf ( HfError :: RecalculateHash ) )
543
528
}
544
529
}
545
530
}
546
531
547
- fn finalize_host_flash_hash_if_sufficient_time_elapsed (
548
- & mut self ,
549
- slot : u16 ,
550
- started : Instant ,
551
- ) -> Result < [ u8 ; 32 ] , SpError > {
552
- match & mut self . phase1_hash_policy {
553
- HostFlashHashPolicy :: Timer ( duration) => {
554
- if started. elapsed ( ) < * duration {
555
- return Err ( SpError :: Hf ( HfError :: HashInProgress ) ) ;
556
- }
557
- }
558
- HostFlashHashPolicy :: Channel ( rx) => {
559
- if rx. try_recv ( ) . is_err ( ) {
560
- return Err ( SpError :: Hf ( HfError :: HashInProgress ) ) ;
561
- }
562
- }
532
+ fn check_host_flash_state_and_policy ( & mut self , slot : u16 ) {
533
+ let state = self
534
+ . phase1_hash_state
535
+ . entry ( slot)
536
+ . or_insert ( HostFlashHashState :: NeverHashed ) ;
537
+
538
+ // If we've already hashed this slot, we're done.
539
+ if matches ! ( state, HostFlashHashState :: Hashed ( _) ) {
540
+ return ;
563
541
}
564
542
565
- let data = self . last_host_phase1_update_data ( slot) ;
566
- let data = data. as_deref ( ) . unwrap_or ( & [ ] ) ;
567
- let hash = Sha256 :: digest ( & data) . into ( ) ;
568
- self . phase1_hash_state . insert ( slot, HostFlashHashState :: Hashed ( hash) ) ;
543
+ // Should we hash the flash now? It depends on our state + policy.
544
+ let should_hash = match ( & mut self . phase1_hash_policy , state) {
545
+ // If we want to always assume contents are hashed, compute that
546
+ // hash _unless_ the contents have changed (in which case a client
547
+ // would need to send us an explicit "start hashing" request).
548
+ (
549
+ HostFlashHashPolicyInner :: AssumeAlreadyHashed ,
550
+ HostFlashHashState :: HashInvalidated ,
551
+ ) => false ,
552
+ ( HostFlashHashPolicyInner :: AssumeAlreadyHashed , _) => true ,
553
+ // If we're timer based, only hash if the timer has elapsed.
554
+ (
555
+ HostFlashHashPolicyInner :: Timer ( timeout) ,
556
+ HostFlashHashState :: HashStarted ( started) ,
557
+ ) => * timeout >= started. elapsed ( ) ,
558
+ ( HostFlashHashPolicyInner :: Timer ( _) , _) => false ,
559
+ // If we're channel based, only hash if we've gotten a request to
560
+ // start hashing and there's a message in the channel.
561
+ (
562
+ HostFlashHashPolicyInner :: Channel ( rx) ,
563
+ HostFlashHashState :: HashStarted ( _) ,
564
+ ) => rx. try_recv ( ) . is_ok ( ) ,
565
+ ( HostFlashHashPolicyInner :: Channel ( _) , _) => false ,
566
+ } ;
569
567
570
- Ok ( hash)
568
+ if should_hash {
569
+ let data = self . last_host_phase1_update_data ( slot) ;
570
+ let data = data. as_deref ( ) . unwrap_or ( & [ ] ) ;
571
+ let hash = Sha256 :: digest ( & data) . into ( ) ;
572
+ self . phase1_hash_state
573
+ . insert ( slot, HostFlashHashState :: Hashed ( hash) ) ;
574
+ }
571
575
}
572
576
573
577
pub ( crate ) fn get_component_caboose_value (
@@ -784,14 +788,48 @@ fn fake_fwid_compute(data: &[u8]) -> Fwid {
784
788
785
789
#[ derive( Debug , Clone , Copy ) ]
786
790
enum HostFlashHashState {
791
+ Hashed ( [ u8 ; 32 ] ) ,
787
792
NeverHashed ,
788
793
HashStarted ( Instant ) ,
789
- Hashed ( [ u8 ; 32 ] ) ,
790
794
HashInvalidated ,
791
795
}
792
796
797
+ /// Policy controlling how `sp-sim` behaves when asked to flash its host phase 1
798
+ /// contents.
799
+ #[ derive( Debug ) ]
800
+ pub struct HostFlashHashPolicy ( HostFlashHashPolicyInner ) ;
801
+
802
+ impl HostFlashHashPolicy {
803
+ /// Always return computed hashes when asked.
804
+ ///
805
+ /// This emulates an SP that has previously computed its phase 1 flash hash
806
+ /// and whose contents haven't changed. Most Nexus tests should use this
807
+ /// policy by default to allow inventory collections to complete promptly.
808
+ pub fn assume_already_hashed ( ) -> Self {
809
+ Self ( HostFlashHashPolicyInner :: AssumeAlreadyHashed )
810
+ }
811
+
812
+ /// Return `HashInProgress` for `timeout` after hashing has started before
813
+ /// completing it successfully.
814
+ pub fn timer ( timeout : Duration ) -> Self {
815
+ Self ( HostFlashHashPolicyInner :: Timer ( timeout) )
816
+ }
817
+
818
+ /// Returns a channel that allows the caller to control when hashing
819
+ /// completes.
820
+ pub fn channel ( ) -> ( Self , HostFlashHashCompletionSender ) {
821
+ let ( tx, rx) = mpsc:: unbounded_channel ( ) ;
822
+ (
823
+ Self ( HostFlashHashPolicyInner :: Channel ( rx) ) ,
824
+ HostFlashHashCompletionSender ( tx) ,
825
+ )
826
+ }
827
+ }
828
+
793
829
#[ derive( Debug ) ]
794
- enum HostFlashHashPolicy {
830
+ enum HostFlashHashPolicyInner {
831
+ /// always assume hashing has already been computed
832
+ AssumeAlreadyHashed ,
795
833
/// complete hashing after `Duration` has elapsed
796
834
Timer ( Duration ) ,
797
835
/// complete hashing if there's a message in this channel
0 commit comments