Merge "Exclude ramdisk tests with tinycore in uefi mode"
diff --git a/ironic_tempest_plugin/tests/api/admin/test_allocations.py b/ironic_tempest_plugin/tests/api/admin/test_allocations.py
index b9ab70b..074914a 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_allocations.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_allocations.py
@@ -225,6 +225,94 @@
         self.assertTrue(body['last_error'])
 
 
+class TestAllocationsWithJsonExtSupport(Base):
+    """Tests for baremetal allocations to validate appending .json extension
+
+     to an allocation's `name` or `uuid` in API versions 1.90 and prior works.
+     """
+
+    max_microversion = '1.90'  # Max API version allowed for testing: 1.90
+    min_microversion = '1.90'  # Min API version allowed for testing: 1.90
+
+    @decorators.idempotent_id('d111b85b-a169-440e-9dcd-9b8b6ad8c917')
+    def test_create_show_allocation_with_json(self):
+        """Show an allocation while appending .json extension to its uuid"""
+        self.assertIsNone(self.node['allocation_uuid'])
+        _, body = self.create_allocation(self.resource_class)
+        uuid = body['uuid']
+
+        self.assertTrue(uuid)
+        self.assertEqual('allocating', body['state'])
+        self.assertEqual(self.resource_class, body['resource_class'])
+        self.assertIsNone(body['last_error'])
+        self.assertIsNone(body['node_uuid'])
+
+        _, body = waiters.wait_for_allocation(self.client, uuid)
+        self.assertEqual('active', body['state'])
+        self.assertEqual(self.resource_class, body['resource_class'])
+        self.assertIsNone(body['last_error'])
+        self.assertEqual(self.node['uuid'], body['node_uuid'])
+
+        _, body2 = self.client.show_node_allocation(body['node_uuid'])
+        self.assertEqual(body, body2)
+
+        _, node = self.client.show_node('%s.json' % self.node['uuid'])
+        self.assertEqual(uuid, node['allocation_uuid'])
+
+    @decorators.idempotent_id('b94643f4-f789-4c04-a806-bf769e0bc181')
+    def test_delete_allocation_with_json(self):
+        """Delete an allocation while appending .json extension to its uuid"""
+        _, body = self.create_allocation(self.resource_class)
+
+        self.client.delete_allocation('%s.json' % body['uuid'])
+
+        self.assertRaises(lib_exc.NotFound, self.client.show_allocation,
+                          '%s.json' % body['uuid'])
+
+
+class TestAllocationsWithoutJsonExtSupport(Base):
+    """Tests for baremetal allocations to validate appending .json extension
+
+    to an allocation's `name` or `uuid` in API versions later than 1.90 is
+
+    disabled.
+    """
+
+    min_microversion = '1.91'  # Min API version allowed for testing: 1.91
+
+    @decorators.idempotent_id('6b851ef4-e364-4a3e-af1d-e6b73c1adec6')
+    def test_create_show_allocation_with_json(self):
+        """Trying to show an allocation while appending .json ext 404s"""
+        self.assertIsNone(self.node['allocation_uuid'])
+        _, body = self.create_allocation(self.resource_class)
+        uuid = body['uuid']
+
+        self.assertTrue(uuid)
+        self.assertEqual('allocating', body['state'])
+        self.assertEqual(self.resource_class, body['resource_class'])
+        self.assertIsNone(body['last_error'])
+        self.assertIsNone(body['node_uuid'])
+
+        _, body = waiters.wait_for_allocation(self.client, uuid)
+        self.assertEqual('active', body['state'])
+        self.assertEqual(self.resource_class, body['resource_class'])
+        self.assertIsNone(body['last_error'])
+        self.assertEqual(self.node['uuid'], body['node_uuid'])
+
+        _, body2 = self.client.show_node_allocation(body['node_uuid'])
+        self.assertEqual(body, body2)
+
+        self.assertRaises(lib_exc.NotFound, self.client.show_allocation,
+                          '%s.json' % body['uuid'])
+
+    @decorators.idempotent_id('cdf36c46-3da7-4306-b265-bcea3ab3bc3f')
+    def test_delete_allocation_with_json(self):
+        """Trying to delete an allocation while appending .json ext 404s"""
+        _, body = self.create_allocation(self.resource_class)
+        self.assertRaises(lib_exc.NotFound, self.client.delete_allocation,
+                          '%s.json' % body['uuid'])
+
+
 class TestBackfill(Base):
     """Tests for backfilling baremetal allocations."""
 
