Expose Allocation objects on Instance

Change-Id: If62a6829478c532428aff2f5f51edc35df8ee2e1
This commit is contained in:
Dmitry Tantsur 2019-05-16 11:49:38 +02:00
parent ce77bf8e1e
commit 1afa4ac4ed
7 changed files with 74 additions and 17 deletions

View File

@ -25,6 +25,9 @@ Installation
pip install --user metalsmith
.. note::
The current versions of *metalsmith* require Bare Metal API from the Stein
release or newer. Use the 0.11 release series for older versions.
Contributing
------------

View File

@ -5,7 +5,7 @@ fixtures==3.0.0
flake8-import-order==0.13
hacking==1.0.0
mock==2.0
openstacksdk==0.22.0
openstacksdk==0.25.0
pbr==2.0.0
Pygments==2.2.0
requests==2.18.4

View File

@ -86,10 +86,16 @@ _DEPLOYED_STATES = frozenset([InstanceState.ACTIVE, InstanceState.MAINTENANCE])
class Instance(object):
"""Instance status in metalsmith."""
def __init__(self, connection, node):
def __init__(self, connection, node, allocation=None):
self._connection = connection
self._uuid = node.id
self._node = node
self._allocation = allocation
@property
def allocation(self):
"""Allocation object associated with the node (if any)."""
return self._allocation
@property
def hostname(self):
@ -163,6 +169,8 @@ class Instance(object):
def to_dict(self):
"""Convert instance to a dict."""
return {
'allocation': (self._allocation.to_dict()
if self._allocation is not None else None),
'hostname': self.hostname,
'ip_addresses': self.ip_addresses(),
'node': self._node.to_dict(),

View File

@ -332,6 +332,7 @@ class Provisioner(_utils.GetNodeMixin):
else:
# Update the node to return it's latest state
node = self._get_node(node, refresh=True)
# We don't create allocations yet, so don't use _get_instance.
instance = _instance.Instance(self.connection, node)
return instance
@ -358,7 +359,9 @@ class Provisioner(_utils.GetNodeMixin):
nodes = [self._get_node(n, accept_hostname=True) for n in nodes]
nodes = self.connection.baremetal.wait_for_nodes_provision_state(
nodes, 'active', timeout=timeout)
return [_instance.Instance(self.connection, node) for node in nodes]
# Using _get_instance in case the deployment started by something
# external that uses allocations.
return [self._get_instance(node) for node in nodes]
def _clean_instance_info(self, instance_info):
return {key: value
@ -426,6 +429,16 @@ class Provisioner(_utils.GetNodeMixin):
"""
return self.show_instances([instance_id])[0]
def _get_instance(self, ident):
node = self._get_node(ident, accept_hostname=True)
if node.allocation_id:
allocation = self.connection.baremetal.get_allocation(
node.allocation_id)
else:
allocation = None
return _instance.Instance(self.connection, node,
allocation=allocation)
def show_instances(self, instances):
"""Show information about instance.
@ -439,12 +452,7 @@ class Provisioner(_utils.GetNodeMixin):
if one of the instances are not valid instances.
"""
with self._cache_node_list_for_lookup():
result = [
_instance.Instance(
self.connection,
self._get_node(inst, accept_hostname=True))
for inst in instances
]
result = [self._get_instance(inst) for inst in instances]
# NOTE(dtantsur): do not accept node names as valid instances if they
# are not deployed or being deployed.
missing = [inst for (res, inst) in zip(result, instances)
@ -461,8 +469,6 @@ class Provisioner(_utils.GetNodeMixin):
:return: list of :py:class:`metalsmith.Instance` objects.
"""
nodes = self.connection.baremetal.nodes(associated=True, details=True)
instances = [i for i in
(_instance.Instance(self.connection, node)
for node in nodes)
instances = [i for i in map(self._get_instance, nodes)
if i.state != _instance.InstanceState.UNKNOWN]
return instances

View File

@ -129,7 +129,28 @@ class TestInstanceStates(test_provisioner.Base):
mock_ips.return_value = {'private': ['1.2.3.4']}
to_dict = self.instance.to_dict()
self.assertEqual({'hostname': 'host',
self.assertEqual({'allocation': None,
'hostname': 'host',
'ip_addresses': {'private': ['1.2.3.4']},
'node': {'node': 'dict'},
'state': 'deploying',
'uuid': self.node.id},
to_dict)
# States are converted to strings
self.assertIsInstance(to_dict['state'], six.string_types)
@mock.patch.object(_instance.Instance, 'ip_addresses', autospec=True)
def test_to_dict_with_allocation(self, mock_ips):
self.node.provision_state = 'wait call-back'
self.node.to_dict.return_value = {'node': 'dict'}
self.node.instance_info = {'metalsmith_hostname': 'host'}
mock_ips.return_value = {'private': ['1.2.3.4']}
self.instance._allocation = mock.Mock()
self.instance._allocation.to_dict.return_value = {'alloc': 'dict'}
to_dict = self.instance.to_dict()
self.assertEqual({'allocation': {'alloc': 'dict'},
'hostname': 'host',
'ip_addresses': {'private': ['1.2.3.4']},
'node': {'node': 'dict'},
'state': 'deploying',

View File

@ -29,7 +29,8 @@ from metalsmith import sources
NODE_FIELDS = ['name', 'id', 'instance_info', 'instance_id', 'is_maintenance',
'maintenance_reason', 'properties', 'provision_state', 'extra',
'last_error', 'traits', 'resource_class', 'conductor_group']
'last_error', 'traits', 'resource_class', 'conductor_group',
'allocation_id']
class TestInit(testtools.TestCase):
@ -66,7 +67,8 @@ class Base(testtools.TestCase):
id='000', instance_id=None,
properties={'local_gb': 100},
instance_info={},
is_maintenance=False, extra={})
is_maintenance=False, extra={},
allocation_id=None)
self.node.name = 'control-0'
def _reset_api_mock(self):
@ -1386,6 +1388,19 @@ class TestShowInstance(Base):
self.assertIs(inst.node, self.node)
self.assertIs(inst.uuid, self.node.id)
def test_show_instance_with_allocation(self):
self.node.allocation_id = 'id2'
self.mock_get_node.side_effect = lambda n, *a, **kw: self.node
inst = self.pr.show_instance('id1')
self.mock_get_node.assert_called_once_with(self.pr, 'id1',
accept_hostname=True)
self.api.baremetal.get_allocation.assert_called_once_with('id2')
self.assertIsInstance(inst, _instance.Instance)
self.assertIs(inst.allocation,
self.api.baremetal.get_allocation.return_value)
self.assertIs(inst.node, self.node)
self.assertIs(inst.uuid, self.node.id)
def test_show_instances(self):
self.mock_get_node.side_effect = [self.node, self.node]
result = self.pr.show_instances(['1', '2'])
@ -1415,10 +1430,11 @@ class TestListInstances(Base):
super(TestListInstances, self).setUp()
self.nodes = [
mock.Mock(spec=NODE_FIELDS, provision_state=state,
instance_id='1234')
instance_id='1234', allocation_id=None)
for state in ('active', 'active', 'deploying', 'wait call-back',
'deploy failed', 'available', 'available', 'enroll')
]
self.nodes[0].allocation_id = 'id2'
self.nodes[6].instance_id = None
self.api.baremetal.nodes.return_value = self.nodes
@ -1426,5 +1442,8 @@ class TestListInstances(Base):
instances = self.pr.list_instances()
self.assertTrue(isinstance(i, _instance.Instance) for i in instances)
self.assertEqual(self.nodes[:6], [i.node for i in instances])
self.assertEqual([self.api.baremetal.get_allocation.return_value]
+ [None] * 5,
[i.allocation for i in instances])
self.api.baremetal.nodes.assert_called_once_with(associated=True,
details=True)

View File

@ -2,7 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr!=2.1.0,>=2.0.0 # Apache-2.0
openstacksdk>=0.22.0 # Apache-2.0
openstacksdk>=0.25.0 # Apache-2.0
requests>=2.18.4 # Apache-2.0
six>=1.10.0 # MIT
enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD