@@ -1027,6 +1027,89 @@ def do_test(mock_destroy_build_req, mock_pop_inst_map,
1027
1027
1028
1028
do_test ()
1029
1029
1030
+ @mock .patch .object (objects .Instance , 'save' , new = mock .MagicMock ())
1031
+ @mock .patch .object (query .SchedulerQueryClient , 'select_destinations' )
1032
+ @mock .patch .object (conductor_manager .ComputeTaskManager ,
1033
+ '_set_vm_state_and_notify' , new = mock .MagicMock ())
1034
+ def test_build_instances_reschedule_not_recalc_mapping_if_claim_fails (
1035
+ self , mock_select_dests ):
1036
+
1037
+ rg1 = objects .RequestGroup (resources = {"CUSTOM_FOO" : 1 })
1038
+ request_spec = objects .RequestSpec (requested_resources = [rg1 ])
1039
+
1040
+ mock_select_dests .return_value = [[fake_selection1 ]]
1041
+ instance = fake_instance .fake_instance_obj (self .context )
1042
+ image = {'fake-data' : 'should_pass_silently' }
1043
+
1044
+ # build_instances() is a cast, we need to wait for it to complete
1045
+ self .useFixture (cast_as_call .CastAsCall (self ))
1046
+
1047
+ @mock .patch ('nova.conductor.manager.ComputeTaskManager.'
1048
+ '_fill_provider_mapping' )
1049
+ @mock .patch ('nova.scheduler.utils.claim_resources' ,
1050
+ # simulate that the first claim fails during re-schedule
1051
+ side_effect = [False , True ])
1052
+ @mock .patch ('nova.objects.request_spec.RequestSpec.from_primitives' ,
1053
+ return_value = request_spec )
1054
+ @mock .patch .object (self .conductor_manager .compute_rpcapi ,
1055
+ 'build_and_run_instance' )
1056
+ @mock .patch .object (self .conductor_manager ,
1057
+ '_populate_instance_mapping' )
1058
+ @mock .patch .object (self .conductor_manager , '_destroy_build_request' )
1059
+ def do_test (mock_destroy_build_req , mock_pop_inst_map ,
1060
+ mock_build_and_run , mock_request_spec_from_primitives ,
1061
+ mock_claim , mock_rp_mapping ):
1062
+ self .conductor .build_instances (
1063
+ context = self .context ,
1064
+ instances = [instance ],
1065
+ image = image ,
1066
+ filter_properties = {'retry' : {'num_attempts' : 1 , 'hosts' : []}},
1067
+ admin_password = 'admin_password' ,
1068
+ injected_files = 'injected_files' ,
1069
+ requested_networks = None ,
1070
+ security_groups = 'security_groups' ,
1071
+ block_device_mapping = 'block_device_mapping' ,
1072
+ legacy_bdm = False ,
1073
+ host_lists = copy .deepcopy (fake_host_lists_alt ),
1074
+ request_spec = request_spec )
1075
+
1076
+ expected_build_run_host_list = copy .copy (fake_host_lists_alt [0 ])
1077
+ if expected_build_run_host_list :
1078
+ # first is consumed but the claim fails so the conductor takes
1079
+ # the next host
1080
+ expected_build_run_host_list .pop (0 )
1081
+ # second is consumed and claim succeeds
1082
+ expected_build_run_host_list .pop (0 )
1083
+ mock_build_and_run .assert_called_with (
1084
+ self .context ,
1085
+ instance = mock .ANY ,
1086
+ host = 'host2' ,
1087
+ image = image ,
1088
+ request_spec = request_spec ,
1089
+ filter_properties = {'retry' : {'num_attempts' : 2 ,
1090
+ 'hosts' : [['host2' , 'node2' ]]},
1091
+ 'limits' : {}},
1092
+ admin_password = 'admin_password' ,
1093
+ injected_files = 'injected_files' ,
1094
+ requested_networks = None ,
1095
+ security_groups = 'security_groups' ,
1096
+ block_device_mapping = test .MatchType (
1097
+ objects .BlockDeviceMappingList ),
1098
+ node = 'node2' ,
1099
+ limits = None ,
1100
+ host_list = expected_build_run_host_list )
1101
+
1102
+ # called only once when the claim succeeded
1103
+ mock_rp_mapping .assert_called_once_with (
1104
+ self .context , instance .uuid , mock .ANY ,
1105
+ test .MatchType (objects .Selection ))
1106
+ actual_request_spec = mock_rp_mapping .mock_calls [0 ][1 ][2 ]
1107
+ self .assertEqual (
1108
+ rg1 .resources ,
1109
+ actual_request_spec .requested_resources [0 ].resources )
1110
+
1111
+ do_test ()
1112
+
1030
1113
@mock .patch .object (cinder .API , 'attachment_get' )
1031
1114
@mock .patch .object (cinder .API , 'attachment_create' )
1032
1115
@mock .patch .object (block_device_obj .BlockDeviceMapping , 'save' )
0 commit comments