diff --git a/ironic_tempest_plugin/tests/api/admin/test_deploy_templates.py b/ironic_tempest_plugin/tests/api/admin/test_deploy_templates.py
index 17631f8..b3b4497 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_deploy_templates.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_deploy_templates.py
@@ -196,6 +196,70 @@
         self.assertEqual([new_steps[1]], body['steps'])
 
 
+class TestDeployTemplatesWithJsonExtSupport(base.BaseBaremetalTest):
+    """Tests for deploy templates to validate appending .json extension to a
+
+    template's `name` or `uuid` in API versions 1.90 and prior works.
+    """
+
+    max_microversion = '1.90'  # Max API version allowed for testing: 1.90
+    min_microversion = '1.90'  # Min API version allowed for testing: 1.90
+
+    def setUp(self):
+        super(TestDeployTemplatesWithJsonExtSupport, self).setUp()
+        self.name = _get_random_trait()
+        self.steps = copy.deepcopy(EXAMPLE_STEPS)
+        _, self.template = self.create_deploy_template(self.name,
+                                                       steps=self.steps)
+
+    @decorators.idempotent_id('df2fabbd-3c8b-4979-8f13-c58c96122adb')
+    def test_delete_deploy_template_with_json(self):
+        """Delete a template while appending .json extension to its uuid"""
+        self.delete_deploy_template('%s.json' % self.template['uuid'])
+
+        self.assertRaises(lib_exc.NotFound, self.client.show_deploy_template,
+                          self.template['uuid'])
+
+    @decorators.idempotent_id('8b64a54d-ead7-4b3f-8fae-a1b8bdefe3ee')
+    def test_show_deploy_template_with_json(self):
+        """Show a template while appending .json extension to its uuid"""
+        _, template = self.client.show_deploy_template('%s.json' %
+                                                       self.template['uuid'])
+        self._assertExpected(self.template, template)
+        self.assertEqual(self.name, template['name'])
+        self.assertEqual(self.steps, template['steps'])
+        self.assertIn('uuid', template)
+        self.assertEqual({}, template['extra'])
+
+
+class TestDeployTemplatesWithoutJsonExtSupport(base.BaseBaremetalTest):
+    """Tests for deploy templates to validate appending .json extension to a
+
+    template's `name` or `uuid` in API versions later than 1.90 is disabled.
+    """
+
+    min_microversion = '1.91'  # Min API version allowed for testing: 1.91
+
+    def setUp(self):
+        super(TestDeployTemplatesWithoutJsonExtSupport, self).setUp()
+        self.name = _get_random_trait()
+        self.steps = copy.deepcopy(EXAMPLE_STEPS)
+        _, self.template = self.create_deploy_template(self.name,
+                                                       steps=self.steps)
+
+    @decorators.idempotent_id('61d05189-9f84-4973-835b-a860b655dfe3')
+    def test_delete_deploy_template_with_json(self):
+        """Trying to delete a template while appending .json ext 404s"""
+        self.assertRaises(lib_exc.NotFound, self.client.delete_deploy_template,
+                          '%s.json' % self.template['uuid'])
+
+    @decorators.idempotent_id('432429dd-b964-4133-aa46-cd43a7cd9c37')
+    def test_show_deploy_template_with_json(self):
+        """Trying to show a template while appending .json ext 404s"""
+        self.assertRaises(lib_exc.NotFound, self.client.show_deploy_template,
+                          '%s.json' % self.template['uuid'])
+
+
 class TestDeployTemplatesOldAPI(base.BaseBaremetalTest):
     """Negative tests for deploy templates using an old API version."""
 
diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodes.py b/ironic_tempest_plugin/tests/api/admin/test_nodes.py
index 79e8da4..a5c52ce 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_nodes.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_nodes.py
@@ -180,6 +180,105 @@
         self.assertNotIn('description', loaded_node)
 
 
