summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJim Rollenhagen <jim@jimrollenhagen.com>2018-01-10 14:55:59 -0500
committerJulia Kreger <juliaashleykreger@gmail.com>2018-01-25 16:13:25 +0000
commitbc2c3a23677df42ae514786731ac2a496a3ef0c6 (patch)
tree1fea78ffbafd4be6270f0e63bc3074c614dbe90e
parent997e11177f251769259a7b77343b224cce31b95f (diff)
downloadpython-ironicclient-bc2c3a23677df42ae514786731ac2a496a3ef0c6.tar.gz
Traits support
This adds support for the traits APIs in both the node portion of the SDK, and the openstackclient plugin. We also bump the last known API version to 1.37 to get access to the new API. Change-Id: I72017d51dea194ec062a66cb19d718ba827e7427 Partial-Bug: #1722194 Depends-On: I313fa01fbf20bf0ff19f102ea63b02e72ac2b856
-rw-r--r--ironicclient/common/base.py17
-rw-r--r--ironicclient/common/http.py2
-rwxr-xr-xironicclient/osc/v1/baremetal_node.py113
-rw-r--r--ironicclient/tests/unit/osc/v1/fakes.py1
-rw-r--r--ironicclient/tests/unit/osc/v1/test_baremetal_node.py186
-rw-r--r--ironicclient/tests/unit/v1/test_node.py73
-rw-r--r--ironicclient/tests/unit/v1/test_node_shell.py1
-rw-r--r--ironicclient/v1/node.py47
-rw-r--r--ironicclient/v1/resource_fields.py7
-rw-r--r--releasenotes/notes/traits-support-8864f6816abecdb2.yaml20
-rw-r--r--setup.cfg3
11 files changed, 464 insertions, 6 deletions
diff --git a/ironicclient/common/base.py b/ironicclient/common/base.py
index 294f06a..563ceb0 100644
--- a/ironicclient/common/base.py
+++ b/ironicclient/common/base.py
@@ -170,25 +170,34 @@ class Manager(object):
return object_list
- def _list(self, url, response_key=None, obj_class=None, body=None):
+ def __list(self, url, response_key=None, body=None):
resp, body = self.api.json_request('GET', url)
+ data = self._format_body_data(body, response_key)
+ return data
+ def _list(self, url, response_key=None, obj_class=None, body=None):
if obj_class is None:
obj_class = self.resource_class
- data = self._format_body_data(body, response_key)
+ data = self.__list(url, response_key=response_key, body=body)
return [obj_class(self, res, loaded=True) for res in data if res]
+ def _list_primitives(self, url, response_key=None):
+ return self.__list(url, response_key=response_key)
+
def _update(self, resource_id, patch, method='PATCH'):
"""Update a resource.
:param resource_id: Resource identifier.
- :param patch: New version of a given resource.
+ :param patch: New version of a given resource, a dictionary or None.
:param method: Name of the method for the request.
"""
url = self._path(resource_id)
- resp, body = self.api.json_request(method, url, body=patch)
+ kwargs = {}
+ if patch is not None:
+ kwargs['body'] = patch
+ resp, body = self.api.json_request(method, url, **kwargs)
# PATCH/PUT requests may not return a body
if body:
return self.resource_class(self, body)
diff --git a/ironicclient/common/http.py b/ironicclient/common/http.py
index e56bef0..35d1ccb 100644
--- a/ironicclient/common/http.py
+++ b/ironicclient/common/http.py
@@ -44,7 +44,7 @@ from ironicclient import exc
# http://specs.openstack.org/openstack/ironic-specs/specs/kilo/api-microversions.html # noqa
# for full details.
DEFAULT_VER = '1.9'
-LAST_KNOWN_API_VERSION = 35
+LAST_KNOWN_API_VERSION = 37
LATEST_VERSION = '1.{}'.format(LAST_KNOWN_API_VERSION)
LOG = logging.getLogger(__name__)
diff --git a/ironicclient/osc/v1/baremetal_node.py b/ironicclient/osc/v1/baremetal_node.py
index 990905f..350ba52 100755
--- a/ironicclient/osc/v1/baremetal_node.py
+++ b/ironicclient/osc/v1/baremetal_node.py
@@ -1574,3 +1574,116 @@ class InjectNmiBaremetalNode(command.Command):
baremetal_client = self.app.client_manager.baremetal
baremetal_client.node.inject_nmi(parsed_args.node)
+
+
+class ListTraitsBaremetalNode(command.Lister):
+ """List a node's traits."""
+
+ log = logging.getLogger(__name__ + ".ListTraitsBaremetalNode")
+
+ def get_parser(self, prog_name):
+ parser = super(ListTraitsBaremetalNode, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'node',
+ metavar='<node>',
+ help=_("Name or UUID of the node"))
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ labels = res_fields.TRAIT_RESOURCE.labels
+
+ baremetal_client = self.app.client_manager.baremetal
+ traits = baremetal_client.node.get_traits(parsed_args.node)
+
+ return (labels, [[trait] for trait in traits])
+
+
+class AddTraitBaremetalNode(command.Command):
+ """Add traits to a node."""
+
+ log = logging.getLogger(__name__ + ".AddTraitBaremetalNode")
+
+ def get_parser(self, prog_name):
+ parser = super(AddTraitBaremetalNode, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'node',
+ metavar='<node>',
+ help=_("Name or UUID of the node"))
+ parser.add_argument(
+ 'traits',
+ nargs='+',
+ metavar='<trait>',
+ help=_("Trait(s) to add"))
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ baremetal_client = self.app.client_manager.baremetal
+
+ failures = []
+ for trait in parsed_args.traits:
+ try:
+ baremetal_client.node.add_trait(parsed_args.node, trait)
+ print(_('Added trait %s') % trait)
+ except exc.ClientException as e:
+ failures.append(_("Failed to add trait %(trait)s: %(error)s")
+ % {'trait': trait, 'error': e})
+
+ if failures:
+ raise exc.ClientException("\n".join(failures))
+
+
+class RemoveTraitBaremetalNode(command.Command):
+ """Remove trait(s) from a node."""
+
+ log = logging.getLogger(__name__ + ".RemoveTraitBaremetalNode")
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveTraitBaremetalNode, self).get_parser(prog_name)
+
+ parser.add_argument(
+ 'node',
+ metavar='<node>',
+ help=_("Name or UUID of the node"))
+ all_or_trait = parser.add_mutually_exclusive_group(required=True)
+ all_or_trait.add_argument(
+ '--all',
+ dest='remove_all',
+ action='store_true',
+ help=_("Remove all traits"))
+ all_or_trait.add_argument(
+ 'traits',
+ metavar='<trait>',
+ nargs='*',
+ default=[],
+ help=_("Trait(s) to remove"))
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.log.debug("take_action(%s)", parsed_args)
+
+ baremetal_client = self.app.client_manager.baremetal
+
+ failures = []
+ if parsed_args.remove_all:
+ baremetal_client.node.remove_all_traits(parsed_args.node)
+ else:
+ for trait in parsed_args.traits:
+ try:
+ baremetal_client.node.remove_trait(parsed_args.node, trait)
+ print(_('Removed trait %s') % trait)
+ except exc.ClientException as e:
+ failures.append(_("Failed to remove trait %(trait)s: "
+ "%(error)s")
+ % {'trait': trait, 'error': e})
+
+ if failures:
+ raise exc.ClientException("\n".join(failures))
diff --git a/ironicclient/tests/unit/osc/v1/fakes.py b/ironicclient/tests/unit/osc/v1/fakes.py
index faa479a..37e04e1 100644
--- a/ironicclient/tests/unit/osc/v1/fakes.py
+++ b/ironicclient/tests/unit/osc/v1/fakes.py
@@ -137,6 +137,7 @@ PORTGROUP = {'uuid': baremetal_portgroup_uuid,
}
VIFS = {'vifs': [{'id': 'aaa-aa'}]}
+TRAITS = ['CUSTOM_FOO', 'CUSTOM_BAR']
baremetal_volume_connector_uuid = 'vvv-cccccc-vvvv'
baremetal_volume_connector_type = 'iqn'
diff --git a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
index ef62756..ca74b0b 100644
--- a/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
+++ b/ironicclient/tests/unit/osc/v1/test_baremetal_node.py
@@ -591,7 +591,7 @@ class TestBaremetalList(TestBaremetal):
'Current RAID configuration', 'Reservation',
'Resource Class',
'Target Power State', 'Target Provision State',
- 'Target RAID configuration',
+ 'Target RAID configuration', 'Traits',
'Updated At', 'Inspection Finished At',
'Inspection Started At', 'UUID', 'Name',
'Boot Interface', 'Console Interface',
@@ -627,6 +627,7 @@ class TestBaremetalList(TestBaremetal):
'',
'',
'',
+ '',
baremetal_fakes.baremetal_uuid,
baremetal_fakes.baremetal_name,
'',
@@ -2663,3 +2664,186 @@ class TestBaremetalInject(TestBaremetal):
self.baremetal_mock.node.inject_nmi.assert_called_once_with(
'node_uuid')
+
+
+class TestListTraits(TestBaremetal):
+ def setUp(self):
+ super(TestListTraits, self).setUp()
+
+ self.baremetal_mock.node.get_traits.return_value = (
+ baremetal_fakes.TRAITS)
+
+ # Get the command object to test
+ self.cmd = baremetal_node.ListTraitsBaremetalNode(self.app, None)
+
+ def test_baremetal_list_traits(self):
+ arglist = ['node_uuid']
+ verifylist = [('node', 'node_uuid')]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.baremetal_mock.node.get_traits.assert_called_once_with(
+ 'node_uuid')
+
+
+class TestAddTrait(TestBaremetal):
+ def setUp(self):
+ super(TestAddTrait, self).setUp()
+
+ # Get the command object to test
+ self.cmd = baremetal_node.AddTraitBaremetalNode(self.app, None)
+
+ def test_baremetal_add_trait(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO']
+ verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO'])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.baremetal_mock.node.add_trait.assert_called_once_with(
+ 'node_uuid', 'CUSTOM_FOO')
+
+ def test_baremetal_add_traits_multiple(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR']
+ verifylist = [('node', 'node_uuid'),
+ ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ expected_calls = [
+ mock.call('node_uuid', 'CUSTOM_FOO'),
+ mock.call('node_uuid', 'CUSTOM_BAR'),
+ ]
+ self.assertEqual(expected_calls,
+ self.baremetal_mock.node.add_trait.call_args_list)
+
+ def test_baremetal_add_traits_multiple_with_failure(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR']
+ verifylist = [('node', 'node_uuid'),
+ ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])]
+
+ self.baremetal_mock.node.add_trait.side_effect = [
+ '', exc.ClientException]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exc.ClientException,
+ self.cmd.take_action,
+ parsed_args)
+
+ expected_calls = [
+ mock.call('node_uuid', 'CUSTOM_FOO'),
+ mock.call('node_uuid', 'CUSTOM_BAR'),
+ ]
+ self.assertEqual(expected_calls,
+ self.baremetal_mock.node.add_trait.call_args_list)
+
+ def test_baremetal_add_traits_no_traits(self):
+ arglist = ['node_uuid']
+ verifylist = [('node', 'node_uuid')]
+
+ self.assertRaises(oscutils.ParserException,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ verifylist)
+
+
+class TestRemoveTrait(TestBaremetal):
+ def setUp(self):
+ super(TestRemoveTrait, self).setUp()
+
+ # Get the command object to test
+ self.cmd = baremetal_node.RemoveTraitBaremetalNode(self.app, None)
+
+ def test_baremetal_remove_trait(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO']
+ verifylist = [('node', 'node_uuid'), ('traits', ['CUSTOM_FOO'])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.baremetal_mock.node.remove_trait.assert_called_once_with(
+ 'node_uuid', 'CUSTOM_FOO')
+
+ def test_baremetal_remove_trait_multiple(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR']
+ verifylist = [('node', 'node_uuid'),
+ ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ expected_calls = [
+ mock.call('node_uuid', 'CUSTOM_FOO'),
+ mock.call('node_uuid', 'CUSTOM_BAR'),
+ ]
+ self.assertEqual(expected_calls,
+ self.baremetal_mock.node.remove_trait.call_args_list)
+
+ def test_baremetal_remove_trait_multiple_with_failure(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO', 'CUSTOM_BAR']
+ verifylist = [('node', 'node_uuid'),
+ ('traits', ['CUSTOM_FOO', 'CUSTOM_BAR'])]
+
+ self.baremetal_mock.node.remove_trait.side_effect = [
+ '', exc.ClientException]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.assertRaises(exc.ClientException,
+ self.cmd.take_action,
+ parsed_args)
+
+ expected_calls = [
+ mock.call('node_uuid', 'CUSTOM_FOO'),
+ mock.call('node_uuid', 'CUSTOM_BAR'),
+ ]
+ self.assertEqual(expected_calls,
+ self.baremetal_mock.node.remove_trait.call_args_list)
+
+ def test_baremetal_remove_trait_all(self):
+ arglist = ['node_uuid', '--all']
+ verifylist = [('node', 'node_uuid'), ('remove_all', True)]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.cmd.take_action(parsed_args)
+
+ self.baremetal_mock.node.remove_all_traits.assert_called_once_with(
+ 'node_uuid')
+
+ def test_baremetal_remove_trait_traits_and_all(self):
+ arglist = ['node_uuid', 'CUSTOM_FOO', '--all']
+ verifylist = [('node', 'node_uuid'),
+ ('traits', ['CUSTOM_FOO']),
+ ('remove_all', True)]
+
+ self.assertRaises(oscutils.ParserException,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ verifylist)
+
+ self.baremetal_mock.node.remove_all_traits.assert_not_called()
+ self.baremetal_mock.node.remove_trait.assert_not_called()
+
+ def test_baremetal_remove_traits_no_traits_no_all(self):
+ arglist = ['node_uuid']
+ verifylist = [('node', 'node_uuid')]
+
+ self.assertRaises(oscutils.ParserException,
+ self.check_parser,
+ self.cmd,
+ arglist,
+ verifylist)
+
+ self.baremetal_mock.node.remove_all_traits.assert_not_called()
+ self.baremetal_mock.node.remove_trait.assert_not_called()
diff --git a/ironicclient/tests/unit/v1/test_node.py b/ironicclient/tests/unit/v1/test_node.py
index a60ad31..7fde845 100644
--- a/ironicclient/tests/unit/v1/test_node.py
+++ b/ironicclient/tests/unit/v1/test_node.py
@@ -103,6 +103,7 @@ NODE_VENDOR_PASSTHRU_METHOD = {"heartbeat": {"attach": "false",
"async": "true"}}
VIFS = {'vifs': [{'id': 'aaa-aaa'}]}
+TRAITS = {'traits': ['CUSTOM_FOO', 'CUSTOM_BAR']}
CREATE_NODE = copy.deepcopy(NODE1)
del CREATE_NODE['uuid']
@@ -448,6 +449,32 @@ fake_responses = {
{},
VIFS,
),
+ },
+ '/v1/nodes/%s/traits' % NODE1['uuid']:
+ {
+ 'GET': (
+ {},
+ TRAITS,
+ ),
+ 'PUT': (
+ {},
+ None,
+ ),
+ 'DELETE': (
+ {},
+ None,
+ ),
+ },
+ '/v1/nodes/%s/traits/CUSTOM_FOO' % NODE1['uuid']:
+ {
+ 'PUT': (
+ {},
+ None,
+ ),
+ 'DELETE': (
+ {},
+ None,
+ ),
}
}
@@ -1641,3 +1668,49 @@ class NodeManagerTest(testtools.TestCase):
self.assertEqual(4, mock_get.call_count)
mock_sleep.assert_called_with(node._DEFAULT_POLL_INTERVAL)
self.assertEqual(3, mock_sleep.call_count)
+
+ def test_node_get_traits(self):
+ traits = self.mgr.get_traits(NODE1['uuid'])
+ expect = [
+ ('GET', '/v1/nodes/%s/traits' % NODE1['uuid'], {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertEqual(TRAITS['traits'], traits)
+
+ def test_node_add_trait(self):
+ trait = 'CUSTOM_FOO'
+ resp = self.mgr.add_trait(NODE1['uuid'], trait)
+ expect = [
+ ('PUT', '/v1/nodes/%s/traits/%s' % (NODE1['uuid'], trait),
+ {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertIsNone(resp)
+
+ def test_node_set_traits(self):
+ traits = ['CUSTOM_FOO', 'CUSTOM_BAR']
+ resp = self.mgr.set_traits(NODE1['uuid'], traits)
+ expect = [
+ ('PUT', '/v1/nodes/%s/traits' % NODE1['uuid'],
+ {}, {'traits': traits}),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertIsNone(resp)
+
+ def test_node_remove_all_traits(self):
+ resp = self.mgr.remove_all_traits(NODE1['uuid'])
+ expect = [
+ ('DELETE', '/v1/nodes/%s/traits' % NODE1['uuid'], {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertIsNone(resp)
+
+ def test_node_remove_trait(self):
+ trait = 'CUSTOM_FOO'
+ resp = self.mgr.remove_trait(NODE1['uuid'], trait)
+ expect = [
+ ('DELETE', '/v1/nodes/%s/traits/%s' % (NODE1['uuid'], trait),
+ {}, None),
+ ]
+ self.assertEqual(expect, self.api.calls)
+ self.assertIsNone(resp)
diff --git a/ironicclient/tests/unit/v1/test_node_shell.py b/ironicclient/tests/unit/v1/test_node_shell.py
index b30eb20..f861572 100644
--- a/ironicclient/tests/unit/v1/test_node_shell.py
+++ b/ironicclient/tests/unit/v1/test_node_shell.py
@@ -65,6 +65,7 @@ class NodeShellTest(utils.BaseTestCase):
'resource_class',
'target_power_state',
'target_provision_state',
+ 'traits',
'updated_at',
'inspection_finished_at',
'inspection_started_at',
diff --git a/ironicclient/v1/node.py b/ironicclient/v1/node.py
index 135a67a..b8571ab 100644
--- a/ironicclient/v1/node.py
+++ b/ironicclient/v1/node.py
@@ -553,6 +553,53 @@ class NodeManager(base.CreateManager):
path = "%s/vendor_passthru/methods" % node_ident
return self._get_as_dict(path)
+ def get_traits(self, node_ident):
+ """Get traits for a node.
+
+ :param node_ident: node UUID or name.
+ """
+ path = "%s/traits" % node_ident
+ return self._list_primitives(self._path(path), 'traits')
+
+ def add_trait(self, node_ident, trait):
+ """Add a trait to a node.
+
+ :param node_ident: node UUID or name.
+ :param trait: trait to add to the node.
+ """
+ path = "%s/traits/%s" % (node_ident, trait)
+ return self.update(path, None, http_method='PUT')
+
+ def set_traits(self, node_ident, traits):
+ """Set traits for a node.
+
+ Removes any existing traits and adds the traits passed in to this
+ method.
+
+ :param node_ident: node UUID or name.
+ :param traits: list of traits to add to the node.
+ """
+ path = "%s/traits" % node_ident
+ body = {'traits': traits}
+ return self.update(path, body, http_method='PUT')
+
+ def remove_trait(self, node_ident, trait):
+ """Remove a trait from a node.
+
+ :param node_ident: node UUID or name.
+ :param trait: trait to remove from the node.
+ """
+ path = "%s/traits/%s" % (node_ident, trait)
+ return self.delete(path)
+
+ def remove_all_traits(self, node_ident):
+ """Remove all traits from a node.
+
+ :param node_ident: node UUID or name.
+ """
+ path = "%s/traits" % node_ident
+ return self.delete(path)
+
def wait_for_provision_state(self, node_ident, expected_state,
timeout=0,
poll_interval=_DEFAULT_POLL_INTERVAL,
diff --git a/ironicclient/v1/resource_fields.py b/ironicclient/v1/resource_fields.py
index 8ff5ad8..d027135 100644
--- a/ironicclient/v1/resource_fields.py
+++ b/ironicclient/v1/resource_fields.py
@@ -87,6 +87,7 @@ class Resource(object):
'target_power_state': 'Target Power State',
'target_provision_state': 'Target Provision State',
'target_raid_config': 'Target RAID configuration',
+ 'traits': 'Traits',
'type': 'Type',
'updated_at': 'Updated At',
'uuid': 'UUID',
@@ -210,6 +211,7 @@ NODE_DETAILED_RESOURCE = Resource(
'target_power_state',
'target_provision_state',
'target_raid_config',
+ 'traits',
'updated_at',
'inspection_finished_at',
'inspection_started_at',
@@ -239,6 +241,7 @@ NODE_DETAILED_RESOURCE = Resource(
'properties',
'raid_config',
'target_raid_config',
+ 'traits',
])
NODE_RESOURCE = Resource(
['uuid',
@@ -319,6 +322,10 @@ VIF_RESOURCE = Resource(
['id'],
)
+TRAIT_RESOURCE = Resource(
+ ['traits'],
+)
+
# Drivers
DRIVER_DETAILED_RESOURCE = Resource(
['name',
diff --git a/releasenotes/notes/traits-support-8864f6816abecdb2.yaml b/releasenotes/notes/traits-support-8864f6816abecdb2.yaml
new file mode 100644
index 0000000..d4dc5f3
--- /dev/null
+++ b/releasenotes/notes/traits-support-8864f6816abecdb2.yaml
@@ -0,0 +1,20 @@
+---
+features:
+ - |
+ Adds support for reading and modifying traits for a node, including adding
+ traits to the detailed output of a node. This is available starting
+ with Bare Metal API version 1.37.
+
+ The new commands are:
+
+ * ``openstack baremetal node trait list <node>``
+ * ``openstack baremetal node add trait <node> <trait> [...]``
+ * ``openstack baremetal node remove trait <node> [<trait> [...]] [--all]``
+
+ It also adds the following methods to the Python SDK:
+
+ * ``NodeManager.get_traits``
+ * ``NodeManager.add_trait``
+ * ``NodeManager.set_traits``
+ * ``NodeManager.remove_trait``
+ * ``NodeManager.remove_all_traits``
diff --git a/setup.cfg b/setup.cfg
index 31d6f57..4a40c8d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -42,6 +42,7 @@ openstack.baremetal.v1 =
baremetal_driver_raid_property_list = ironicclient.osc.v1.baremetal_driver:ListBaremetalDriverRaidProperty
baremetal_driver_show = ironicclient.osc.v1.baremetal_driver:ShowBaremetalDriver
baremetal_node_abort = ironicclient.osc.v1.baremetal_node:AbortBaremetalNode
+ baremetal_node_add_trait = ironicclient.osc.v1.baremetal_node:AddTraitBaremetalNode
baremetal_node_adopt = ironicclient.osc.v1.baremetal_node:AdoptBaremetalNode
baremetal_node_boot_device_set = ironicclient.osc.v1.baremetal_node:BootdeviceSetBaremetalNode
baremetal_node_boot_device_show = ironicclient.osc.v1.baremetal_node:BootdeviceShowBaremetalNode
@@ -64,8 +65,10 @@ openstack.baremetal.v1 =
baremetal_node_provide = ironicclient.osc.v1.baremetal_node:ProvideBaremetalNode
baremetal_node_reboot = ironicclient.osc.v1.baremetal_node:RebootBaremetalNode
baremetal_node_rebuild = ironicclient.osc.v1.baremetal_node:RebuildBaremetalNode
+ baremetal_node_remove_trait = ironicclient.osc.v1.baremetal_node:RemoveTraitBaremetalNode
baremetal_node_set = ironicclient.osc.v1.baremetal_node:SetBaremetalNode
baremetal_node_show = ironicclient.osc.v1.baremetal_node:ShowBaremetalNode
+ baremetal_node_trait_list = ironicclient.osc.v1.baremetal_node:ListTraitsBaremetalNode
baremetal_node_undeploy = ironicclient.osc.v1.baremetal_node:UndeployBaremetalNode
baremetal_node_unset = ironicclient.osc.v1.baremetal_node:UnsetBaremetalNode
baremetal_node_validate = ironicclient.osc.v1.baremetal_node:ValidateBaremetalNode