diff --git a/shade/__init__.py b/shade/__init__.py index 1479c1f1f..f77e2dec9 100644 --- a/shade/__init__.py +++ b/shade/__init__.py @@ -4837,7 +4837,12 @@ class OperatorCloud(OpenStackCloud): "ironic node %s failed to validate. " "(deploy: %s, power: %s)" % (ifaces.deploy, ifaces.power)) - def node_set_provision_state(self, name_or_id, state, configdrive=None): + def node_set_provision_state(self, + name_or_id, + state, + configdrive=None, + wait=False, + timeout=3600): """Set Node Provision State Enables a user to provision a Machine and optionally define a @@ -4854,19 +4859,39 @@ class OperatorCloud(OpenStackCloud): configuration drive file and post the file contents to the API for deployment. + :param boolean wait: A boolean value, defaulted to false, to control + if the method will wait for the desire end state + to be reached before returning. + :param integer timeout: Integer value, defaulting to 3600 seconds, + representing the amount of time to wait for + the desire end state to be reached. :raises: OpenStackCloudException on operation error. - :returns: Per the API, no value should be returned with a successful - operation. + :returns: Dictonary representing the current state of the machine + upon exit of the method. """ try: - return meta.obj_to_dict( - self.manager.submitTask( - _tasks.MachineSetProvision(node_uuid=name_or_id, - state=state, - configdrive=configdrive)) - ) + machine = self.manager.submitTask( + _tasks.MachineSetProvision(node_uuid=name_or_id, + state=state, + configdrive=configdrive)) + + if wait: + for count in _utils._iterate_timeout( + timeout, + "Timeout waiting for node transition to " + "target state of '%s'" % state): + machine = self.get_machine(name_or_id) + if state in machine['provision_state']: + break + if ("available" in machine['provision_state'] and + "provide" in state): + break + else: + machine = self.get_machine(name_or_id) + return machine + except Exception as e: raise OpenStackCloudException( "Baremetal machine node failed change provision" diff --git a/shade/tests/unit/test_shade_operator.py b/shade/tests/unit/test_shade_operator.py index a456afd2b..077aa266b 100644 --- a/shade/tests/unit/test_shade_operator.py +++ b/shade/tests/unit/test_shade_operator.py @@ -660,17 +660,95 @@ class TestShadeOperator(base.TestCase): @mock.patch.object(shade.OperatorCloud, 'ironic_client') def test_node_set_provision_state(self, mock_client): + + class active_node_state: + provision_state = "active" + + active_return_value = dict( + provision_state="active") + mock_client.node.set_provision_state.return_value = None + mock_client.node.get.return_value = active_node_state node_id = 'node01' return_value = self.cloud.node_set_provision_state( node_id, 'active', configdrive='http://127.0.0.1/file.iso') - self.assertEqual({}, return_value) + self.assertEqual(active_return_value, return_value) mock_client.node.set_provision_state.assert_called_with( node_uuid='node01', state='active', configdrive='http://127.0.0.1/file.iso') + self.assertTrue(mock_client.node.get.called) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_node_set_provision_state_wait_timeout(self, mock_client): + class deploying_node_state: + provision_state = "deploying" + + class active_node_state: + provision_state = "active" + + class managable_node_state: + provision_state = "managable" + + class available_node_state: + provision_state = "available" + + active_return_value = dict( + provision_state="active") + mock_client.node.get.return_value = active_node_state + mock_client.node.set_provision_state.return_value = None + node_id = 'node01' + return_value = self.cloud.node_set_provision_state( + node_id, + 'active', + configdrive='http://127.0.0.1/file.iso', + wait=True) + + self.assertEqual(active_return_value, return_value) + mock_client.node.set_provision_state.assert_called_with( + node_uuid='node01', + state='active', + configdrive='http://127.0.0.1/file.iso') + self.assertTrue(mock_client.node.get.called) + mock_client.mock_reset() + mock_client.node.get.return_value = deploying_node_state + self.assertRaises( + shade.OpenStackCloudException, + self.cloud.node_set_provision_state, + node_id, + 'active', + configdrive='http://127.0.0.1/file.iso', + wait=True, + timeout=0.001) + self.assertTrue(mock_client.node.get.called) + mock_client.node.set_provision_state.assert_called_with( + node_uuid='node01', + state='active', + configdrive='http://127.0.0.1/file.iso') + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_node_set_provision_state_wait_provide(self, mock_client): + + class managable_node_state: + provision_state = "managable" + + class available_node_state: + provision_state = "available" + + node_provide_return_value = dict( + provision_state="available") + + mock_client.node.get.side_effect = iter([ + managable_node_state, + available_node_state]) + return_value = self.cloud.node_set_provision_state( + 'test_node', + 'provide', + wait=True) + self.assertEqual(mock_client.node.get.call_count, 2) + self.assertDictEqual(node_provide_return_value, return_value) @mock.patch.object(shade.OperatorCloud, 'ironic_client') def test_activate_node(self, mock_client):