+class TestNodesWithJsonExtSupport(base.BaseBaremetalTest):
+    """Tests for baremetal nodes to validate appending .json extension to a
+
+    node's `name` or `uuid` in API versions 1.90 and prior works.
+    """
+
+    max_microversion = '1.90'  # Max API version allowed for testing: 1.90
+    min_microversion = '1.90'  # Min API version allowed for testing: 1.90
+
+    def setUp(self):
+        super(TestNodesWithJsonExtSupport, self).setUp()
+
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'])
+
+    @decorators.idempotent_id('2ffec2ec-3dca-4e4f-ac6c-dee7c44cdef5')
+    def test_update_node_with_json(self):
+        """Update a node while appending .json extension to its uuid"""
+        props = {'cpu_arch': 'x86_64',
+                 'cpus': '12',
+                 'local_gb': '10',
+                 'memory_mb': '128'}
+
+        _, node = self.create_node(self.chassis['uuid'], **props)
+
+        new_p = {'cpu_arch': 'arm64',
+                 'cpus': '1',
+                 'local_gb': '10000',
+                 'memory_mb': '12300'}
+
+        _, body = self.client.update_node('%s.json' % node['uuid'],
+                                          properties=new_p)
+        _, node = self.client.show_node(node['uuid'])
+        self._assertExpected(new_p, node['properties'])
+
+    @decorators.idempotent_id('4c376a23-04b8-4fc8-955f-abc5668d34c3')
+    def test_delete_node_with_json(self):
+        """Delete a node while appending .json extension to its uuid"""
+        _, node = self.create_node(self.chassis['uuid'])
+
+        self.delete_node('%s.json' % node['uuid'])
+
+        self.assertRaises(lib_exc.NotFound, self.client.show_node,
+                          node['uuid'])
+
+    @decorators.idempotent_id('bc1134aa-2950-409c-b91c-b2d85df9b065')
+    def test_show_node_with_json(self):
+        """Show a node while appending .json extension to its uuid"""
+        _, loaded_node = self.client.show_node('%s.json' % self.node['uuid'])
+        self._assertExpected(self.node, loaded_node)
+
+
+class TestNodesWithoutJsonExtSupport(base.BaseBaremetalTest):
+    """Tests for baremetal nodes to validate appending .json extension to a
+
+    node's `name` or `uuid` in API versions later than 1.90 is disabled.
+    """
+
+    min_microversion = '1.91'  # Min API version allowed for testing: 1.91
+
+    def setUp(self):
+        super(TestNodesWithoutJsonExtSupport, self).setUp()
+
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'])
+
+    @decorators.idempotent_id('65505002-3ac7-4a07-9f66-2e3bcb0f5e95')
+    def test_update_node_with_json(self):
+        """Trying to update a node while appending .json extension 404s"""
+        props = {'cpu_arch': 'x86_64',
+                 'cpus': '12',
+                 'local_gb': '10',
+                 'memory_mb': '128'}
+
+        _, node = self.create_node(self.chassis['uuid'], **props)
+
+        new_p = {'cpu_arch': 'arm64',
+                 'cpus': '1',
+                 'local_gb': '10000',
+                 'memory_mb': '12300'}
+
+        self.assertRaises(lib_exc.NotFound, self.client.update_node,
+                          '%s.json' % node['uuid'], properties=new_p)
+
+    @decorators.idempotent_id('594160b6-a608-4024-98d1-818f75d2d895')
+    def test_delete_node_with_json(self):
+        """Trying to delete a node while appending .json extension 404s"""
+        _, node = self.create_node(self.chassis['uuid'])
+        self.assertRaises(lib_exc.NotFound, self.client.delete_node,
+                          '%s.json' % node['uuid'])
+
+    @decorators.idempotent_id('e83cd539-4d09-46d0-a448-365a219ca353')
+    def test_show_node_with_json(self):
+        """Trying to show a node while appending .json extension 404s"""
+
+        self.assertRaises(lib_exc.NotFound, self.client.show_node,
+                          '%s.json' % self.node['uuid'])
+
+
 class TestNodesResourceClass(base.BaseBaremetalTest):
 
     min_microversion = '1.21'
@@ -642,7 +741,15 @@
                                     [{'path': '/%s' % field,
                                       'op': 'remove'}])
             _, node = self.client.show_node(self.node['uuid'])
-            self.assertEqual('fake', node[field])
+            if (iface in ['boot', 'deploy', 'inspect', 'network']
+                    and 'fake' != node[field]):
+                # NOTE(TheJulia): Depending on the remote configuration
+                # these interfaces may have explicit defaults which the
+                # reset action changes the value to. This is okay, but
+                # we need to not fail when that is the case.
+                self.assertNotEqual(self.node[field], node[field])
+            else:
+                self.assertEqual('fake', node[field])
 
 
 class TestResetInterfaces(TestHardwareInterfaces):
