Return Instance object from provisioning API
Change-Id: I2676ee281bfebb12abbb5702bcc6639526fe3189
This commit is contained in:
parent
c7dddc1f28
commit
804f0a7a3a
@ -13,6 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from metalsmith._provisioner import Instance
|
||||
from metalsmith._provisioner import Provisioner
|
||||
|
||||
__all__ = ['Provisioner']
|
||||
__all__ = ['Instance', 'Provisioner']
|
||||
|
@ -23,7 +23,7 @@ import six
|
||||
LOG = logging.getLogger(__name__)
|
||||
REMOVE = object()
|
||||
NODE_FIELDS = ['name', 'uuid', 'instance_uuid', 'maintenance',
|
||||
'maintenance_reason', 'properties', 'extra']
|
||||
'maintenance_reason', 'properties', 'provision_state', 'extra']
|
||||
|
||||
|
||||
class DictWithAttrs(dict):
|
||||
@ -87,6 +87,9 @@ class API(object):
|
||||
def get_node(self, node):
|
||||
if isinstance(node, six.string_types):
|
||||
return self.ironic.node.get(node, fields=NODE_FIELDS)
|
||||
elif hasattr(node, 'node'):
|
||||
# Instance object
|
||||
return node.node
|
||||
else:
|
||||
return node
|
||||
|
||||
|
@ -30,6 +30,72 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
_CREATED_PORTS = 'metalsmith_created_ports'
|
||||
_ATTACHED_PORTS = 'metalsmith_attached_ports'
|
||||
# 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'])
|
||||
_ACTIVE_STATES = frozenset(['active'])
|
||||
_ERROR_STATE = frozenset(['error', 'deploy failed'])
|
||||
|
||||
_HEALTHY_STATES = frozenset(['deploying', 'active'])
|
||||
|
||||
|
||||
class Instance(object):
|
||||
"""Instance status in metalsmith."""
|
||||
|
||||
def __init__(self, api, node):
|
||||
self._api = api
|
||||
self._uuid = node.uuid
|
||||
self._node = node
|
||||
|
||||
@property
|
||||
def is_deployed(self):
|
||||
"""Whether the node is deployed."""
|
||||
return self._node.provision_state in _ACTIVE_STATES
|
||||
|
||||
@property
|
||||
def is_healthy(self):
|
||||
"""Whether the node is not at fault or maintenance."""
|
||||
return self.state in _HEALTHY_STATES and not self._node.maintenance
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
"""Underlying `Node` object."""
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Instance state.
|
||||
|
||||
``deploying``
|
||||
deployment is in progress
|
||||
``active``
|
||||
node is provisioned
|
||||
``maintenance``
|
||||
node is provisioned but is in maintenance mode
|
||||
``error``
|
||||
node has a failure
|
||||
``unknown``
|
||||
node in unexpected state (maybe unprovisioned or modified by
|
||||
a third party)
|
||||
"""
|
||||
prov_state = self._node.provision_state
|
||||
if prov_state in _DEPLOYING_STATES:
|
||||
return 'deploying'
|
||||
elif prov_state in _ERROR_STATE:
|
||||
return 'error'
|
||||
elif prov_state in _ACTIVE_STATES:
|
||||
if self._node.maintenance:
|
||||
return 'maintenance'
|
||||
else:
|
||||
return 'active'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
@property
|
||||
def uuid(self):
|
||||
"""Instance UUID (the same as `Node` UUID for metalsmith)."""
|
||||
return self._uuid
|
||||
|
||||
|
||||
class Provisioner(object):
|
||||
@ -135,7 +201,9 @@ class Provisioner(object):
|
||||
:param netboot: Whether to use networking boot for final instances.
|
||||
:param wait: How many seconds to wait for the deployment to finish,
|
||||
None to return immediately.
|
||||
:return: provisioned `Node` object.
|
||||
:return: :py:class:`metalsmith.Instance` object with the current
|
||||
status of provisioning. If ``wait`` is not ``None``, provisioning
|
||||
is already finished.
|
||||
:raises: :py:class:`metalsmith.exceptions.Error`
|
||||
"""
|
||||
node = self._check_node_for_deploy(node)
|
||||
@ -208,7 +276,7 @@ class Provisioner(object):
|
||||
LOG.info('Deploy succeeded on node %s', _utils.log_node(node))
|
||||
self._log_ips(node, created_ports)
|
||||
|
||||
return node
|
||||
return Instance(self._api, node)
|
||||
|
||||
def _log_ips(self, node, created_ports):
|
||||
ips = []
|
||||
@ -313,7 +381,8 @@ class Provisioner(object):
|
||||
def unprovision_node(self, node, wait=None):
|
||||
"""Unprovision a previously provisioned node.
|
||||
|
||||
:param node: node object, UUID or name.
|
||||
:param node: `Node` object, :py:class:`metalsmith.Instance`,
|
||||
UUID or name.
|
||||
:param wait: How many seconds to wait for the process to finish,
|
||||
None to return immediately.
|
||||
:return: nothing.
|
||||
|
@ -13,10 +13,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from metalsmith import _os_api
|
||||
from metalsmith import _provisioner
|
||||
|
||||
|
||||
class TestInit(testtools.TestCase):
|
||||
@ -38,3 +40,31 @@ class TestInit(testtools.TestCase):
|
||||
api = _os_api.API(cloud_region=region)
|
||||
self.assertIs(api.session, region.get_session.return_value)
|
||||
mock_conn.assert_called_once_with(config=region)
|
||||
|
||||
|
||||
class TestNodes(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(TestNodes, self).setUp()
|
||||
self.session = mock.Mock()
|
||||
self.ironic_fixture = self.useFixture(
|
||||
fixtures.MockPatchObject(_os_api.ir_client, 'get_client',
|
||||
autospec=True))
|
||||
self.cli = self.ironic_fixture.mock.return_value
|
||||
self.api = _os_api.API(session=self.session)
|
||||
|
||||
def test_get_node_by_uuid(self):
|
||||
res = self.api.get_node('uuid1')
|
||||
self.cli.node.get.assert_called_once_with('uuid1',
|
||||
fields=_os_api.NODE_FIELDS)
|
||||
self.assertIs(res, self.cli.node.get.return_value)
|
||||
|
||||
def test_get_node_by_node(self):
|
||||
res = self.api.get_node(mock.sentinel.node)
|
||||
self.assertIs(res, mock.sentinel.node)
|
||||
self.assertFalse(self.cli.node.get.called)
|
||||
|
||||
def test_get_node_by_instance(self):
|
||||
inst = _provisioner.Instance(mock.Mock(), mock.Mock())
|
||||
res = self.api.get_node(inst)
|
||||
self.assertIs(res, inst.node)
|
||||
self.assertFalse(self.cli.node.get.called)
|
||||
|
@ -108,7 +108,11 @@ class TestProvisionNode(Base):
|
||||
}
|
||||
|
||||
def test_ok(self):
|
||||
self.pr.provision_node(self.node, 'image', [{'network': 'network'}])
|
||||
inst = self.pr.provision_node(self.node, 'image',
|
||||
[{'network': 'network'}])
|
||||
|
||||
self.assertEqual(inst.uuid, self.node.uuid)
|
||||
self.assertEqual(inst.node, self.node)
|
||||
|
||||
self.api.create_port.assert_called_once_with(
|
||||
network_id=self.api.get_network.return_value.id)
|
||||
@ -529,3 +533,53 @@ class TestUnprovisionNode(Base):
|
||||
self.assertFalse(self.api.detach_port_from_node.called)
|
||||
self.assertFalse(self.api.wait_for_node_state.called)
|
||||
self.assertFalse(self.api.update_node.called)
|
||||
|
||||
|
||||
class TestInstance(Base):
|
||||
def setUp(self):
|
||||
super(TestInstance, self).setUp()
|
||||
self.instance = _provisioner.Instance(self.api, self.node)
|
||||
|
||||
def test_state_deploying(self):
|
||||
self.node.provision_state = 'wait call-back'
|
||||
self.assertEqual('deploying', self.instance.state)
|
||||
self.assertFalse(self.instance.is_deployed)
|
||||
self.assertTrue(self.instance.is_healthy)
|
||||
|
||||
def test_state_deploying_when_available(self):
|
||||
self.node.provision_state = 'available'
|
||||
self.assertEqual('deploying', self.instance.state)
|
||||
self.assertFalse(self.instance.is_deployed)
|
||||
self.assertTrue(self.instance.is_healthy)
|
||||
|
||||
def test_state_deploying_maintenance(self):
|
||||
self.node.maintenance = True
|
||||
self.node.provision_state = 'wait call-back'
|
||||
self.assertEqual('deploying', self.instance.state)
|
||||
self.assertFalse(self.instance.is_deployed)
|
||||
self.assertFalse(self.instance.is_healthy)
|
||||
|
||||
def test_state_active(self):
|
||||
self.node.provision_state = 'active'
|
||||
self.assertEqual('active', self.instance.state)
|
||||
self.assertTrue(self.instance.is_deployed)
|
||||
self.assertTrue(self.instance.is_healthy)
|
||||
|
||||
def test_state_maintenance(self):
|
||||
self.node.maintenance = True
|
||||
self.node.provision_state = 'active'
|
||||
self.assertEqual('maintenance', self.instance.state)
|
||||
self.assertTrue(self.instance.is_deployed)
|
||||
self.assertFalse(self.instance.is_healthy)
|
||||
|
||||
def test_state_error(self):
|
||||
self.node.provision_state = 'deploy failed'
|
||||
self.assertEqual('error', self.instance.state)
|
||||
self.assertFalse(self.instance.is_deployed)
|
||||
self.assertFalse(self.instance.is_healthy)
|
||||
|
||||
def test_state_unknown(self):
|
||||
self.node.provision_state = 'enroll'
|
||||
self.assertEqual('unknown', self.instance.state)
|
||||
self.assertFalse(self.instance.is_deployed)
|
||||
self.assertFalse(self.instance.is_healthy)
|
||||
|
@ -4,6 +4,7 @@
|
||||
coverage!=4.4,>=4.0 # Apache-2.0
|
||||
doc8>=0.6.0 # Apache-2.0
|
||||
flake8-import-order>=0.13 # LGPLv3
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
hacking>=1.0.0 # Apache-2.0
|
||||
mock>=2.0 # BSD
|
||||
testtools>=2.2.0 # MIT
|
||||
|
Loading…
x
Reference in New Issue
Block a user