From 0a26a6677d6934168318d2120204f72fdb3eb37c Mon Sep 17 00:00:00 2001 From: Dmitry Tantsur Date: Mon, 17 Sep 2018 15:20:28 +0200 Subject: [PATCH] Add introspection rules actions to add/remove traits on nodes Otherwise it's not possible to modify them, since they're not updated via the regular node updating mechanism. Change-Id: I338015ff9dafe07f4e70a23ddcf6cd488eda9907 Story: #2003788 Task: #26496 --- doc/source/user/usage.rst | 6 +++ ironic_inspector/node_cache.py | 22 +++++++++++ ironic_inspector/plugins/rules.py | 14 +++++++ .../test/unit/test_plugins_rules.py | 38 +++++++++++++++++++ .../notes/trait-actions-eec05cbb6a944619.yaml | 5 +++ setup.cfg | 2 + 6 files changed, 87 insertions(+) create mode 100644 releasenotes/notes/trait-actions-eec05cbb6a944619.yaml diff --git a/doc/source/user/usage.rst b/doc/source/user/usage.rst index efae0b9b8..14c78a091 100644 --- a/doc/source/user/usage.rst +++ b/doc/source/user/usage.rst @@ -123,6 +123,12 @@ Default available actions include: value as a list and appends value to it. If optional ``unique`` parameter is set to ``True``, nothing will be added if given value is already in a list. +* ``add-trait`` adds a trait to an Ironic node. Requires a ``name`` field + with the name of the trait to add. + +* ``remove-trait`` removes a trait from an Ironic node. Requires a ``name`` + field with the name of the trait to remove. + Starting from Mitaka release, ``value`` field in actions supports fetching data from introspection, using `python string formatting notation `_:: diff --git a/ironic_inspector/node_cache.py b/ironic_inspector/node_cache.py index 3e9ca3e83..9365222b6 100644 --- a/ironic_inspector/node_cache.py +++ b/ironic_inspector/node_cache.py @@ -470,6 +470,28 @@ class NodeInfo(object): ironic=ironic, capabilities=ir_utils.dict_to_capabilities(existing)) + def add_trait(self, trait, ironic=None): + """Add a trait to the node. + + :param trait: trait to add + :param ironic: Ironic client to use instead of self.ironic + """ + ironic = ironic or self.ironic + ironic.node.add_trait(self.uuid, trait) + + def remove_trait(self, trait, ironic=None): + """Remove a trait from the node. + + :param trait: trait to add + :param ironic: Ironic client to use instead of self.ironic + """ + ironic = ironic or self.ironic + try: + ironic.node.remove_trait(self.uuid, trait) + except exceptions.NotFound: + LOG.debug('Trait %s is not set, cannot remove', trait, + node_info=self) + def delete_port(self, port, ironic=None): """Delete port. diff --git a/ironic_inspector/plugins/rules.py b/ironic_inspector/plugins/rules.py index adc19429a..dd04a1815 100644 --- a/ironic_inspector/plugins/rules.py +++ b/ironic_inspector/plugins/rules.py @@ -151,3 +151,17 @@ class ExtendAttributeAction(base.RuleActionPlugin): return values node_info.replace_field(params['path'], _replace, default=[]) + + +class AddTraitAction(base.RuleActionPlugin): + REQUIRED_PARAMS = {'name'} + + def apply(self, node_info, params, **kwargs): + node_info.add_trait(params['name']) + + +class RemoveTraitAction(base.RuleActionPlugin): + REQUIRED_PARAMS = {'name'} + + def apply(self, node_info, params, **kwargs): + node_info.remove_trait(params['name']) diff --git a/ironic_inspector/test/unit/test_plugins_rules.py b/ironic_inspector/test/unit/test_plugins_rules.py index cfdbbb540..b3fd80a10 100644 --- a/ironic_inspector/test/unit/test_plugins_rules.py +++ b/ironic_inspector/test/unit/test_plugins_rules.py @@ -14,6 +14,7 @@ """Tests for introspection rules plugins.""" +from ironicclient import exceptions import mock from ironic_inspector.common import ironic as ir_utils @@ -222,3 +223,40 @@ class TestExtendAttributeAction(test_base.NodeTest): self.node.extra['value'] = [42] self.act.apply(self.node_info, params) self.assertFalse(mock_patch.called) + + +@mock.patch('ironic_inspector.common.ironic.get_client', autospec=True) +class TestAddTraitAction(test_base.NodeTest): + act = rules_plugins.AddTraitAction() + params = {'name': 'CUSTOM_FOO'} + + def test_validate(self, mock_cli): + self.act.validate(self.params) + self.assertRaises(ValueError, self.act.validate, {'value': 42}) + + def test_add(self, mock_cli): + self.act.apply(self.node_info, self.params) + mock_cli.return_value.node.add_trait.assert_called_once_with( + self.uuid, 'CUSTOM_FOO') + + +@mock.patch('ironic_inspector.common.ironic.get_client', autospec=True) +class TestRemoveTraitAction(test_base.NodeTest): + act = rules_plugins.RemoveTraitAction() + params = {'name': 'CUSTOM_FOO'} + + def test_validate(self, mock_cli): + self.act.validate(self.params) + self.assertRaises(ValueError, self.act.validate, {'value': 42}) + + def test_remove(self, mock_cli): + self.act.apply(self.node_info, self.params) + mock_cli.return_value.node.remove_trait.assert_called_once_with( + self.uuid, 'CUSTOM_FOO') + + def test_remove_not_found(self, mock_cli): + mock_cli.return_value.node.remove_trait.side_effect = ( + exceptions.NotFound('trait not found')) + self.act.apply(self.node_info, self.params) + mock_cli.return_value.node.remove_trait.assert_called_once_with( + self.uuid, 'CUSTOM_FOO') diff --git a/releasenotes/notes/trait-actions-eec05cbb6a944619.yaml b/releasenotes/notes/trait-actions-eec05cbb6a944619.yaml new file mode 100644 index 000000000..a68a7299e --- /dev/null +++ b/releasenotes/notes/trait-actions-eec05cbb6a944619.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds new introspection rules actions to add or remove traits on nodes: + ``add-trait`` and ``remove-trait``. diff --git a/setup.cfg b/setup.cfg index 2edbd692e..2c84cc1f2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,8 @@ ironic_inspector.rules.actions = set-attribute = ironic_inspector.plugins.rules:SetAttributeAction set-capability = ironic_inspector.plugins.rules:SetCapabilityAction extend-attribute = ironic_inspector.plugins.rules:ExtendAttributeAction + add-trait = ironic_inspector.plugins.rules:AddTraitAction + remove-trait = ironic_inspector.plugins.rules:RemoveTraitAction ironic_inspector.pxe_filter = dnsmasq = ironic_inspector.pxe_filter.dnsmasq:DnsmasqFilter iptables = ironic_inspector.pxe_filter.iptables:IptablesFilter