Merge "Add stable/2026.1 job"
diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py
index 38a67eb..b0f44d2 100644
--- a/ironic_tempest_plugin/config.py
+++ b/ironic_tempest_plugin/config.py
@@ -193,6 +193,9 @@
     cfg.ListOpt('enabled_bios_interfaces',
                 default=['fake'],
                 help="List of Ironic enabled bios interfaces."),
+    cfg.ListOpt('enabled_console_interfaces',
+                default=['fake', 'no-console'],
+                help="List of Ironic enabled console interfaces."),
     cfg.ListOpt('enabled_deploy_interfaces',
                 default=['iscsi', 'direct'],
                 help="List of Ironic enabled deploy interfaces."),
diff --git a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py
index d12ece4..3a7e35d 100644
--- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py
+++ b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py
@@ -299,12 +299,13 @@
 
         """
         node = {}
-        # Explicitly allow definition of network interface and deploy
-        # interface to allow tests to specify the required values
-        # as they hold a great deal of logic which is executed upon and
-        # they can ultimately impact test behavior.
+        # Explicitly allow definition of network interface, deploy
+        # interface, and console interface to allow tests to specify
+        # the required values as they hold a great deal of logic which
+        # is executed upon and they can ultimately impact test behavior.
         for field in ('resource_class', 'name', 'description', 'shard',
-                      'network_interface', 'deploy_interface'):
+                      'network_interface', 'deploy_interface',
+                      'console_interface'):
             if kwargs.get(field):
                 node[field] = kwargs[field]
 
diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodes.py b/ironic_tempest_plugin/tests/api/admin/test_nodes.py
index 0d82577..27d87cf 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_nodes.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_nodes.py
@@ -10,7 +10,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+
 from oslo_utils import uuidutils
+from tempest.common import compute
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -1319,3 +1321,47 @@
     def test_create_node_with_description(self):
         _, body = self.create_node(self.chassis['uuid'], description='meow')
         self.assertEqual('meow', body['description'])
+
+
+class TestNodeGraphicalConsole(base.BaseBaremetalTest,
+                               compute.NoVNCValidateMixin):
+    """Tests for fake-graphical console connection."""
+
+    min_microversion = '1.96'
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestNodeGraphicalConsole, cls).skip_checks()
+        if CONF.baremetal.driver != 'fake-hardware':
+            raise cls.skipException('These tests rely on fake-hardware')
+        if 'fake-graphical' not in CONF.baremetal.enabled_console_interfaces:
+            raise cls.skipException('These tests rely on fake-graphical being'
+                                    'included in enabled_console_interfaces')
+
+    def setUp(self):
+        super(TestNodeGraphicalConsole, self).setUp()
+
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'],
+                                        console_interface="fake-graphical")
+
+    @decorators.idempotent_id('80504575-9b21-4670-92d1-143b948f9437')
+    def test_graphical_console_connection(self):
+        self.client.set_console_mode(self.node['uuid'], True)
+        waiters.wait_for_bm_node_status(self.client, self.node['uuid'],
+                                        'console_enabled', True)
+        self.addCleanup(self.client.set_console_mode, self.node['uuid'],
+                        False)
+
+        _, body = self.client.get_console(self.node['uuid'])
+        console_info = body['console_info']
+        self.assertEqual('vnc', console_info['type'])
+        # Do the initial HTTP Request to novncproxy to get the NoVNC JavaScript
+        self.validate_novnc_html(console_info['url'])
+        # Do the WebSockify HTTP Request to novncproxy to do the RFB connection
+        self.websocket = compute.create_websocket(console_info['url'])
+        self.addCleanup(self.websocket.close)
+        # Validate that we successfully connected and upgraded to Web Sockets
+        self.validate_websocket_upgrade()
+        # Validate the RFB Negotiation to determine if a valid VNC session
+        self.validate_rfb_negotiation()
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py
index 09da6d3..63aa0ed 100644
--- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py
@@ -423,18 +423,6 @@
         self.boot_and_verify_node()
 
 
-class BaremetalIdracWSManDirectIPxeWholedisk(
-        BaremetalIdracDirectIPxeWholedisk):
-    power_interface = 'idrac-wsman'
-    management_interface = 'idrac-wsman'
-
-
-class BaremetalIdracWSManDirectPxeWholedisk(
-        BaremetalIdracDirectPxeWholedisk):
-    power_interface = 'idrac-wsman'
-    management_interface = 'idrac-wsman'
-
-
 class BaremetalIdracRedfishDirectIPxeWholedisk(
         BaremetalIdracDirectIPxeWholedisk):
 
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py
index ca63292..74efaba 100644
--- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py
@@ -125,8 +125,3 @@
 class BaremetalIdracRedfishBios(
         BaremetalIdracBiosCleaning):
     bios_interface = 'idrac-redfish'
-
-
-class BaremetalIdracWSManBios(
-        BaremetalIdracBiosCleaning):
-    bios_interface = 'idrac-wsman'
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py
index a18f8e9..519eea6 100644
--- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py
@@ -198,13 +198,6 @@
     power_interface = 'idrac-redfish'
 
 
-class BaremetalIdracWSManManagementCleaning(
-        BaremetalIdracManagementCleaning):
-
-    management_interface = 'idrac-wsman'
-    power_interface = 'idrac-wsman'
-
-
 class BaremetalIdracRaidCleaning(bsm.BaremetalStandaloneScenarioTest):
 
     mandatory_attr = ['driver', 'raid_interface']
@@ -319,11 +312,6 @@
     raid_interface = 'idrac-redfish'
 
 
-class BaremetalIdracWSManRaidCleaning(
-        BaremetalIdracRaidCleaning):
-    raid_interface = 'idrac-wsman'
-
-
 class BaremetalRedfishFirmwareUpdate(bsm.BaremetalStandaloneScenarioTest):
 
     api_microversion = '1.68'  # to support redfish firmware update
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_inspection_basic.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_inspection_basic.py
index 7d1d602..98f1b64 100644
--- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_inspection_basic.py
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_inspection_basic.py
@@ -139,7 +139,3 @@
 
 class BaremetalIdracRedfishInspect(BaremetalIdracInspect):
     inspect_interface = 'idrac-redfish'
-
-
-class BaremetalIdracWSManInspect(BaremetalIdracInspect):
-    inspect_interface = 'idrac-wsman'
diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py
index 5bb7df5..4a8db41 100644
--- a/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py
+++ b/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py
@@ -174,7 +174,13 @@
     @decorators.idempotent_id('26e2f145-2a8e-4dc7-8457-7f2eb2c6749d')
     @utils.services('compute', 'image', 'network')
     def test_baremetal_multitenancy(self):
-        self.multitenancy_check()
+        if CONF.service_available.nova:
+            # If nova is not present, run the test, otherwise
+            # skip the test in order to not duplicate test execution.
+            self.skipTest('Compute service is present, skipping this test '
+                          'to avoid duplication.')
+        else:
+            self.multitenancy_check()
 
     @decorators.idempotent_id('9e38631a-2df2-11e9-810e-8c16450ea513')
     @utils.services('compute', 'image', 'network')
diff --git a/ironic_tempest_plugin/tests/scenario/test_introspection_basic.py b/ironic_tempest_plugin/tests/scenario/test_introspection_basic.py
index 44b8f98..918eea8 100644
--- a/ironic_tempest_plugin/tests/scenario/test_introspection_basic.py
+++ b/ironic_tempest_plugin/tests/scenario/test_introspection_basic.py
@@ -22,19 +22,21 @@
 
     def verify_node_introspection_data(self, node):
         data = self.introspection_data(node['uuid'])
-        self.assertEqual(data['cpu_arch'],
-                         self.flavor['properties']['cpu_arch'])
+        # Validate that introspection discovered CPU architecture
+        # (common architectures supported by Ironic)
+        self.assertIn(data['cpu_arch'],
+                      ['x86_64', 'i386', 'aarch64', 'ppc64', 'ppc64le'])
         self.assertGreater(int(data['memory_mb']), 0)
         self.assertGreater(int(data['cpus']), 0)
 
     def verify_node_flavor(self, node):
-        expected_cpu_arch = self.flavor['properties']['cpu_arch']
-
+        # Validate that introspection populated node properties correctly
         self.assertGreater(int(node['properties']['cpus']), 0)
         self.assertGreater(int(node['properties']['memory_mb']), 0)
         self.assertGreater(int(node['properties']['local_gb']), 0)
-        self.assertEqual(expected_cpu_arch,
-                         node['properties']['cpu_arch'])
+        # Validate cpu_arch was discovered and is a known architecture
+        self.assertIn(node['properties']['cpu_arch'],
+                      ['x86_64', 'i386', 'aarch64', 'ppc64', 'ppc64le'])
 
     def verify_introspection_aborted(self, uuid):
         status = self.introspection_status(uuid)
@@ -53,7 +55,6 @@
     def test_baremetal_introspection(self):
         """This smoke test case follows this set of operations:
 
-            * Fetches expected properties from baremetal flavor
             * Removes all properties from nodes
             * Sets nodes to manageable state
             * Imports introspection rule basic_ops_rule.json