Add Tempest API tests for inspection rules
As of the 2025.1 "Epoxy" release, inspection rules have been migrated
from the ironic-inspector project into Ironic itself.
This commit introduces Tempest tests to validate the creation, updation
and deletion of new inspection rules functionality within Ironic.
Current focus is on covering the important CRUD operations for
inspection rules to ensure robustness and correctness of
the Inspection rules API.
I guess this is the starting point, as Inspection rules evolves
more tests should be added to ensure the continued relevance
effectiveness of the API test suite.
closes-bug: 2105478
Change-Id: I3f6de02acee8d8c3764d3b1465b92292be3b690c
Signed-off-by: abhibongale <abhishekbongale@outlook.com>
diff --git a/ironic_tempest_plugin/tests/api/admin/test_inspection_rules.py b/ironic_tempest_plugin/tests/api/admin/test_inspection_rules.py
new file mode 100644
index 0000000..3d00ce3
--- /dev/null
+++ b/ironic_tempest_plugin/tests/api/admin/test_inspection_rules.py
@@ -0,0 +1,172 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
+from ironic_tempest_plugin.tests.api import base
+
+
+class TestInspectionRules(base.BaseBaremetalTest):
+ """API tests for Inspection Rules endpoints"""
+
+ # Inspection rules API was introduced in microversion 1.96
+ # We will be skipping this test for the older version.
+ min_microversion = '1.96'
+
+ def setUp(self):
+ super(TestInspectionRules, self).setUp()
+
+ _, self.node = self.create_node(None)
+
+ self.useFixture(
+ api_microversion_fixture.APIMicroversionFixture('1.96'))
+
+ def _create_inspection_rule_payload(self, **kwargs):
+ """Create a Inspection rule payload."""
+ payload = {
+ "description": "Inspection rule to log node UUID",
+ "conditions": [],
+ "actions": [
+ {
+ "op": "log",
+ "args": {
+ "msg": "Node with UUID {node.uuid} is being inspected"
+ }
+ }
+ ],
+ "phase": "main",
+ "priority": 0,
+ "sensitive": False
+ }
+
+ payload.update(kwargs)
+
+ return payload
+
+ @decorators.idempotent_id('7fb771cd-b011-409e-a255-3c71cf7251e8')
+ def test_create_rule_sensitive_true(self):
+ """Test creating rule with sensitive=True."""
+ rule_uuid = data_utils.rand_uuid()
+ payload = self._create_inspection_rule_payload(sensitive=True)
+
+ self.create_inspection_rule(rule_uuid, payload)
+
+ _, fetched_rule = self.client.show_inspection_rule(rule_uuid)
+
+ self.assertTrue(fetched_rule.get('sensitive'))
+ self.assertIsNone(fetched_rule.get('conditions'))
+ self.assertIsNone(fetched_rule.get('actions'))
+
+ @decorators.idempotent_id('e60b4513-7c3d-4b2c-b485-17443bf6485f')
+ def test_create_rule_complex_logging_conditions_actions(self):
+ """Test creating rule with loop conditions and actions"""
+ complex_log_conditions = [
+ {
+ "op": "eq",
+ "args": [
+ "{inventory.system.product_name}",
+ "{item}"
+ ],
+ "loop": [
+ "product_name_1",
+ "product_name_2",
+ "product_name_3"
+ ],
+ "multiple": "any"
+ }
+ ]
+
+ complex_log_actions = [
+ {
+ "op": "set-attribute",
+ "args": [
+ "{item[path]}",
+ "{item[value]}"
+ ],
+ "loop": [
+ {
+ "path": "/driver_info/ipmi_username",
+ "value": "admin"
+ },
+ {
+ "path": "/driver_info/ipmi_password",
+ "value": "password"
+ },
+ {
+ "path": "/driver_info/ipmi_address",
+ "value": "{inventory[bmc_address]}"
+ }
+ ]
+ }
+ ]
+
+ payload = self._create_inspection_rule_payload(
+ conditions=complex_log_conditions,
+ actions=complex_log_actions,
+ )
+
+ _, created_rule = self.create_inspection_rule(None, payload)
+
+ self.assertEqual(complex_log_conditions,
+ created_rule.get('conditions'))
+ self.assertEqual(complex_log_actions,
+ created_rule.get('actions'))
+
+ @decorators.idempotent_id('a786a4ec-1e43-4fb9-8fc3-c53aa4e1f52f')
+ def test_patch_conditions_actions_priority(self):
+ """Test Updating rule'si priority, condition and actions"""
+ payload = self._create_inspection_rule_payload()
+
+ patch = [
+ {
+ "op": "replace",
+ "path": "/priority",
+ "value": 200
+ },
+ {
+ "op": "replace",
+ "path": "/conditions",
+ "value": [
+ {
+ "op": "eq",
+ "args": ["{{ inventory.cpu.count }}", 8]
+ }
+ ]
+ },
+ {
+ "op": "replace",
+ "path": "/actions",
+ "value": [
+ {
+ "op": "set-attribute",
+ "args": ["{{ /properties/cpu_model }}", "cpu_xyz"]
+ },
+ {
+ "op": "log",
+ "args": ["CPU model updated via rule."]
+ }
+ ]
+ }
+ ]
+
+ _, created_rule = self.create_inspection_rule(None, payload)
+ _, fetched_rule = self.client.update_inspection_rule(
+ created_rule.get('uuid'), patch)
+
+ self.assertEqual(fetched_rule.get('priority'),
+ patch[0]['value'])
+ self.assertEqual(fetched_rule.get('conditions'),
+ patch[1]['value'])
+ self.assertEqual(fetched_rule.get('actions'),
+ patch[2]['value'])
diff --git a/ironic_tempest_plugin/tests/api/admin/test_inspection_rules_negatives.py b/ironic_tempest_plugin/tests/api/admin/test_inspection_rules_negatives.py
new file mode 100644
index 0000000..a92728b
--- /dev/null
+++ b/ironic_tempest_plugin/tests/api/admin/test_inspection_rules_negatives.py
@@ -0,0 +1,79 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture
+from ironic_tempest_plugin.tests.api import base
+
+
+class TestInspectionRulesNegative(base.BaseBaremetalTest):
+ """Negative Inspection Rules test"""
+
+ # Inspection rules API was introduced in microversion 1.96
+ # We will be skipping this test for the older version.
+ min_microversion = '1.96'
+
+ def setUp(self):
+ super(TestInspectionRulesNegative, self).setUp()
+
+ _, self.node = self.create_node(None)
+
+ self.useFixture(
+ api_microversion_fixture.APIMicroversionFixture('1.96'))
+
+ def _create_inspection_rule_payload(self, **kwargs):
+ """Create a Inspection rule payload."""
+ payload = {
+ "description": "Inspection rule to log node UUID",
+ "conditions": [],
+ "actions": [
+ {
+ "op": "log",
+ "args": {
+ "msg": "Node with UUID {node.uuid} is being inspected"
+ }
+ }
+ ],
+ "phase": "main",
+ "priority": 0,
+ "sensitive": False
+ }
+
+ payload.update(kwargs)
+
+ return payload
+
+ @decorators.idempotent_id('55403d94-53ce-41ab-989a-da3399314c9d')
+ @decorators.attr(type=['negative'])
+ def test_create_invalid_priority_fails(self):
+ """Test to create Inspection rule with invalid priorities"""
+ invalid_priorities = [-1, 10000, 5000.50]
+
+ for priority_val in invalid_priorities:
+ payload = self._create_inspection_rule_payload(
+ priority=priority_val)
+
+ self.assertRaises(lib_exc.BadRequest,
+ self.create_inspection_rule,
+ rule_uuid=None, payload=payload)
+
+ @decorators.idempotent_id('cf9615b3-904e-4456-b00a-622d39892b88')
+ @decorators.attr(type=['negative'])
+ def test_delete_by_wrong_uiid(self):
+ """Test to delete Inspection Rule with wrong uuid"""
+ rule_uuid = data_utils.rand_uuid()
+ self.assertRaises(lib_exc.NotFound,
+ self.delete_inspection_rule,
+ rule_uuid=rule_uuid)
diff --git a/ironic_tempest_plugin/tests/api/base.py b/ironic_tempest_plugin/tests/api/base.py
index c07137b..7744aee 100644
--- a/ironic_tempest_plugin/tests/api/base.py
+++ b/ironic_tempest_plugin/tests/api/base.py
@@ -38,7 +38,8 @@
# NOTE(jroll): resources must be deleted in a specific order, this list
# defines the resource types to clean up, and the correct order.
RESOURCE_TYPES = ['port', 'portgroup', 'node', 'volume_connector',
- 'volume_target', 'chassis', 'deploy_template', 'runbook']
+ 'volume_target', 'chassis', 'deploy_template',
+ 'runbook', 'inspection_rule']
def creates(resource):
@@ -520,6 +521,32 @@
resp, body = cls.client.create_allocation(resource_class, **kwargs)
return resp, body
+ @classmethod
+ @creates('inspection_rule')
+ def create_inspection_rule(cls, rule_uuid, payload):
+ """Wrapper utility for creating Inspection rule.
+
+ :param rule_uuid: UUID of the Inspection rule.
+ :param payload: Inspection rule other fields.
+ :return: Server response.
+ """
+ if rule_uuid is not None:
+ payload['uuid'] = rule_uuid
+
+ resp, body = cls.client.create_inspection_rule(payload)
+
+ return resp, body
+
+ @classmethod
+ def delete_inspection_rule(cls, rule_uuid):
+ """Delete a inspection rules having the specified UUID.
+
+ :param rule_uuid: UUID of the Inspection rule.
+ """
+ resp, body = cls.client.delete_inspection_rule(rule_uuid)
+
+ return resp
+
class BaseBaremetalRBACTest(BaseBaremetalTest):