@@ -1112,7 +1219,21 @@
     def setUp(self):
         super(TestNodesProtectedOldApi, self).setUp()
         _, self.chassis = self.create_chassis()
+
         _, self.node = self.create_node(self.chassis['uuid'])
+        self.useFixture(
+            api_microversion_fixture.APIMicroversionFixture('1.31'))
+        # Now with a 1.31 microversion, swap the deploy and network
+        # interfaces into place so the test doesn't break depending on
+        # the environment's default state.
+        self.client.update_node(self.node['uuid'],
+                                [{'path': '/deploy_interface',
+                                  'op': 'replace',
+                                  'value': 'fake'},
+                                 {'path': '/network_interface',
+                                  'op': 'replace',
+                                  'value': 'noop'}])
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.1'))
         self.deploy_node(self.node['uuid'])
         _, self.node = self.client.show_node(self.node['uuid'])
 
diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodestates.py b/ironic_tempest_plugin/tests/api/admin/test_nodestates.py
index 010e45b..3890fae 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_nodestates.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_nodestates.py
@@ -212,7 +212,8 @@
             api_microversion_fixture.APIMicroversionFixture('1.31'))
         _, self.node = self.create_node(self.chassis['uuid'],
                                         deploy_interface='fake',
-                                        network_interface='noop')
+                                        network_interface='noop',
+                                        inspect_interface='fake')
         self.useFixture(
             api_microversion_fixture.APIMicroversionFixture('1.11')
         )
diff --git a/ironic_tempest_plugin/tests/api/admin/test_portgroups.py b/ironic_tempest_plugin/tests/api/admin/test_portgroups.py
index 86dccd9..332c791 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_portgroups.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_portgroups.py
@@ -153,3 +153,106 @@
         self.assertEqual(new_address, body['address'])
         self._assertExpected(new_extra, body['extra'])
         self.assertNotIn('foo', body['extra'])
+
+
+class TestPortGroupsWithJsonExtSupport(base.BaseBaremetalTest):
+    """Basic test cases to validate appending .json extension to a portgroup's
+
+    `name` or `uuid` in API versions 1.90 and prior works.
+    """
+
+    max_microversion = '1.90'  # Max API version allowed for testing: 1.90
+    min_microversion = '1.90'  # Min API version allowed for testing: 1.90
+
+    def setUp(self):
+        super(TestPortGroupsWithJsonExtSupport, self).setUp()
+        self.useFixture(
+            api_microversion_fixture.APIMicroversionFixture(
+                self.min_microversion))
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'])
+        _, self.portgroup = self.create_portgroup(
+            self.node['uuid'], address=data_utils.rand_mac_address(),
+            name=data_utils.rand_name('portgroup'))
+
+    @decorators.idempotent_id('607ea131-ef5c-44e9-a900-a8e426b804e8')
+    def test_delete_portgroup_with_json(self):
+        """Delete a portgroup while appending .json extension to its uuid"""
+        self.delete_portgroup('%s.json' % self.portgroup['uuid'])
+        self.assertRaises(lib_exc.NotFound, self.client.show_portgroup,
+                          self.portgroup['uuid'])
+
+    @decorators.idempotent_id('9126ecac-310f-440a-81d4-ba9af1767845')
+    def test_show_portgroup_with_json(self):
+        """Show a portgroup while appending .json extension to its uuid"""
+        _, portgroup = self.client.show_portgroup('%s.json' %
+                                                  self.portgroup['uuid'])
+        self._assertExpected(self.portgroup, portgroup)
+
+    @decorators.idempotent_id('a6f154aa-e447-4576-89e2-9e8951fb7c43')
+    def test_update_portgroup_with_json(self):
+        """Update a portgroup while appending .json extension to its uuid"""
+        new_address = data_utils.rand_mac_address()
+        new_extra = {'foo': 'test'}
+
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': new_address},
+                 {'path': '/extra/foo',
+                  'op': 'replace',
+                  'value': new_extra['foo']},
+                 ]
+
+        self.client.update_portgroup('%s.json' % self.portgroup['uuid'],
+                                     patch)
+
+        _, body = self.client.show_portgroup(self.portgroup['uuid'])
+
+        self.assertEqual(new_address, body['address'])
+        self._assertExpected(new_extra, body['extra'])
+
+
+class TestPortGroupsWithoutJsonExtSupport(base.BaseBaremetalTest):
+    """Basic test cases to validate appending .json extension to a portgroup's
+
+    `name` or `uuid` in API versions later than 1.90 is disabled.
+    """
+
+    min_microversion = '1.91'  # Min API version allowed for testing: 1.91
+
+    def setUp(self):
+        super(TestPortGroupsWithoutJsonExtSupport, self).setUp()
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'])
+        _, self.portgroup = self.create_portgroup(
+            self.node['uuid'], address=data_utils.rand_mac_address(),
+            name=data_utils.rand_name('portgroup'))
+
+    @decorators.idempotent_id('2aede448-7165-4d54-92de-3be31ae19af0')
+    def test_delete_portgroup_with_json(self):
+        """Trying to delete a portgroup while appending .json ext 404s"""
+        self.assertRaises(lib_exc.NotFound, self.client.delete_portgroup,
+                          '%s.json' % self.portgroup['uuid'])
+
+    @decorators.idempotent_id('be8ade56-8ac1-49f4-981a-93b403fa1d79')
+    def test_show_portgroup_with_json(self):
+        """Trying to show a portgroup while appending .json ext 404s"""
+        self.assertRaises(lib_exc.NotFound, self.client.show_portgroup,
+                          '%s.json' % self.portgroup['uuid'])
+
+    @decorators.idempotent_id('f624352a-6560-4ec1-b4ff-ccfe7cda08f2')
+    def test_update_portgroup_with_json(self):
+        """Trying to update a portgroup while appending .json ext 404s"""
+        new_address = data_utils.rand_mac_address()
+        new_extra = {'foo': 'test'}
+
+        patch = [{'path': '/address',
+                  'op': 'replace',
+                  'value': new_address},
+                 {'path': '/extra/foo',
+                  'op': 'replace',
+                  'value': new_extra['foo']},
+                 ]
+
+        self.assertRaises(lib_exc.NotFound, self.client.update_portgroup,
+                          '%s.json' % self.portgroup['uuid'], patch)
diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
index 9ceecb7..e9090cb 100644
--- a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
+++ b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
@@ -244,6 +244,13 @@
 
         return instance, node
 
