Add node traits support to baremetal
This patch adds add-trait, remove-trait and set-traits methods to the baremetal module. Change-Id: I9c2dd35c1c5f823d8c482f0177e87d49024242a4
This commit is contained in:
parent
a88328e90e
commit
f162b71cb8
@ -917,3 +917,44 @@ class Proxy(proxy.Proxy):
|
|||||||
"""
|
"""
|
||||||
res = self._get_resource(_allocation.Allocation, allocation)
|
res = self._get_resource(_allocation.Allocation, allocation)
|
||||||
return res.wait(self, timeout=timeout, ignore_error=ignore_error)
|
return res.wait(self, timeout=timeout, ignore_error=ignore_error)
|
||||||
|
|
||||||
|
def add_node_trait(self, node, trait):
|
||||||
|
"""Add a trait to a node.
|
||||||
|
|
||||||
|
:param node: The value can be the name or ID of a node or a
|
||||||
|
:class:`~openstack.baremetal.v1.node.Node` instance.
|
||||||
|
:param trait: trait to remove from the node.
|
||||||
|
:returns: The updated node
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_node.Node, node)
|
||||||
|
return res.add_trait(self, trait)
|
||||||
|
|
||||||
|
def remove_node_trait(self, node, trait, ignore_missing=True):
|
||||||
|
"""Remove a trait from a node.
|
||||||
|
|
||||||
|
:param node: The value can be the name or ID of a node or a
|
||||||
|
:class:`~openstack.baremetal.v1.node.Node` instance.
|
||||||
|
:param trait: trait to remove from the node.
|
||||||
|
:param bool ignore_missing: When set to ``False``, an exception
|
||||||
|
:class:`~openstack.exceptions.ResourceNotFound` will be raised
|
||||||
|
when the trait could not be found. When set to ``True``, no
|
||||||
|
exception will be raised when attempting to delete a non-existent
|
||||||
|
trait.
|
||||||
|
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_node.Node, node)
|
||||||
|
return res.remove_trait(self, trait, ignore_missing=ignore_missing)
|
||||||
|
|
||||||
|
def set_node_traits(self, node, traits):
|
||||||
|
"""Set traits for a node.
|
||||||
|
|
||||||
|
Removes any existing traits and adds the traits passed in to this
|
||||||
|
method.
|
||||||
|
|
||||||
|
:param node: The value can be the name or ID of a node or a
|
||||||
|
:class:`~openstack.baremetal.v1.node.Node` instance.
|
||||||
|
:param traits: list of traits to add to the node.
|
||||||
|
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
|
||||||
|
"""
|
||||||
|
res = self._get_resource(_node.Node, node)
|
||||||
|
return res.set_traits(self, traits)
|
||||||
|
@ -745,5 +745,89 @@ class Node(_common.ListMixin, resource.Resource):
|
|||||||
.format(node=self.id))
|
.format(node=self.id))
|
||||||
exceptions.raise_from_response(response, error_message=msg)
|
exceptions.raise_from_response(response, error_message=msg)
|
||||||
|
|
||||||
|
def add_trait(self, session, trait):
|
||||||
|
"""Add a trait to a node.
|
||||||
|
|
||||||
|
:param session: The session to use for making this request.
|
||||||
|
:param trait: The trait to add to the node.
|
||||||
|
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
|
||||||
|
"""
|
||||||
|
session = self._get_session(session)
|
||||||
|
version = utils.pick_microversion(session, '1.37')
|
||||||
|
request = self._prepare_request(requires_id=True)
|
||||||
|
request.url = utils.urljoin(request.url, 'traits', trait)
|
||||||
|
response = session.put(
|
||||||
|
request.url, json=None,
|
||||||
|
headers=request.headers, microversion=version,
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
msg = ("Failed to add trait {trait} for node {node}"
|
||||||
|
.format(trait=trait, node=self.id))
|
||||||
|
exceptions.raise_from_response(response, error_message=msg)
|
||||||
|
|
||||||
|
self.traits = list(set(self.traits or ()) | {trait})
|
||||||
|
|
||||||
|
def remove_trait(self, session, trait, ignore_missing=True):
|
||||||
|
"""Remove a trait from a node.
|
||||||
|
|
||||||
|
:param session: The session to use for making this request.
|
||||||
|
:param trait: The trait to remove from the node.
|
||||||
|
:param bool ignore_missing: When set to ``False``
|
||||||
|
:class:`~openstack.exceptions.ResourceNotFound` will be
|
||||||
|
raised when the trait does not exist.
|
||||||
|
Otherwise, ``False`` is returned.
|
||||||
|
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
|
||||||
|
"""
|
||||||
|
session = self._get_session(session)
|
||||||
|
version = utils.pick_microversion(session, '1.37')
|
||||||
|
request = self._prepare_request(requires_id=True)
|
||||||
|
request.url = utils.urljoin(request.url, 'traits', trait)
|
||||||
|
|
||||||
|
response = session.delete(
|
||||||
|
request.url, headers=request.headers, microversion=version,
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
if ignore_missing or response.status_code == 400:
|
||||||
|
session.log.debug(
|
||||||
|
'Trait %(trait)s was already removed from node %(node)s',
|
||||||
|
{'trait': trait, 'node': self.id})
|
||||||
|
return False
|
||||||
|
|
||||||
|
msg = ("Failed to remove trait {trait} from bare metal node {node}"
|
||||||
|
.format(node=self.id, trait=trait))
|
||||||
|
exceptions.raise_from_response(response, error_message=msg)
|
||||||
|
|
||||||
|
self.traits = list(set(self.traits) - {trait})
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def set_traits(self, session, traits):
|
||||||
|
"""Set traits for a node.
|
||||||
|
|
||||||
|
Removes any existing traits and adds the traits passed in to this
|
||||||
|
method.
|
||||||
|
|
||||||
|
:param session: The session to use for making this request.
|
||||||
|
:param traits: list of traits to add to the node.
|
||||||
|
:returns: The updated :class:`~openstack.baremetal.v1.node.Node`
|
||||||
|
"""
|
||||||
|
session = self._get_session(session)
|
||||||
|
version = utils.pick_microversion(session, '1.37')
|
||||||
|
request = self._prepare_request(requires_id=True)
|
||||||
|
request.url = utils.urljoin(request.url, 'traits')
|
||||||
|
|
||||||
|
body = {'traits': traits}
|
||||||
|
|
||||||
|
response = session.put(
|
||||||
|
request.url, json=body,
|
||||||
|
headers=request.headers, microversion=version,
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
msg = ("Failed to set traits for node {node}"
|
||||||
|
.format(node=self.id))
|
||||||
|
exceptions.raise_from_response(response, error_message=msg)
|
||||||
|
|
||||||
|
self.traits = traits
|
||||||
|
|
||||||
|
|
||||||
NodeDetail = Node
|
NodeDetail = Node
|
||||||
|
@ -303,3 +303,49 @@ class TestBareMetalVif(base.BaseBaremetalTest):
|
|||||||
self.assertRaises(exceptions.ResourceNotFound,
|
self.assertRaises(exceptions.ResourceNotFound,
|
||||||
self.conn.baremetal.detach_vif_from_node,
|
self.conn.baremetal.detach_vif_from_node,
|
||||||
uuid, self.vif_id, ignore_missing=False)
|
uuid, self.vif_id, ignore_missing=False)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTraits(base.BaseBaremetalTest):
|
||||||
|
|
||||||
|
min_microversion = '1.37'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestTraits, self).setUp()
|
||||||
|
self.node = self.create_node()
|
||||||
|
|
||||||
|
def test_add_remove_node_trait(self):
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual([], node.traits)
|
||||||
|
|
||||||
|
self.conn.baremetal.add_node_trait(self.node, 'CUSTOM_FAKE')
|
||||||
|
self.assertEqual(['CUSTOM_FAKE'], self.node.traits)
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual(['CUSTOM_FAKE'], node.traits)
|
||||||
|
|
||||||
|
self.conn.baremetal.add_node_trait(self.node, 'CUSTOM_REAL')
|
||||||
|
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], self.node.traits)
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], node.traits)
|
||||||
|
|
||||||
|
self.conn.baremetal.remove_node_trait(node, 'CUSTOM_FAKE',
|
||||||
|
ignore_missing=False)
|
||||||
|
self.assertEqual(['CUSTOM_REAL'], self.node.traits)
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual(['CUSTOM_REAL'], node.traits)
|
||||||
|
|
||||||
|
def test_set_node_traits(self):
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual([], node.traits)
|
||||||
|
|
||||||
|
traits1 = ['CUSTOM_FAKE', 'CUSTOM_REAL']
|
||||||
|
traits2 = ['CUSTOM_FOOBAR']
|
||||||
|
|
||||||
|
self.conn.baremetal.set_node_traits(self.node, traits1)
|
||||||
|
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], self.node.traits)
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual(['CUSTOM_FAKE', 'CUSTOM_REAL'], node.traits)
|
||||||
|
|
||||||
|
self.conn.baremetal.set_node_traits(self.node, traits2)
|
||||||
|
self.assertEqual(['CUSTOM_FOOBAR'], self.node.traits)
|
||||||
|
node = self.conn.baremetal.get_node(self.node)
|
||||||
|
self.assertEqual(['CUSTOM_FOOBAR'], node.traits)
|
||||||
|
@ -702,3 +702,48 @@ class TestNodeSetBootDevice(base.TestCase):
|
|||||||
json={'boot_device': 'pxe', 'persistent': False},
|
json={'boot_device': 'pxe', 'persistent': False},
|
||||||
headers=mock.ANY, microversion=mock.ANY,
|
headers=mock.ANY, microversion=mock.ANY,
|
||||||
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(node.Node, 'fetch', lambda self, session: self)
|
||||||
|
@mock.patch.object(exceptions, 'raise_from_response', mock.Mock())
|
||||||
|
class TestNodeTraits(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestNodeTraits, self).setUp()
|
||||||
|
self.node = node.Node(**FAKE)
|
||||||
|
self.session = mock.Mock(spec=adapter.Adapter,
|
||||||
|
default_microversion='1.37')
|
||||||
|
self.session.log = mock.Mock()
|
||||||
|
|
||||||
|
def test_node_add_trait(self):
|
||||||
|
self.node.add_trait(self.session, 'CUSTOM_FAKE')
|
||||||
|
self.session.put.assert_called_once_with(
|
||||||
|
'nodes/%s/traits/%s' % (self.node.id, 'CUSTOM_FAKE'),
|
||||||
|
json=None,
|
||||||
|
headers=mock.ANY, microversion='1.37',
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
def test_remove_trait(self):
|
||||||
|
self.node.remove_trait(self.session, 'CUSTOM_FAKE')
|
||||||
|
self.session.delete.assert_called_once_with(
|
||||||
|
'nodes/%s/traits/%s' % (self.node.id, 'CUSTOM_FAKE'),
|
||||||
|
headers=mock.ANY, microversion='1.37',
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
def test_remove_trait_missing(self):
|
||||||
|
self.session.delete.return_value.status_code = 400
|
||||||
|
self.assertFalse(self.node.remove_trait(self.session,
|
||||||
|
'CUSTOM_MISSING'))
|
||||||
|
self.session.delete.assert_called_once_with(
|
||||||
|
'nodes/%s/traits/%s' % (self.node.id, 'CUSTOM_MISSING'),
|
||||||
|
headers=mock.ANY, microversion='1.37',
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
|
||||||
|
def test_set_traits(self):
|
||||||
|
traits = ['CUSTOM_FAKE', 'CUSTOM_REAL', 'CUSTOM_MISSING']
|
||||||
|
self.node.set_traits(self.session, traits)
|
||||||
|
self.session.put.assert_called_once_with(
|
||||||
|
'nodes/%s/traits' % self.node.id,
|
||||||
|
json={'traits': ['CUSTOM_FAKE', 'CUSTOM_REAL', 'CUSTOM_MISSING']},
|
||||||
|
headers=mock.ANY, microversion='1.37',
|
||||||
|
retriable_status_codes=_common.RETRIABLE_STATUS_CODES)
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Implements add/remove/set traits API for bare metal nodes.
|
Loading…
x
Reference in New Issue
Block a user