@@ -217,9 +217,31 @@ impl Policy {
217217 result. unregister_and_destroy_runner_ids . push ( id) ;
218218 }
219219
220- let profile_wanted_counts = self
220+ // Adjust for critical runners, regardless of whether they fit the new policy.
221+ let mut scenario = self
222+ . profiles ( )
223+ . map ( |( key, profile) | {
224+ (
225+ key. to_owned ( ) ,
226+ self . target_runner_count ( profile)
227+ . max ( self . critical_runner_count ( profile) ) ,
228+ )
229+ } )
230+ . collect :: < BTreeMap < _ , _ > > ( ) ;
231+ let mut profile_target_counts = self
221232 . profiles ( )
222- . map ( |( key, profile) | ( key, self . wanted_runner_count ( profile) ) ) ;
233+ . map ( |( key, profile) | ( key. to_owned ( ) , self . target_runner_count ( profile) ) )
234+ . collect :: < BTreeMap < _ , _ > > ( ) ;
235+ let mut profile_wanted_counts = self
236+ . profiles ( )
237+ . map ( |( key, profile) | ( key. to_owned ( ) , self . wanted_runner_count ( profile) ) )
238+ . collect :: < BTreeMap < _ , _ > > ( ) ;
239+ self . adjust_runner_counts_for_resource_limits (
240+ & mut scenario,
241+ & mut profile_target_counts,
242+ & mut profile_wanted_counts,
243+ ) ;
244+
223245 for ( profile_key, wanted_count) in profile_wanted_counts {
224246 result
225247 . create_counts_by_profile_key
@@ -622,13 +644,41 @@ impl Policy {
622644 * count += * extra_count;
623645 }
624646 }
625-
626- // Starting with the ideal scenario, try to adjust the scenario until it validates.
627647 let mut adjusted_override_counts = profile_override_counts;
628- ' validate: while self
629- . validate_resource_requirements ( dbg ! ( & scenario) )
630- . is_err ( )
631- {
648+ self . adjust_runner_counts_for_resource_limits (
649+ & mut scenario,
650+ & mut adjusted_override_counts,
651+ & mut profile_extra_counts,
652+ ) ;
653+
654+ // Fail if the requested override had to be adjusted so far that it became meaningless.
655+ if profile_extra_counts. values ( ) . sum :: < usize > ( ) == 0 {
656+ bail ! ( "Requested override had to be adjusted so far that it became meaningless" ) ;
657+ }
658+
659+ self . current_override = Some ( Override {
660+ profile_override_counts : adjusted_override_counts,
661+ profile_target_counts : scenario,
662+ actual_runner_ids_by_profile_key : BTreeMap :: default ( ) ,
663+ } ) ;
664+
665+ Ok ( self
666+ . current_override
667+ . as_ref ( )
668+ . expect ( "Guaranteed by assignment" ) )
669+ }
670+
671+ /// - `scenario` is the proposed ideal scenario, including `adjusted_counts` and critical runners
672+ /// - `adjusted_counts` are the proposed runner counts after we create `extra_counts`
673+ /// - `extra_counts` are the proposed create counts that will achieve `adjusted_counts`
674+ fn adjust_runner_counts_for_resource_limits (
675+ & self ,
676+ scenario : & mut BTreeMap < String , usize > ,
677+ adjusted_counts : & mut BTreeMap < String , usize > ,
678+ profile_extra_counts : & mut BTreeMap < String , usize > ,
679+ ) {
680+ // Starting with the given scenario, try to adjust the scenario until it validates.
681+ ' validate: while self . validate_resource_requirements ( & scenario) . is_err ( ) {
632682 // Try profiles in descending count order, to avoid starving niche runners.
633683 let candidate_profile_keys = scenario
634684 . clone ( )
@@ -639,7 +689,7 @@ impl Policy {
639689 . collect :: < Vec < _ > > ( ) ;
640690 // First try decrementing a profile that was not requested.
641691 for profile_key in candidate_profile_keys. iter ( ) {
642- if !adjusted_override_counts . contains_key ( profile_key) {
692+ if !adjusted_counts . contains_key ( profile_key) {
643693 let profile = self
644694 . profile ( profile_key)
645695 . expect ( "Guaranteed by initialiser" ) ;
@@ -655,7 +705,7 @@ impl Policy {
655705 }
656706 // If none of those profiles could be decremented, try decrementing a profile that was requested.
657707 for profile_key in candidate_profile_keys. iter ( ) {
658- if adjusted_override_counts . contains_key ( profile_key) {
708+ if adjusted_counts . contains_key ( profile_key) {
659709 let profile = self
660710 . profile ( profile_key)
661711 . expect ( "Guaranteed by initialiser" ) ;
@@ -664,8 +714,7 @@ impl Policy {
664714 . expect ( "Guaranteed by candidate_profile_keys" ) ;
665715 // Only try decrementing profiles that have non-critical runners to spare.
666716 if * count > self . critical_runner_count ( profile) {
667- if let Some ( override_count) = adjusted_override_counts. get_mut ( profile_key)
668- {
717+ if let Some ( override_count) = adjusted_counts. get_mut ( profile_key) {
669718 let extra_count = profile_extra_counts
670719 . get_mut ( profile_key)
671720 . expect ( "Guaranteed by initialiser" ) ;
@@ -683,23 +732,7 @@ impl Policy {
683732 break ;
684733 }
685734
686- // Fail if the requested override had to be adjusted so far that it became meaningless.
687- if profile_extra_counts. values ( ) . sum :: < usize > ( ) == 0 {
688- bail ! ( "Requested override had to be adjusted so far that it became meaningless" ) ;
689- }
690-
691- info ! ( adjusted_override_counts = ?adjusted_override_counts, extra_counts = ?profile_extra_counts, ?scenario, "Found solution for override request" ) ;
692-
693- self . current_override = Some ( Override {
694- profile_override_counts : adjusted_override_counts,
695- profile_target_counts : scenario,
696- actual_runner_ids_by_profile_key : BTreeMap :: default ( ) ,
697- } ) ;
698-
699- Ok ( self
700- . current_override
701- . as_ref ( )
702- . expect ( "Guaranteed by assignment" ) )
735+ info ! ( ?scenario, adjusted_counts = ?adjusted_counts, extra_counts = ?profile_extra_counts, "Best possible proposal" ) ;
703736 }
704737
705738 pub fn cancel_override ( & mut self ) -> eyre:: Result < Option < Override > > {
@@ -1388,6 +1421,48 @@ mod test {
13881421 Ok ( ( ) )
13891422 }
13901423
1424+ #[ test]
1425+ fn test_compute_runner_changes_dynamic ( ) -> eyre:: Result < ( ) > {
1426+ let mut policy = Policy :: new (
1427+ [
1428+ ( "linux" . to_owned ( ) , profile ( "linux" , 5 , 8 , "0B" ) ) ,
1429+ ( "windows" . to_owned ( ) , profile ( "windows" , 4 , 8 , "0B" ) ) ,
1430+ ( "macos" . to_owned ( ) , profile ( "macos" , 3 , 8 , "0B" ) ) ,
1431+ ( "wpt" . to_owned ( ) , profile ( "wpt" , 0 , 24 , "0B" ) ) ,
1432+ ]
1433+ . into ( ) ,
1434+ ) ?;
1435+ let fresh = system_time_minus_seconds ( 0 ) ;
1436+ set_base_image_mtime_for_test ( "/var/lib/libvirt/images/base/linux/base.img@" , fresh) ;
1437+ set_base_image_mtime_for_test ( "/var/lib/libvirt/images/base/macos/base.img@" , fresh) ;
1438+ set_base_image_mtime_for_test ( "/var/lib/libvirt/images/base/windows/base.img@" , fresh) ;
1439+ set_base_image_mtime_for_test ( "/var/lib/libvirt/images/base/wpt/base.img@" , fresh) ;
1440+ policy. set_base_image_snapshot ( "linux" , "" ) ?;
1441+ policy. set_base_image_snapshot ( "macos" , "" ) ?;
1442+ policy. set_base_image_snapshot ( "windows" , "" ) ?;
1443+ policy. set_base_image_snapshot ( "wpt" , "" ) ?;
1444+
1445+ // Proposed create counts should be adjusted for critical runners, regardless of whether
1446+ // those runners fit the current policy.
1447+ let fake_runners = vec ! [ FakeRunner :: busy( "wpt" ) , FakeRunner :: reserved( "wpt" ) ] ;
1448+ policy. set_runners ( runners ( fake_runners. clone ( ) ) ) ;
1449+ assert_eq ! (
1450+ policy. compute_runner_changes( ) ?,
1451+ RunnerChanges {
1452+ unregister_and_destroy_runner_ids: vec![ ] ,
1453+ create_counts_by_profile_key: [
1454+ ( "linux" . to_owned( ) , 2 ) ,
1455+ ( "windows" . to_owned( ) , 2 ) ,
1456+ ( "macos" . to_owned( ) , 2 ) ,
1457+ ( "wpt" . to_owned( ) , 0 ) ,
1458+ ]
1459+ . into( ) ,
1460+ } ,
1461+ ) ;
1462+
1463+ Ok ( ( ) )
1464+ }
1465+
13911466 #[ test]
13921467 fn test_try_override ( ) -> eyre:: Result < ( ) > {
13931468 let mut policy = Policy :: new (
0 commit comments