Merge "Update the home-page url in setup.cfg"
diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py
index 8d10b00..c4fb185 100644
--- a/ironic_tempest_plugin/config.py
+++ b/ironic_tempest_plugin/config.py
@@ -120,12 +120,17 @@
     cfg.ListOpt('enabled_hardware_types',
                 default=['ipmi'],
                 help="List of Ironic enabled hardware types."),
+    cfg.ListOpt('enabled_bios_interfaces',
+                default=['fake'],
+                help="List of Ironic enabled bios interfaces."),
     cfg.ListOpt('enabled_deploy_interfaces',
                 default=['iscsi', 'direct'],
                 help="List of Ironic enabled deploy interfaces."),
     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/manager.py b/ironic_tempest_plugin/manager.py
index 9967a5d..b529e3c 100644
--- a/ironic_tempest_plugin/manager.py
+++ b/ironic_tempest_plugin/manager.py
@@ -81,12 +81,10 @@
         cls.security_group_rules_client = (
             cls.os_primary.security_group_rules_client)
 
-        if CONF.volume_feature_enabled.api_v2:
-            cls.volumes_client = cls.os_primary.volumes_v2_client
-            cls.snapshots_client = cls.os_primary.snapshots_v2_client
-        else:
-            cls.volumes_client = cls.os_primary.volumes_client
-            cls.snapshots_client = cls.os_primary.snapshots_client
+        if (CONF.volume_feature_enabled.api_v2 or
+            CONF.volume_feature_enabled.api_v3):
+            cls.volumes_client = cls.os_primary.volumes_client_latest
+            cls.snapshots_client = cls.os_primary.snapshots_client_latest
 
     # ## Test functions library
     #
diff --git a/ironic_tempest_plugin/services/baremetal/base.py b/ironic_tempest_plugin/services/baremetal/base.py
index c588a70..46c0a2f 100644
--- a/ironic_tempest_plugin/services/baremetal/base.py
+++ b/ironic_tempest_plugin/services/baremetal/base.py
@@ -85,7 +85,8 @@
 
         return json.loads(object_str)
 
-    def _get_uri(self, resource_name, uuid=None, permanent=False):
+    def _get_uri(self, resource_name, uuid=None, permanent=False,
+                 params=None):
         """Get URI for a specific resource or object.
 
         :param resource_name: The name of the REST resource, e.g., 'nodes'.
@@ -94,10 +95,15 @@
 
         """
         prefix = self.uri_prefix if not permanent else ''
+        if params:
+            params = '?' + '&'.join('%s=%s' % tpl for tpl in params.items())
+        else:
+            params = ''
 
-        return '{pref}/{res}{uuid}'.format(pref=prefix,
-                                           res=resource_name,
-                                           uuid='/%s' % uuid if uuid else '')
+        return '{pref}/{res}{uuid}{params}'.format(
+            pref=prefix, res=resource_name,
+            uuid='/%s' % uuid if uuid else '',
+            params=params)
 
     def _make_patch(self, allowed_attributes, **kwargs):
         """Create a JSON patch according to RFC 6902.
@@ -212,30 +218,34 @@
 
         return resp
 
-    def _delete_request(self, resource, uuid):
+    def _delete_request(self, resource, uuid,
+                        expected_status=http_client.NO_CONTENT):
         """Delete specified object.
 
         :param resource: The name of the REST resource, e.g., 'nodes'.
         :param uuid: The unique identifier of an object in UUID format.
+        :param expected_status: Expected response status code. By default is
+                                http_client.NO_CONTENT (204)
         :returns: A tuple with the server response and the response body.
 
         """
         uri = self._get_uri(resource, uuid)
 
         resp, body = self.delete(uri)
-        self.expected_success(http_client.NO_CONTENT, resp.status)
+        self.expected_success(expected_status, resp.status)
         return resp, body
 
-    def _patch_request(self, resource, uuid, patch_object):
+    def _patch_request(self, resource, uuid, patch_object, params=None):
         """Update specified object with JSON-patch.
 
         :param resource: The name of the REST resource, e.g., 'nodes'.
         :param uuid: The unique identifier of an object in UUID format.
+        :param params: query parameters to pass.
         :returns: A tuple with the server response and the serialized patched
                  object.
 
         """
-        uri = self._get_uri(resource, uuid)
+        uri = self._get_uri(resource, uuid, params=params)
         patch_body = json.dumps(patch_object)
 
         resp, body = self.patch(uri, body=patch_body)
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 11fdebf..b31b433 100644
--- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py
+++ b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py
@@ -89,6 +89,11 @@
         return self._list_request('/nodes/%s/states' % uuid)
 
     @base.handle_errors
+    def list_node_bios_settings(self, uuid):
+        """List node bios settings."""
+        return self._list_request('/nodes/%s/bios' % uuid)
+
+    @base.handle_errors
     def list_ports_detail(self, **kwargs):
         """Details list all existing ports."""
         return self._list_request('/ports/detail', **kwargs)
@@ -405,11 +410,17 @@
         :return: A tuple with the server response and the updated node.
 
         """
+        if 'reset_interfaces' in kwargs:
+            params = {'reset_interfaces': str(kwargs.pop('reset_interfaces'))}
+        else:
+            params = {}
+
         node_attributes = ('properties/cpu_arch',
                            'properties/cpus',
                            'properties/local_gb',
                            'properties/memory_mb',
                            'driver',
+                           'bios_interface',
                            'deploy_interface',
                            'rescue_interface',
                            'instance_uuid',
@@ -417,7 +428,7 @@
         if not patch:
             patch = self._make_patch(node_attributes, **kwargs)
 
-        return self._patch_request('nodes', uuid, patch)
+        return self._patch_request('nodes', uuid, patch, params=params)
 
     @base.handle_errors
     def update_chassis(self, uuid, **kwargs):
diff --git a/ironic_tempest_plugin/tests/api/admin/base.py b/ironic_tempest_plugin/tests/api/admin/base.py
index c589461..8850c55 100644
--- a/ironic_tempest_plugin/tests/api/admin/base.py
+++ b/ironic_tempest_plugin/tests/api/admin/base.py
@@ -162,12 +162,12 @@
 
     @classmethod
     @creates('node')
-    def create_node(cls, chassis_id, cpu_arch='x86', cpus=8, local_gb=10,
+    def create_node(cls, chassis_id, cpu_arch='x86_64', cpus=8, local_gb=10,
                     memory_mb=4096, resource_class=None):
         """Wrapper utility for creating test baremetal nodes.
 
         :param chassis_id: The unique identifier of the chassis.
-        :param cpu_arch: CPU architecture of the node. Default: x86.
+        :param cpu_arch: CPU architecture of the node. Default: x86_64.
         :param cpus: Number of CPUs. Default: 8.
         :param local_gb: Disk size. Default: 10.
         :param memory_mb: Available RAM. Default: 4096.
diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodes.py b/ironic_tempest_plugin/tests/api/admin/test_nodes.py
index 75a17ec..8c1343f 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_nodes.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_nodes.py
@@ -111,7 +111,7 @@
 
         _, node = self.create_node(self.chassis['uuid'], **props)
 
-        new_p = {'cpu_arch': 'x86',
+        new_p = {'cpu_arch': 'arm64',
                  'cpus': '1',
                  'local_gb': '10000',
                  'memory_mb': '12300'}
@@ -165,6 +165,11 @@
         self.assertEqual(1, len(body['nodes']))
         self.assertIn(self.node['uuid'], [n['uuid'] for n in body['nodes']])
 
+    @decorators.idempotent_id('b85af8c6-572b-4f20-815e-1cf31844b9f6')
+    def test_fault_hidden(self):
+        _, loaded_node = self.client.show_node(self.node['uuid'])
+        self.assertNotIn('fault', loaded_node)
+
 
 class TestNodesResourceClass(base.BaseBaremetalTest):
 
@@ -404,6 +409,96 @@
         self.client.vif_detach(self.node['uuid'], self.nport_id)
 
 
+class TestHardwareInterfaces(base.BaseBaremetalTest):
+
+    min_microversion = '1.31'
+    # Subclasses can override this with more interfaces available in later API
+    # versions.
+    hardware_interfaces = [
+        'boot',
+        'console',
+        'deploy',
+        'inspect',
+        'management',
+        'power',
+        'raid',
+        'vendor',
+    ]
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestHardwareInterfaces, cls).skip_checks()
+        if CONF.baremetal.driver != 'fake-hardware':
+            raise cls.skipException('These tests rely on fake-hardware')
+
+    @property
+    def optional_interfaces(self):
+        return set(self.hardware_interfaces) - {'boot', 'deploy',
+                                                'management', 'power'}
+
+    def setUp(self):
+        super(TestHardwareInterfaces, self).setUp()
+
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'])
+
+        # Reset optional interfaces to non-default values
+        for iface in self.optional_interfaces:
+            self.client.update_node(self.node['uuid'],
+                                    [{'path': '/%s_interface' % iface,
+                                      'op': 'add',
+                                      'value': 'no-%s' % iface}])
+
+    def test_set_interfaces(self):
+        for iface in self.hardware_interfaces:
+            field = '%s_interface' % iface
+            self.client.update_node(self.node['uuid'],
+                                    [{'path': '/%s' % field,
+                                      'op': 'add',
+                                      'value': 'fake'}])
+            _, node = self.client.show_node(self.node['uuid'])
+            self.assertEqual('fake', node[field])
+
+    def test_reset_interfaces(self):
+        for iface in self.hardware_interfaces:
+            field = '%s_interface' % iface
+            self.client.update_node(self.node['uuid'],
+                                    [{'path': '/%s' % field,
+                                      'op': 'remove'}])
+            _, node = self.client.show_node(self.node['uuid'])
+            self.assertEqual('fake', node[field])
+
+
+class TestResetInterfaces(TestHardwareInterfaces):
+
+    min_microversion = '1.45'
+
+    @classmethod
+    def skip_checks(cls):
+        super(TestResetInterfaces, cls).skip_checks()
+        if 'ipmi' not in CONF.baremetal.enabled_hardware_types:
+            raise cls.skipException('These tests rely on ipmi enabled')
+
+    def test_no_reset_by_default(self):
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.client.update_node,
+            self.node['uuid'],
+            [{'path': '/driver', 'value': 'ipmi', 'op': 'replace'}])
+        _, node = self.client.show_node(self.node['uuid'])
+        self.assertEqual('fake-hardware', node['driver'])
+
+    def test_reset_all_interfaces(self):
+        self.client.update_node(self.node['uuid'],
+                                [{'path': '/driver',
+                                  'value': 'ipmi',
+                                  'op': 'replace'}],
+                                reset_interfaces=True)
+        _, node = self.client.show_node(self.node['uuid'])
+        for iface in self.hardware_interfaces:
+            self.assertNotEqual('fake', node['%s_interface' % iface])
+
+
 class TestNodesTraits(base.BaseBaremetalTest):
 
     min_microversion = '1.37'
@@ -723,3 +818,35 @@
         """Show a node, ensure it has no traits."""
         _, body = self.client.show_node(self.node['uuid'])
         self.assertNotIn('traits', body)
+
+
+class TestNodeFault(base.BaseBaremetalTest):
+    """Tests for fault of baremetal nodes."""
+
+    min_microversion = '1.42'
+
+    def setUp(self):
+        super(TestNodeFault, self).setUp()
+
+        _, self.chassis = self.create_chassis()
+        _, self.node = self.create_node(self.chassis['uuid'])
+
+    @decorators.idempotent_id('649b4660-4f76-4d67-94df-6631a2cb2cd9')
+    def test_fault_shown(self):
+        _, loaded_node = self.client.show_node(self.node['uuid'])
+        self.assertIn('fault', loaded_node)
+
+    @decorators.idempotent_id('62f453be-8f30-4cfe-a19a-23656068e546')
+    def test_list_nodes_fault(self):
+        _, body = self.client.list_nodes()
+        self.assertIn(self.node['uuid'], [n['uuid'] for n in body['nodes']])
+
+        _, body = self.client.list_nodes(fault='power failure')
+        self.assertNotIn(self.node['uuid'],
+                         [n['uuid'] for n in body['nodes']])
+
+    @decorators.idempotent_id('c8fb55f1-873f-4fb9-bd57-6f1de0479873')
+    def test_list_nodes_with_invalid_fault(self):
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.client.list_nodes, fault='somefake')
diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodestates.py b/ironic_tempest_plugin/tests/api/admin/test_nodestates.py
index f2a33eb..f184b8b 100644
--- a/ironic_tempest_plugin/tests/api/admin/test_nodestates.py
+++ b/ironic_tempest_plugin/tests/api/admin/test_nodestates.py
@@ -21,7 +21,7 @@
 
 
 class TestNodeStatesMixin(object):
-    """Mixin for for baremetal node states tests."""
+    """Mixin for baremetal node states tests."""
 
     @classmethod
     def resource_setup(cls):
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/baremetal_standalone_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py
index 9eb5d7f..c7d6b50 100644
--- a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py
+++ b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py
@@ -35,6 +35,9 @@
     # attach VIF to a node.
     min_microversion = '1.28'
 
+    image_ref = None
+    image_checksum = None
+
     @classmethod
     def skip_checks(cls):
         """Defines conditions to skip these tests."""
@@ -218,30 +221,28 @@
         return nodes[0]
 
     @classmethod
-    def boot_node(cls, driver, image_ref, image_checksum=None, **interfaces):
+    def boot_node(cls, image_ref=None, image_checksum=None):
         """Boot ironic node.
 
         The following actions are executed:
-          * Randomly pick an available node for deployment and reserve it.
-          * Update node driver.
           * Create/Pick networks to boot node in.
           * Create Neutron port and attach it to node.
           * Update node image_source/root_gb.
           * Deploy node.
           * Wait until node is deployed.
 
-        :param driver: Node driver to use.
         :param image_ref: Reference to user image to boot node with.
         :param image_checksum: md5sum of image specified in image_ref.
                                Needed only when direct HTTP link is provided.
-        :param interfaces: driver interfaces to set on the node
-        :returns: Ironic node.
         """
-        node = cls.get_and_reserve_node()
-        cls.update_node_driver(node['uuid'], driver, **interfaces)
+        if image_ref is None:
+            image_ref = cls.image_ref
+        if image_checksum is None:
+            image_checksum = cls.image_checksum
+
         network, subnet, router = cls.create_networks()
         n_port = cls.create_neutron_port(network_id=network['id'])
-        cls.vif_attach(node_id=node['uuid'], vif_id=n_port['id'])
+        cls.vif_attach(node_id=cls.node['uuid'], vif_id=n_port['id'])
         patch = [{'path': '/instance_info/image_source',
                   'op': 'add',
                   'value': image_ref}]
@@ -253,14 +254,14 @@
                       'op': 'add',
                       'value': CONF.baremetal.adjusted_root_disk_size_gb})
         # TODO(vsaienko) add testing for custom configdrive
-        cls.update_node(node['uuid'], patch=patch)
-        cls.set_node_provision_state(node['uuid'], 'active')
-        cls.wait_power_state(node['uuid'], bm.BaremetalPowerStates.POWER_ON)
-        cls.wait_provisioning_state(node['uuid'],
+        cls.update_node(cls.node['uuid'], patch=patch)
+        cls.set_node_provision_state(cls.node['uuid'], 'active')
+        cls.wait_power_state(cls.node['uuid'],
+                             bm.BaremetalPowerStates.POWER_ON)
+        cls.wait_provisioning_state(cls.node['uuid'],
                                     bm.BaremetalProvisionStates.ACTIVE,
                                     timeout=CONF.baremetal.active_timeout,
                                     interval=30)
-        return node
 
     @classmethod
     def terminate_node(cls, node_id):
@@ -274,15 +275,18 @@
         :param node_id: Name or UUID for the node.
         """
         cls.detach_all_vifs_from_node(node_id)
-        cls.set_node_provision_state(node_id, 'deleted')
-        # NOTE(vsaienko) We expect here fast switching from deleted to
-        # available as automated cleaning is disabled so poll status each 1s.
-        cls.wait_provisioning_state(
-            node_id,
-            [bm.BaremetalProvisionStates.NOSTATE,
-             bm.BaremetalProvisionStates.AVAILABLE],
-            timeout=CONF.baremetal.unprovision_timeout,
-            interval=1)
+
+        if cls.delete_node:
+            cls.set_node_provision_state(node_id, 'deleted')
+            # NOTE(vsaienko) We expect here fast switching from deleted to
+            # available as automated cleaning is disabled so poll status
+            # each 1s.
+            cls.wait_provisioning_state(
+                node_id,
+                [bm.BaremetalProvisionStates.NOSTATE,
+                 bm.BaremetalProvisionStates.AVAILABLE],
+                timeout=CONF.baremetal.unprovision_timeout,
+                interval=1)
 
     @classmethod
     def rescue_node(cls, node_id, rescue_password):
@@ -305,6 +309,88 @@
             timeout=CONF.baremetal.unrescue_timeout,
             interval=1)
 
+    def manual_cleaning(self, node, clean_steps):
+        """Performs manual cleaning.
+
+        The following actions are executed:
+           * Expects node to be in available state.
+           * Brings the node to manageable state.
+           * Do manual cleaning.
+           * Brings the node back to original available.
+
+        :param node: Ironic node to associate instance_uuid with.
+        :param clean_steps: clean steps for manual cleaning.
+        """
+        self.set_node_provision_state(node['uuid'], 'manage')
+        self.wait_provisioning_state(
+            node['uuid'],
+            [bm.BaremetalProvisionStates.MANAGEABLE],
+            timeout=CONF.baremetal.unprovision_timeout,
+            interval=30)
+        self.set_node_provision_state(
+            node['uuid'], 'clean', clean_steps=clean_steps)
+        self.wait_provisioning_state(
+            node['uuid'],
+            [bm.BaremetalProvisionStates.MANAGEABLE],
+            timeout=CONF.baremetal.unprovision_timeout,
+            interval=30)
+        self.set_node_provision_state(node['uuid'], 'provide')
+        self.wait_provisioning_state(
+            node['uuid'],
+            [bm.BaremetalProvisionStates.NOSTATE,
+             bm.BaremetalProvisionStates.AVAILABLE],
+            timeout=CONF.baremetal.unprovision_timeout,
+            interval=30)
+
+    def check_manual_partition_cleaning(self, node):
+        """Tests the cleanup step for erasing devices metadata.
+
+        :param node: Ironic node to associate instance_uuid with, it is
+            expected to be in 'active' state
+        """
+        clean_steps = [
+            {
+                "interface": "deploy",
+                "step": "erase_devices_metadata"
+            }
+        ]
+        self.manual_cleaning(node, clean_steps=clean_steps)
+        # TODO(yolanda): we currently are not checking it the cleanup
+        # was actually removing the metadata, because there was not a good
+        # way to achieve that check for vms and baremetal
+
+    def check_bios_apply_and_reset_configuration(self, node, settings):
+        clean_steps = [
+            {
+                "interface": "bios",
+                "step": "apply_configuration",
+                "args": {"settings": settings}
+            }
+        ]
+        self.manual_cleaning(node, clean_steps=clean_steps)
+
+        # query the api to check node bios settings
+        _, bios_settings = self.baremetal_client.list_node_bios_settings(
+            node['uuid'])
+
+        for setting in settings:
+            self.assertIn(setting['name'],
+                          [i['name'] for i in bios_settings['bios']])
+            self.assertIn(setting['value'],
+                          [i['value'] for i in bios_settings['bios']])
+
+        # reset bios and ensure that the settings are not there
+        clean_steps = [
+            {
+                "interface": "bios",
+                "step": "factory_reset"
+            }
+        ]
+        self.manual_cleaning(node, clean_steps=clean_steps)
+        _, bios_settings = self.baremetal_client.list_node_bios_settings(
+            node['uuid'])
+        self.assertEqual([], bios_settings['bios'])
+
 
 class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager):
 
@@ -314,6 +400,12 @@
     # The node driver to use in the test
     driver = None
 
+    # The bios interface to use by the HW type. The bios interface of the
+    # node used in the test will be set to this value. If set to None, the
+    # node will retain its existing bios_interface value (which may have been
+    # set via a different test).
+    bios_interface = None
+
     # The deploy interface to use by the HW type. The deploy interface of
     # the node used in the test will be set to this value. If set to None,
     # the node will retain its existing deploy_interface value (which may have
@@ -326,14 +418,11 @@
     # been set via a different test).
     rescue_interface = None
 
-    # User image ref to boot node with.
-    image_ref = None
-
     # Boolean value specify if image is wholedisk or not.
     wholedisk_image = None
 
-    # Image checksum, required when image is stored on HTTP server.
-    image_checksum = None
+    # If we need to set provision state 'deleted' for the node  after test
+    delete_node = True
 
     mandatory_attr = ['driver', 'image_ref', 'wholedisk_image']
 
@@ -353,6 +442,13 @@
                     'driver': cls.driver,
                     'enabled_drivers': CONF.baremetal.enabled_drivers,
                     'enabled_hw_types': CONF.baremetal.enabled_hardware_types})
+        if (cls.bios_interface and cls.bios_interface not in
+                CONF.baremetal.enabled_bios_interfaces):
+            raise cls.skipException(
+                "Bios interface %(iface)s required by the test is not in the "
+                "list of enabled bios interfaces %(enabled)s" % {
+                    'iface': cls.bios_interface,
+                    'enabled': CONF.baremetal.enabled_bios_interfaces})
         if (cls.deploy_interface and cls.deploy_interface not in
                 CONF.baremetal.enabled_deploy_interfaces):
             raise cls.skipException(
@@ -372,6 +468,18 @@
                 'Partitioned images are not supported with multitenancy.')
 
     @classmethod
+    def set_node_to_active(cls, image_ref=None, image_checksum=None):
+        cls.boot_node(image_ref, image_checksum)
+        if CONF.validation.connect_method == 'floating':
+            cls.node_ip = cls.add_floatingip_to_node(cls.node['uuid'])
+        elif CONF.validation.connect_method == 'fixed':
+            cls.node_ip = cls.get_server_ip(cls.node['uuid'])
+        else:
+            m = ('Configuration option "[validation]/connect_method" '
+                 'must be set.')
+            raise lib_exc.InvalidConfiguration(m)
+
+    @classmethod
     def resource_setup(cls):
         super(BaremetalStandaloneScenarioTest, cls).resource_setup()
         base.set_baremetal_api_microversion(cls.api_microversion)
@@ -383,29 +491,37 @@
         if not uuidutils.is_uuid_like(cls.image_ref):
             image_checksum = cls.image_checksum
         boot_kwargs = {'image_checksum': image_checksum}
+        if cls.bios_interface:
+            boot_kwargs['bios_interface'] = cls.bios_interface
         if cls.deploy_interface:
             boot_kwargs['deploy_interface'] = cls.deploy_interface
         if cls.rescue_interface:
             boot_kwargs['rescue_interface'] = cls.rescue_interface
-        cls.node = cls.boot_node(cls.driver, cls.image_ref, **boot_kwargs)
-        if CONF.validation.connect_method == 'floating':
-            cls.node_ip = cls.add_floatingip_to_node(cls.node['uuid'])
-        elif CONF.validation.connect_method == 'fixed':
-            cls.node_ip = cls.get_server_ip(cls.node['uuid'])
-        else:
-            m = ('Configuration option "[validation]/connect_method" '
-                 'must be set.')
-            raise lib_exc.InvalidConfiguration(m)
+
+        # just get an available node
+        cls.node = cls.get_and_reserve_node()
+        cls.update_node_driver(cls.node['uuid'], cls.driver, **boot_kwargs)
 
     @classmethod
     def resource_cleanup(cls):
         if CONF.validation.connect_method == 'floating':
-            cls.cleanup_floating_ip(cls.node_ip)
+            if cls.node_ip:
+                cls.cleanup_floating_ip(cls.node_ip)
+
         vifs = cls.get_node_vifs(cls.node['uuid'])
         # Remove ports before deleting node, to catch regression for cases
         # when user did this prior unprovision node.
         for vif in vifs:
-            cls.ports_client.delete_port(vif)
+            try:
+                cls.ports_client.delete_port(vif)
+            except lib_exc.NotFound:
+                pass
         cls.terminate_node(cls.node['uuid'])
         base.reset_baremetal_api_microversion()
         super(BaremetalStandaloneManager, cls).resource_cleanup()
+
+    def boot_and_verify_node(self, image_ref=None, image_checksum=None,
+                             should_succeed=True):
+        self.set_node_to_active(image_ref, image_checksum)
+        self.assertTrue(self.ping_ip_address(self.node_ip,
+                                             should_succeed=should_succeed))
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 f90f8ca..5aa7543 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
@@ -32,8 +32,7 @@
     @decorators.idempotent_id('defff515-a6ff-44f6-9d8d-2ded51196d98')
     @utils.services('image', 'network', 'object_storage')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalAgentIpmitoolWholediskHttpLink(
@@ -54,8 +53,7 @@
     @decorators.idempotent_id('d926c683-1a32-44df-afd0-e60134346fd0')
     @utils.services('network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalAgentIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest):
@@ -67,8 +65,7 @@
     @decorators.idempotent_id('27b86130-d8dc-419d-880a-fbbbe4ce3f8c')
     @utils.services('image', 'network', 'object_storage')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalPxeIpmitoolWholedisk(bsm.BaremetalStandaloneScenarioTest):
@@ -80,8 +77,7 @@
     @decorators.idempotent_id('d8c5badd-45db-4d05-bbe8-35babbed6e86')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalPxeIpmitoolWholediskHttpLink(
@@ -102,8 +98,7 @@
     @decorators.idempotent_id('71ccf06f-6765-40fd-8252-1b1bfa423b9b')
     @utils.services('network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalPxeIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest):
@@ -115,8 +110,7 @@
     @decorators.idempotent_id('ea85e19c-6869-4577-b9bb-2eb150f77c90')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalIpmiIscsiWholedisk(bsm.BaremetalStandaloneScenarioTest):
@@ -130,8 +124,7 @@
     @decorators.idempotent_id('f25b71df-2150-45d7-a780-7f5b07124808')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalIpmiDirectWholedisk(bsm.BaremetalStandaloneScenarioTest):
@@ -145,8 +138,7 @@
     @decorators.idempotent_id('c2db24e7-07dc-4a20-8f93-d4efae2bfd4e')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalIpmiIscsiPartitioned(bsm.BaremetalStandaloneScenarioTest):
@@ -160,8 +152,7 @@
     @decorators.idempotent_id('7d0b205e-edbc-4e2d-9f6d-95cd74eefecb')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalIpmiDirectPartitioned(bsm.BaremetalStandaloneScenarioTest):
@@ -175,8 +166,7 @@
     @decorators.idempotent_id('7b4b2dcd-2bbb-44f5-991f-0964300af6b7')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalIpmiAnsibleWholedisk(bsm.BaremetalStandaloneScenarioTest):
@@ -190,8 +180,7 @@
     @decorators.idempotent_id('cde532cc-81ba-4489-b374-b4a85cc203eb')
     @utils.services('image', 'network')
     def test_ip_access_to_server(self):
-        self.assertTrue(self.ping_ip_address(self.node_ip,
-                                             should_succeed=True))
+        self.boot_and_verify_node()
 
 
 class BaremetalIpmiRescueWholedisk(bsm.BaremetalStandaloneScenarioTest):
@@ -212,6 +201,7 @@
     @decorators.idempotent_id('d6a1780f-c4bb-4136-8144-29e822e14d66')
     @utils.services('image', 'network')
     def test_rescue_mode(self):
+        self.set_node_to_active(self.image_ref)
         self.rescue_node(self.node['uuid'], 'abc123')
         self.assertTrue(self.ping_ip_address(self.node_ip,
                                              should_succeed=True))
@@ -239,6 +229,7 @@
     @decorators.idempotent_id('113acd0a-9872-4631-b3ee-54da7e3bb262')
     @utils.services('image', 'network')
     def test_rescue_mode(self):
+        self.set_node_to_active(self.image_ref)
         self.rescue_node(self.node['uuid'], 'abc123')
         self.assertTrue(self.ping_ip_address(self.node_ip,
                                              should_succeed=True))
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py
new file mode 100644
index 0000000..be92c16
--- /dev/null
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_bios.py
@@ -0,0 +1,51 @@
+#
+# Copyright 2018 Red Hat Inc.
+#
+# 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 oslo_log import log as logging
+from tempest import config
+from tempest.lib import decorators
+
+from ironic_tempest_plugin.tests.scenario import \
+    baremetal_standalone_manager as bsm
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class BaremetalFakeBios(
+        bsm.BaremetalStandaloneScenarioTest):
+
+    driver = 'fake-hardware'
+    bios_interface = 'fake'
+    deploy_interface = 'iscsi'
+    image_ref = CONF.baremetal.whole_disk_image_ref
+    wholedisk_image = True
+    delete_node = False
+    api_microversion = '1.40'
+
+    @decorators.idempotent_id('ef55c44a-cc10-4cf6-8fda-85f0c0793150')
+    def test_bios_apply_and_reset_configuration(self):
+        settings = [
+            {
+                "name": "setting1_name",
+                "value": "setting1_value"
+            },
+            {
+                "name": "setting2_name",
+                "value": "setting2_value"
+            }
+        ]
+
+        self.check_bios_apply_and_reset_configuration(self.node, settings)
diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py
new file mode 100644
index 0000000..e30b9ef
--- /dev/null
+++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_cleaning.py
@@ -0,0 +1,71 @@
+#
+# Copyright 2017 Mirantis Inc.
+#
+# 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 oslo_log import log as logging
+from tempest.common import utils
+from tempest import config
+from tempest.lib import decorators
+
+from ironic_tempest_plugin.tests.scenario import \
+    baremetal_standalone_manager as bsm
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+
+class BaremetalCleaningAgentIpmitoolWholedisk(
+        bsm.BaremetalStandaloneScenarioTest):
+
+    driver = 'agent_ipmitool'
+    image_ref = CONF.baremetal.whole_disk_image_ref
+    wholedisk_image = True
+    delete_node = False
+    api_microversion = '1.28'
+
+    @decorators.idempotent_id('0d82cedd-9697-4cf7-8e4a-80d510f53615')
+    @utils.services('image', 'network')
+    def test_manual_cleaning(self):
+        self.check_manual_partition_cleaning(self.node)
+
+
+class BaremetalCleaningPxeIpmitoolWholedisk(
+        bsm.BaremetalStandaloneScenarioTest):
+
+    driver = 'pxe_ipmitool'
+    image_ref = CONF.baremetal.whole_disk_image_ref
+    wholedisk_image = True
+    delete_node = False
+    api_microversion = '1.28'
+
+    @decorators.idempotent_id('fb03abfa-cdfc-41ec-aaa8-c70402786a85')
+    @utils.services('image', 'network')
+    def test_manual_cleaning(self):
+        self.check_manual_partition_cleaning(self.node)
+
+
+class BaremetalCleaningIpmiWholedisk(
+        bsm.BaremetalStandaloneScenarioTest):
+
+    driver = 'ipmi'
+    image_ref = CONF.baremetal.whole_disk_image_ref
+    wholedisk_image = True
+    delete_node = False
+    deploy_interface = 'iscsi'
+    api_microversion = '1.31'
+
+    @decorators.idempotent_id('065238db-1b6d-4d75-a9da-c240f8cbd956')
+    @utils.services('image', 'network')
+    def test_manual_cleaning(self):
+        self.check_manual_partition_cleaning(self.node)
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
diff --git a/tox.ini b/tox.ini
index 5a4d525..6b0e1ae 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 minversion = 2.0
-envlist = py34,py27,pypy,pep8
+envlist = py3,py27,pep8
 skipsdist = True
 
 [testenv]
@@ -13,18 +13,23 @@
 commands = python setup.py test --slowest --testr-args='{posargs}'
 
 [testenv:pep8]
+basepython = python3
 commands = flake8 {posargs}
 
 [testenv:venv]
+basepython = python3
 commands = {posargs}
 
 [testenv:cover]
+basepython = python3
 commands = python setup.py test --coverage --testr-args='{posargs}'
 
 [testenv:docs]
+basepython = python3
 commands = python setup.py build_sphinx
 
 [testenv:debug]
+basepython = python3
 commands = oslo_debug_helper {posargs}
 
 [flake8]