diff --git a/shade/__init__.py b/shade/__init__.py index f77e2dec9..a6356989e 100644 --- a/shade/__init__.py +++ b/shade/__init__.py @@ -4468,6 +4468,74 @@ class OperatorCloud(OpenStackCloud): except ironic_exceptions.ClientException: return None + def inspect_machine(self, name_or_id, wait=False, timeout=3600): + """Inspect a Barmetal machine + + Engages the Ironic node inspection behavior in order to collect + metadata about the baremetal machine. + + :param name_or_id: String representing machine name or UUID value in + order to identify the machine. + + :param wait: Boolean value controlling if the method is to wait for + the desired state to be reached or a failure to occur. + + :param timeout: Integer value, defautling to 3600 seconds, for the$ + wait state to reach completion. + + :returns: Dictonary representing the current state of the machine + upon exit of the method. + """ + + return_to_available = False + + machine = self.get_machine(name_or_id) + if not machine: + raise OpenStackCloudException( + "Machine inspection failed to find: %s." % name_or_id) + + # NOTE(TheJulia): If in available state, we can do this, however + # We need to to move the host back to m + if "available" in machine['provision_state']: + return_to_available = True + # NOTE(TheJulia): Changing available machine to managedable state + # and due to state transitions we need to until that transition has + # completd. + self.node_set_provision_state(machine['uuid'], 'manage', + wait=True, timeout=timeout) + elif ("manage" not in machine['provision_state'] and + "inspect failed" not in machine['provision_state']): + raise OpenStackCloudException( + "Machine must be in 'manage' or 'available' state to " + "engage inspection: Machine: %s State: %s" + % (machine['uuid'], machine['provision_state'])) + try: + machine = self.node_set_provision_state(machine['uuid'], 'inspect') + if wait: + for count in _utils._iterate_timeout( + timeout, + "Timeout waiting for node transition to " + "target state of 'inpection'"): + machine = self.get_machine(name_or_id) + + if "inspect failed" in machine['provision_state']: + raise OpenStackCloudException( + "Inspection of node %s failed, last error: %s" + % (machine['uuid'], machine['last_error'])) + + if "manageable" in machine['provision_state']: + break + + if return_to_available: + machine = self.node_set_provision_state( + machine['uuid'], 'provide', wait=wait, timeout=timeout) + + return(machine) + + except Exception as e: + raise OpenStackCloudException( + "Error inspecting machine: %s" % e) + def register_machine(self, nics, wait=False, timeout=3600, lock_timeout=600, **kwargs): """Register Baremetal with Ironic diff --git a/shade/tests/unit/test_shade_operator.py b/shade/tests/unit/test_shade_operator.py index 077aa266b..1aeef011d 100644 --- a/shade/tests/unit/test_shade_operator.py +++ b/shade/tests/unit/test_shade_operator.py @@ -363,6 +363,180 @@ class TestShadeOperator(base.TestCase): '00000000-0000-0000-0000-000000000000', expected_patch) + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_fail_active(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class active_machine: + uuid = machine_uuid + provision_state = "active" + + mock_client.node.get.return_value = active_machine + self.assertRaises( + shade.OpenStackCloudException, + self.cloud.inspect_machine, + machine_uuid, + wait=True, + timeout=0.001) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_failed(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class inspect_failed_machine: + uuid = machine_uuid + provision_state = "inspect failed" + last_error = "kaboom" + + mock_client.node.get.return_value = inspect_failed_machine + self.cloud.inspect_machine(machine_uuid) + self.assertTrue(mock_client.node.set_provision_state.called) + self.assertEqual( + mock_client.node.set_provision_state.call_count, 1) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_managable(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class manageable_machine: + uuid = machine_uuid + provision_state = "manageable" + + mock_client.node.get.return_value = manageable_machine + self.cloud.inspect_machine(machine_uuid) + self.assertEqual( + mock_client.node.set_provision_state.call_count, 1) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_available(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class available_machine: + uuid = machine_uuid + provision_state = "available" + + class manageable_machine: + uuid = machine_uuid + provision_state = "manageable" + + class inspecting_machine: + uuid = machine_uuid + provision_state = "inspecting" + + mock_client.node.get.side_effect = iter([ + available_machine, + available_machine, + manageable_machine, + manageable_machine, + inspecting_machine]) + self.cloud.inspect_machine(machine_uuid) + self.assertTrue(mock_client.node.set_provision_state.called) + self.assertEqual( + mock_client.node.set_provision_state.call_count, 3) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_available_wait(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class available_machine: + uuid = machine_uuid + provision_state = "available" + + class manageable_machine: + uuid = machine_uuid + provision_state = "manageable" + + class inspecting_machine: + uuid = machine_uuid + provision_state = "inspecting" + + mock_client.node.get.side_effect = iter([ + available_machine, + available_machine, + manageable_machine, + inspecting_machine, + manageable_machine, + available_machine, + available_machine]) + expected_return_value = dict( + uuid=machine_uuid, + provision_state="available" + ) + + return_value = self.cloud.inspect_machine( + machine_uuid, wait=True, timeout=0.001) + self.assertTrue(mock_client.node.set_provision_state.called) + self.assertEqual( + mock_client.node.set_provision_state.call_count, 3) + self.assertDictEqual(expected_return_value, return_value) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_wait(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class manageable_machine: + uuid = machine_uuid + provision_state = "manageable" + + class inspecting_machine: + uuid = machine_uuid + provision_state = "inspecting" + + expected_return_value = dict( + uuid=machine_uuid, + provision_state="manageable" + ) + mock_client.node.get.side_effect = iter([ + manageable_machine, + inspecting_machine, + inspecting_machine, + manageable_machine, + manageable_machine]) + + return_value = self.cloud.inspect_machine( + machine_uuid, wait=True, timeout=0.001) + self.assertDictEqual(expected_return_value, return_value) + + @mock.patch.object(shade.OperatorCloud, 'ironic_client') + def test_inspect_machine_inspect_failed(self, mock_client): + + machine_uuid = '00000000-0000-0000-0000-000000000000' + + class manageable_machine: + uuid = machine_uuid + provision_state = "manageable" + last_error = None + + class inspecting_machine: + uuid = machine_uuid + provision_state = "inspecting" + last_error = None + + class inspect_failed_machine: + uuid = machine_uuid + provision_state = "inspect failed" + last_error = "kaboom" + + mock_client.node.get.side_effect = iter([ + manageable_machine, + inspecting_machine, + inspect_failed_machine]) + self.assertRaises( + shade.OpenStackCloudException, + self.cloud.inspect_machine, + machine_uuid, + wait=True, + timeout=0.001) + self.assertEqual( + mock_client.node.set_provision_state.call_count, 1) + self.assertEqual(mock_client.node.get.call_count, 3) + @mock.patch.object(shade.OperatorCloud, 'ironic_client') def test_register_machine(self, mock_client): class fake_node: