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]