From d20abe3c799b95b6c8ad4610e595a98e9f7bb57d Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Wed, 29 Aug 2018 11:59:29 +0200 Subject: [PATCH] Implement list_instances and `metalsmith list` Change-Id: I51f83fcebf665be8b290774433fbf1da5108ec27 Story: #2003585 Task: #24891 --- metalsmith/_cmd.py | 8 ++++++++ metalsmith/_instance.py | 15 ++++++++++----- metalsmith/_provisioner.py | 11 +++++++++++ metalsmith/test/test_cmd.py | 24 ++++++++++++++++++++++++ metalsmith/test/test_provisioner.py | 20 ++++++++++++++++++++ playbooks/integration/exercise.yaml | 11 ++++++++++- 6 files changed, 83 insertions(+), 6 deletions(-) diff --git a/metalsmith/_cmd.py b/metalsmith/_cmd.py index e5ae636..76ccde6 100644 --- a/metalsmith/_cmd.py +++ b/metalsmith/_cmd.py @@ -85,6 +85,11 @@ def _do_wait(api, args, formatter): formatter.show(instances) +def _do_list(api, args, formatter): + instances = api.list_instances() + formatter.show(instances) + + def _parse_args(args, config): parser = argparse.ArgumentParser( description='Deployment and Scheduling tool for Bare Metal') @@ -151,6 +156,9 @@ def _parse_args(args, config): show.set_defaults(func=_do_show) show.add_argument('instance', nargs='+', help='instance UUID(s)') + show = subparsers.add_parser('list') + show.set_defaults(func=_do_list) + wait = subparsers.add_parser('wait') wait.set_defaults(func=_do_wait) wait.add_argument('instance', nargs='+', help='instance UUID(s)') diff --git a/metalsmith/_instance.py b/metalsmith/_instance.py index 4c367b3..477ce5d 100644 --- a/metalsmith/_instance.py +++ b/metalsmith/_instance.py @@ -16,14 +16,15 @@ from metalsmith import _os_api +_PROGRESS_STATES = frozenset(['deploying', 'wait call-back', + 'deploy complete']) # NOTE(dtantsur): include available since there is a period of time between # claiming the instance and starting the actual provisioning via ironic. -_DEPLOYING_STATES = frozenset(['available', 'deploying', 'wait call-back', - 'deploy complete']) +_DEPLOYING_STATES = _PROGRESS_STATES | {'available'} _ACTIVE_STATES = frozenset(['active']) -_ERROR_STATE = frozenset(['error', 'deploy failed']) +_ERROR_STATES = frozenset(['error', 'deploy failed']) -_HEALTHY_STATES = frozenset(['deploying', 'active']) +_HEALTHY_STATES = _PROGRESS_STATES | _ACTIVE_STATES class Instance(object): @@ -58,6 +59,10 @@ class Instance(object): """Whether the node is deployed.""" return self._node.provision_state in _ACTIVE_STATES + @property + def _is_deployed_by_metalsmith(self): + return _os_api.HOSTNAME_FIELD in self._node.instance_info + @property def is_healthy(self): """Whether the node is not at fault or maintenance.""" @@ -101,7 +106,7 @@ class Instance(object): prov_state = self._node.provision_state if prov_state in _DEPLOYING_STATES: return 'deploying' - elif prov_state in _ERROR_STATE: + elif prov_state in _ERROR_STATES: return 'error' elif prov_state in _ACTIVE_STATES: if self._node.maintenance: diff --git a/metalsmith/_provisioner.py b/metalsmith/_provisioner.py index 06ef913..126d8ac 100644 --- a/metalsmith/_provisioner.py +++ b/metalsmith/_provisioner.py @@ -506,3 +506,14 @@ class Provisioner(object): self._api.get_node(inst, accept_hostname=True)) for inst in instances ] + + def list_instances(self): + """List instances deployed by metalsmith. + + :return: list of :py:class:`metalsmith.Instance` objects. + """ + nodes = self._api.list_nodes(provision_state=None, associated=True) + instances = [i for i in + (_instance.Instance(self._api, node) for node in nodes) + if i._is_deployed_by_metalsmith] + return instances diff --git a/metalsmith/test/test_cmd.py b/metalsmith/test/test_cmd.py index 28188d6..3c616a6 100644 --- a/metalsmith/test/test_cmd.py +++ b/metalsmith/test/test_cmd.py @@ -751,6 +751,18 @@ class TestShowWait(testtools.TestCase): mock_pr.return_value.show_instances.assert_called_once_with( ['uuid1', 'hostname2']) + def test_list(self, mock_os_conf, mock_pr): + mock_pr.return_value.list_instances.return_value = self.instances + args = ['list'] + _cmd.main(args) + + self.mock_print.assert_has_calls([ + mock.call(mock.ANY, node='name-1 (UUID 1)', state='active'), + mock.call(mock.ANY, ips='private=1.2.3.4'), + mock.call(mock.ANY, node='name-2 (UUID 2)', state='deploying'), + ]) + mock_pr.return_value.list_instances.assert_called_once_with() + def test_wait(self, mock_os_conf, mock_pr): mock_pr.return_value.wait_for_provisioning.return_value = ( self.instances) @@ -790,6 +802,18 @@ class TestShowWait(testtools.TestCase): {'hostname1': {'1': 'name-1'}, 'hostname2': {'2': 'name-2'}}) + def test_list_json(self, mock_os_conf, mock_pr): + mock_pr.return_value.list_instances.return_value = self.instances + args = ['--format', 'json', 'list'] + + fake_io = six.StringIO() + with mock.patch('sys.stdout', fake_io): + _cmd.main(args) + self.assertEqual(json.loads(fake_io.getvalue()), + {'hostname1': {'1': 'name-1'}, + 'hostname2': {'2': 'name-2'}}) + mock_pr.return_value.list_instances.assert_called_once_with() + def test_wait_json(self, mock_os_conf, mock_pr): mock_pr.return_value.wait_for_provisioning.return_value = ( self.instances) diff --git a/metalsmith/test/test_provisioner.py b/metalsmith/test/test_provisioner.py index 57b49e7..0c38b2e 100644 --- a/metalsmith/test/test_provisioner.py +++ b/metalsmith/test/test_provisioner.py @@ -903,3 +903,23 @@ class TestWaitForState(Base): mock_sleep.assert_called_with(1) self.assertEqual(3, mock_sleep.call_count) + + +class TestListInstances(Base): + def setUp(self): + super(TestListInstances, self).setUp() + self.nodes = [ + mock.Mock(spec=_os_api.NODE_FIELDS, provision_state=state, + instance_info={'metalsmith_hostname': '1234'}) + for state in ('active', 'active', 'deploying', 'wait call-back', + 'deploy failed', 'available') + ] + del self.nodes[-1].instance_info['metalsmith_hostname'] + self.api.list_nodes.return_value = self.nodes + + def test_list(self): + instances = self.pr.list_instances() + self.assertTrue(isinstance(i, _instance.Instance) for i in instances) + self.assertEqual(self.nodes[:5], [i.node for i in instances]) + self.api.list_nodes.assert_called_once_with(provision_state=None, + associated=True) diff --git a/playbooks/integration/exercise.yaml b/playbooks/integration/exercise.yaml index 0a4c8d0..800d979 100644 --- a/playbooks/integration/exercise.yaml +++ b/playbooks/integration/exercise.yaml @@ -29,7 +29,7 @@ - "{{ ssh_key_file }}" user_name: "{{ configure_instance_user | default('') }}" -- name: Get instance info via CLI +- name: Get instance info via CLI show command: metalsmith --format=json show test register: instance_info @@ -38,6 +38,15 @@ instance: "{{ (instance_info.stdout | from_json).test }}" failed_when: instance.state != 'active' or instance.node.provision_state != 'active' +- name: Get instance info via CLI list + command: metalsmith --format=json list + register: instance_info_via_list + +- name: Verify that instance info via list is also correct + set_fact: + instance_via_list: "{{ (instance_info_via_list.stdout | from_json).test }}" + failed_when: instance_via_list.state != 'active' or instance_via_list.node.provision_state != 'active' + - name: Show active node information command: openstack baremetal node show {{ instance.node.uuid }}