Implement list_instances and `metalsmith list`

Change-Id: I51f83fcebf665be8b290774433fbf1da5108ec27
Story: #2003585
Task: #24891
This commit is contained in:
Dmitry Tantsur 2018-08-29 11:59:29 +02:00
parent 06a19626cd
commit d20abe3c79
6 changed files with 83 additions and 6 deletions

View File

@ -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)')

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 }}