+    def reboot_node(self, instance, servers_client=None):
+        if servers_client is None:
+            servers_client = self.os_primary.servers_client
+        servers_client.reboot_server(instance['id'], type='HARD')
+        waiters.wait_for_server_status(servers_client,
+                                       instance['id'], 'ACTIVE')
+
     def terminate_instance(self, instance, servers_client=None):
         if servers_client is None:
             servers_client = self.os_primary.servers_client
diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py
index dcfc023..010c220 100644
--- a/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py
+++ b/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py
@@ -237,6 +237,12 @@
             self.rescue_instance(self.instance, self.node, ip_address)
             self.unrescue_instance(self.instance, self.node, ip_address)
 
+        # Reboot node
+        self.reboot_node(self.instance)
+
+        # ensure we can ping the node again
+        self.assertTrue(self.ping_ip_address(ip_address))
+
         self.terminate_instance(self.instance)
 
     @decorators.idempotent_id('549173a5-38ec-42bb-b0e2-c8b9f4a08943')
diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py
index b724683..aa2d1d6 100644
--- a/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py
+++ b/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py
@@ -67,6 +67,9 @@
         self.addCleanup(test_utils.call_and_ignore_notfound_exc,
                         self.volumes_client.delete_volume, volume['id'])
         self.assertEqual(name, volume['name'])
+        # NOTE(TheJulia): This can fail if the remote service can't figure out
+        # what to do. Really depends on the remote storage configuration which
+        # is not something we can sense out remotely.
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'available')
         # The volume retrieved on creation has a non-up-to-date status.
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 7720986..11eb7c0 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -38,8 +38,9 @@
             voting: false
         # NOTE(dtantsur): these jobs cover rarely changed tests and are quite
         # unstable, so keep them non-voting.
-        - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode:
-            voting: false
+        # NOTE(TheJulia): Except this first one so we can validate fixes to
+        # the base tests as we make them.
+        - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode
         - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode-2024.1:
             voting: false
         - ironic-tempest-ipa-wholedisk-direct-tinyipa-multinode-2023.2: