Rescue mode tempest tests

- Adds scenario test for rescue mode.
- Removes redundant call to validate_authentication() in
existing test code due to it is called within get_remote_client.

Partial-Bug: 1526449
Depends-On: I7c20a0c5f566c3255350fd494d1a2cde84a99440

Change-Id: I6b62724711ddbe17b59dad30283e544272d9c1f3
Co-Authored-By: Mario Villaplana <mario.villaplana@gmail.com>
Co-Authored-By: Annie Lezil <annie.lezil@gmail.com>
diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py
index 3594b5b..c4fb185 100644
--- a/ironic_tempest_plugin/config.py
+++ b/ironic_tempest_plugin/config.py
@@ -129,6 +129,8 @@
     cfg.ListOpt('enabled_rescue_interfaces',
                 default=['no-rescue'],
                 help="List of Ironic enabled rescue interfaces."),
+    cfg.StrOpt('default_rescue_interface',
+               help="Ironic default rescue interface."),
     cfg.IntOpt('adjusted_root_disk_size_gb',
                min=0,
                help="Ironic adjusted disk size to use in the standalone tests "
diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
index c220619..6d7d55b 100644
--- a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
+++ b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py
@@ -19,6 +19,7 @@
 from tempest.common import waiters
 from tempest import config
 from tempest.lib.common import api_version_utils
+from tempest.lib.common.utils.linux import remote_client
 from tempest.lib import exceptions as lib_exc
 
 from ironic_tempest_plugin import clients
@@ -232,3 +233,44 @@
              BaremetalProvisionStates.AVAILABLE],
             timeout=CONF.baremetal.unprovision_timeout,
             interval=30)
+
+    def rescue_instance(self, instance, node, server_ip,
+                        servers_client=None):
+        """Rescue the instance, verify we can ping and SSH."""
+        if servers_client is None:
+            servers_client = self.servers_client
+
+        rescuing_instance = servers_client.rescue_server(instance['id'])
+        rescue_password = rescuing_instance['adminPass']
+
+        self.wait_provisioning_state(
+            node['uuid'],
+            BaremetalProvisionStates.RESCUE,
+            timeout=CONF.baremetal.rescue_timeout)
+        waiters.wait_for_server_status(servers_client,
+                                       instance['id'], 'RESCUE')
+        # Ping server ip
+        self.assertTrue(self.ping_ip_address(server_ip))
+        # Open ssh connection to server
+        linux_client = remote_client.RemoteClient(
+            server_ip,
+            'rescue',
+            password=rescue_password,
+            server=self.instance,
+            servers_client=servers_client,
+            ssh_timeout=CONF.baremetal.rescue_timeout)
+        linux_client.validate_authentication()
+
+    def unrescue_instance(self, instance, node, server_ip,
+                          servers_client=None):
+        if servers_client is None:
+            servers_client = self.servers_client
+        self.servers_client.unrescue_server(instance['id'])
+        self.wait_provisioning_state(
+            node['uuid'],
+            BaremetalProvisionStates.ACTIVE,
+            timeout=CONF.baremetal.unrescue_timeout)
+        waiters.wait_for_server_status(servers_client,
+                                       instance['id'], 'ACTIVE')
+        # Verify server connection
+        self.get_remote_client(server_ip)
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 f78155d..61e5323 100644
--- a/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py
+++ b/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py
@@ -45,6 +45,26 @@
           expected state transitions
     """
 
+    TEST_RESCUE_MODE = False
+
+    @classmethod
+    def skip_checks(cls):
+        super(BaremetalBasicOps, cls).skip_checks()
+
+        # If default rescue interface is configured to test the rescue
+        # feature, then skips this test and let the test derived class
+        # to be executed.
+        rescue_if = CONF.baremetal.default_rescue_interface
+        if cls.TEST_RESCUE_MODE:
+            if not rescue_if or rescue_if == 'no-rescue':
+                msg = 'Node rescue interface is not enabled.'
+                raise cls.skipException(msg)
+        else:
+            if rescue_if and rescue_if != 'no-rescue':
+                msg = ('Node rescue interface is enabled, but %s class '
+                       'cannot test rescue operations.' % cls.__name__)
+                raise cls.skipException(msg)
+
     @staticmethod
     def _is_version_supported(version):
         """Return whether an API microversion is supported."""
@@ -193,7 +213,6 @@
         self.validate_ports()
         self.validate_scheduling()
         ip_address = self.get_server_ip(self.instance)
-        self.get_remote_client(ip_address).validate_authentication()
         vm_client = self.get_remote_client(ip_address)
 
         # We expect the ephemeral partition to be mounted on /mnt and to have
@@ -205,4 +224,15 @@
             self.create_timestamp(
                 ip_address, private_key=self.keypair['private_key'])
 
+        # Test rescue mode
+        if self.TEST_RESCUE_MODE:
+            self.rescue_instance(self.instance, self.node, ip_address)
+            self.unrescue_instance(self.instance, self.node, ip_address)
+
         self.terminate_instance(self.instance)
+
+
+class BaremetalBasicOpsAndRescue(BaremetalBasicOps):
+    """This test includes rescue/unrescue ops."""
+
+    TEST_RESCUE_MODE = True