From e48375538c11a988e66fd9eadc944e82cb7f4b57 Mon Sep 17 00:00:00 2001 From: Vasyl Saienko Date: Fri, 20 Jan 2017 16:26:04 +0000 Subject: [PATCH] Add Ironic standalone tests This patch adds the following standalone tests: * agent_ipmitool + wholedisk image + bios * agent_ipmitool + partitioned image + bios * pxe_ipmitool + wholedisk image + bios * pxe_ipmitool + partitioned image + bios Partial-Bug: #1660606 Change-Id: Ic04b0f134e20d9937a610a14d7c4128f45738eeb --- devstack/files/debs/ironic | 1 + devstack/files/rpms/ironic | 1 + devstack/lib/ironic | 25 +- ironic_tempest_plugin/config.py | 16 +- .../services/baremetal/base.py | 12 + .../baremetal/v1/json/baremetal_client.py | 22 +- .../api/admin/api_microversion_fixture.py | 7 +- .../tests/scenario/baremetal_manager.py | 62 +++- .../scenario/baremetal_standalone_manager.py | 324 ++++++++++++++++++ .../scenario/ironic_standalone/__init__.py | 0 .../ironic_standalone/test_basic_ops.py | 70 ++++ 11 files changed, 520 insertions(+), 20 deletions(-) create mode 100644 ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py create mode 100644 ironic_tempest_plugin/tests/scenario/ironic_standalone/__init__.py create mode 100644 ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py diff --git a/devstack/files/debs/ironic b/devstack/files/debs/ironic index 141b75ff70..8bb5deaa8e 100644 --- a/devstack/files/debs/ironic +++ b/devstack/files/debs/ironic @@ -17,6 +17,7 @@ iptables ipxe gnupg libguestfs0 +libguestfs-tools libvirt-bin open-iscsi openssh-client diff --git a/devstack/files/rpms/ironic b/devstack/files/rpms/ironic index ed66746d07..ac22dd4e97 100644 --- a/devstack/files/rpms/ironic +++ b/devstack/files/rpms/ironic @@ -4,6 +4,7 @@ iptables ipxe-bootimgs gnupg libguestfs +libguestfs-tools libvirt libvirt-python net-tools diff --git a/devstack/lib/ironic b/devstack/lib/ironic index 11754d4d1d..2dc1e9992d 100644 --- a/devstack/lib/ironic +++ b/devstack/lib/ironic @@ -567,10 +567,14 @@ else add_image_link http://download.cirros-cloud.net/${CIRROS_VERSION}/cirros-${CIRROS_VERSION}-x86_64-disk.img fi + +IRONIC_WHOLEDISK_IMAGE_NAME=${IRONIC_WHOLEDISK_IMAGE_NAME:-${IRONIC_IMAGE_NAME/-uec/-disk}} +IRONIC_PARTITIONED_IMAGE_NAME=${IRONIC_PARTITIONED_IMAGE_NAME:-${IRONIC_IMAGE_NAME/-disk/-uec}} + if [[ "$IRONIC_TEMPEST_WHOLE_DISK_IMAGE" == "True" ]]; then - IRONIC_IMAGE_NAME=${IRONIC_IMAGE_NAME/-uec/-disk} + IRONIC_IMAGE_NAME=$IRONIC_WHOLEDISK_IMAGE_NAME else - IRONIC_IMAGE_NAME=${IRONIC_IMAGE_NAME/-disk/-uec} + IRONIC_IMAGE_NAME=$IRONIC_PARTITIONED_IMAGE_NAME fi # NOTE(vsaienko) set DEFAULT_IMAGE_NAME here, as it is still used by grenade @@ -656,7 +660,7 @@ function install_ironic { if [[ "$HOST_TOPOLOGY_ROLE" != "subnode" ]]; then # make sure all needed service were enabled local req_services="key" - if [[ "$VIRT_DRIVER" == "ironic" ]]; then + if is_service_enabled nova && [[ "$VIRT_DRIVER" == "ironic" ]]; then req_services+=" nova glance neutron" fi for srv in $req_services; do @@ -1996,6 +2000,21 @@ function ironic_configure_tempest { iniset $TEMPEST_CONFIG compute image_ref $image_uuid iniset $TEMPEST_CONFIG compute image_ref_alt $image_uuid + image_uuid=$(openstack image show $IRONIC_WHOLEDISK_IMAGE_NAME -f value -c id) + iniset $TEMPEST_CONFIG baremetal whole_disk_image_ref $image_uuid + image_uuid=$(openstack image show $IRONIC_PARTITIONED_IMAGE_NAME -f value -c id) + iniset $TEMPEST_CONFIG baremetal partition_image_ref $image_uuid + iniset $TEMPEST_CONFIG baremetal enabled_drivers $IRONIC_ENABLED_DRIVERS + iniset $TEMPEST_CONFIG baremetal enabled_hardware_types $IRONIC_ENABLED_HARDWARE_TYPES + + local adjusted_root_disk_size_gb + if [[ "$IRONIC_IS_HARDWARE" == "False" ]]; then + adjusted_root_disk_size_gb=$(( ${IRONIC_VM_SPECS_DISK} - ${IRONIC_VM_EPHEMERAL_DISK} )) + else + adjusted_root_disk_size_gb=$(( ${IRONIC_HW_NODE_DISK} - ${IRONIC_HW_EPHEMERAL_DISK} )) + fi + iniset $TEMPEST_CONFIG baremetal adjusted_root_disk_size_gb $adjusted_root_disk_size_gb + if [[ -n "${IRONIC_TEMPEST_BUILD_TIMEOUT}" ]]; then iniset $TEMPEST_CONFIG baremetal unprovision_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT iniset $TEMPEST_CONFIG baremetal active_timeout $IRONIC_TEMPEST_BUILD_TIMEOUT diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py index 60ee1137a1..a7afcd6d72 100644 --- a/ironic_tempest_plugin/config.py +++ b/ironic_tempest_plugin/config.py @@ -83,5 +83,19 @@ BaremetalGroup = [ "require a microversion."), cfg.BoolOpt('use_provision_network', default=False, - help="Whether the Ironic/Neutron tenant isolation is enabled") + help="Whether the Ironic/Neutron tenant isolation is enabled"), + cfg.StrOpt('whole_disk_image_ref', + help="UUID of the wholedisk image to use in the tests."), + cfg.StrOpt('partition_image_ref', + help="UUID of the partitioned image to use in the tests."), + cfg.ListOpt('enabled_drivers', + default=['fake', 'pxe_ipmitool', 'agent_ipmitool'], + help="List of Ironic enabled drivers."), + cfg.ListOpt('enabled_hardware_types', + default=['ipmi'], + help="List of Ironic enabled hardware types."), + cfg.IntOpt('adjusted_root_disk_size_gb', + min=0, + help="Ironic adjusted disk size to use in the standalone tests " + "as instance_info/root_gb value."), ] diff --git a/ironic_tempest_plugin/services/baremetal/base.py b/ironic_tempest_plugin/services/baremetal/base.py index ec572b24f8..bb9ba9e0a7 100644 --- a/ironic_tempest_plugin/services/baremetal/base.py +++ b/ironic_tempest_plugin/services/baremetal/base.py @@ -18,9 +18,21 @@ from six.moves.urllib import parse as urllib from tempest.lib.common import api_version_utils from tempest.lib.common import rest_client +# NOTE(vsaienko): concurrent tests work because they are launched in +# separate processes so global variables are not shared among them. BAREMETAL_MICROVERSION = None +def set_baremetal_api_microversion(baremetal_microversion): + global BAREMETAL_MICROVERSION + BAREMETAL_MICROVERSION = baremetal_microversion + + +def reset_baremetal_api_microversion(): + global BAREMETAL_MICROVERSION + BAREMETAL_MICROVERSION = None + + def handle_errors(f): """A decorator that allows to ignore certain types of errors.""" 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 dc4cdc15bd..559e21d4e5 100644 --- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py +++ b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py @@ -215,10 +215,14 @@ class BaremetalClient(base.BaremetalClient): return self._delete_request('ports', uuid) @base.handle_errors - def update_node(self, uuid, **kwargs): + def update_node(self, uuid, patch=None, **kwargs): """Update the specified node. :param uuid: The unique identifier of the node. + :param patch: A JSON path that sets values of the specified attributes + to the new ones. + :param **kwargs: Attributes and new values for them, used only when + patch param is not set. :return: A tuple with the server response and the updated node. """ @@ -228,8 +232,8 @@ class BaremetalClient(base.BaremetalClient): 'properties/memory_mb', 'driver', 'instance_uuid') - - patch = self._make_patch(node_attributes, **kwargs) + if not patch: + patch = self._make_patch(node_attributes, **kwargs) return self._patch_request('nodes', uuid, patch) @@ -271,7 +275,8 @@ class BaremetalClient(base.BaremetalClient): target) @base.handle_errors - def set_node_provision_state(self, node_uuid, state, configdrive=None): + def set_node_provision_state(self, node_uuid, state, configdrive=None, + clean_steps=None): """Set provision state of the specified node. :param node_uuid: The unique identifier of the node. @@ -279,8 +284,15 @@ class BaremetalClient(base.BaremetalClient): (active/rebuild/deleted/inspect/manage/provide). :param configdrive: A gzipped, base64-encoded configuration drive string. + :param clean_steps: A list with clean steps to execute. """ - data = {'target': state, 'configdrive': configdrive} + data = {'target': state} + # NOTE (vsaienk0): Add both here if specified, do not check anything. + # API will return an error in case of invalid parameters. + if configdrive is not None: + data['configdrive'] = configdrive + if clean_steps is not None: + data['clean_steps'] = clean_steps return self._put_request('nodes/%s/states/provision' % node_uuid, data) diff --git a/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py b/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py index 9dd643c41b..ff7e09a8a4 100644 --- a/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py +++ b/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py @@ -22,8 +22,5 @@ class APIMicroversionFixture(fixtures.Fixture): def _setUp(self): super(APIMicroversionFixture, self)._setUp() - base.BAREMETAL_MICROVERSION = self.baremetal_microversion - self.addCleanup(self._reset_compute_microversion) - - def _reset_compute_microversion(self): - base.BAREMETAL_MICROVERSION = None + base.set_baremetal_api_microversion(self.baremetal_microversion) + self.addCleanup(base.reset_baremetal_api_microversion) diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py index a629364d84..18cf6f1897 100644 --- a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py +++ b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py @@ -14,8 +14,12 @@ # License for the specific language governing permissions and limitations # under the License. +import time + from tempest.common import waiters from tempest import config +from tempest.lib.common import api_version_utils +from tempest.lib import exceptions as lib_exc from tempest.scenario import manager # noqa from ironic_tempest_plugin import clients @@ -25,6 +29,21 @@ from ironic_tempest_plugin.common import waiters as ironic_waiters CONF = config.CONF +def retry_on_conflict(func): + def inner(*args, **kwargs): + # TODO(vsaienko): make number of retries and delay between + # them configurable in future. + e = None + for att in range(10): + try: + return func(*args, **kwargs) + except lib_exc.Conflict as e: + time.sleep(1) + raise lib_exc.Conflict(e) + + return inner + + # power/provision states as of icehouse class BaremetalPowerStates(object): """Possible power states of an Ironic node.""" @@ -49,17 +68,26 @@ class BaremetalProvisionStates(object): DELETING = 'deleting' DELETED = 'deleted' ERROR = 'error' + MANAGEABLE = 'manageable' class BaremetalScenarioTest(manager.ScenarioTest): credentials = ['primary', 'admin'] + min_microversion = None + max_microversion = api_version_utils.LATEST_MICROVERSION @classmethod def skip_checks(cls): super(BaremetalScenarioTest, cls).skip_checks() if not CONF.service_available.ironic: raise cls.skipException('Ironic is not enabled.') + cfg_min_version = CONF.baremetal.min_microversion + cfg_max_version = CONF.baremetal.max_microversion + api_version_utils.check_skip_with_microversion(cls.min_microversion, + cls.max_microversion, + cfg_min_version, + cfg_max_version) @classmethod def setup_clients(cls): @@ -73,14 +101,16 @@ class BaremetalScenarioTest(manager.ScenarioTest): # allow any issues obtaining the node list to raise early cls.baremetal_client.list_nodes() - def wait_provisioning_state(self, node_id, state, timeout=10, interval=1): + @classmethod + def wait_provisioning_state(cls, node_id, state, timeout=10, interval=1): ironic_waiters.wait_for_bm_node_status( - self.baremetal_client, node_id=node_id, attr='provision_state', + cls.baremetal_client, node_id=node_id, attr='provision_state', status=state, timeout=timeout, interval=interval) - def wait_power_state(self, node_id, state): + @classmethod + def wait_power_state(cls, node_id, state): ironic_waiters.wait_for_bm_node_status( - self.baremetal_client, node_id=node_id, attr='power_state', + cls.baremetal_client, node_id=node_id, attr='power_state', status=state, timeout=CONF.baremetal.power_timeout) def wait_node(self, instance_id): @@ -88,8 +118,9 @@ class BaremetalScenarioTest(manager.ScenarioTest): ironic_waiters.wait_node_instance_association(self.baremetal_client, instance_id) - def get_node(self, node_id=None, instance_id=None): - return utils.get_node(self.baremetal_client, node_id, instance_id) + @classmethod + def get_node(cls, node_id=None, instance_id=None): + return utils.get_node(cls.baremetal_client, node_id, instance_id) def get_ports(self, node_uuid): ports = [] @@ -107,6 +138,25 @@ class BaremetalScenarioTest(manager.ScenarioTest): def add_keypair(self): self.keypair = self.create_keypair() + @classmethod + @retry_on_conflict + def update_node_driver(cls, node_id, driver): + _, body = cls.baremetal_client.update_node( + node_id, driver=driver) + return body + + @classmethod + @retry_on_conflict + def update_node(cls, node_id, patch): + cls.baremetal_client.update_node(node_id, patch=patch) + + @classmethod + @retry_on_conflict + def set_node_provision_state(cls, node_id, state, configdrive=None, + clean_steps=None): + cls.baremetal_client.set_node_provision_state( + node_id, state, configdrive=configdrive, clean_steps=clean_steps) + def verify_connectivity(self, ip=None): if ip: dest = self.get_remote_client(ip) diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py new file mode 100644 index 0000000000..8415a48001 --- /dev/null +++ b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py @@ -0,0 +1,324 @@ +# +# 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. + +import random + +from oslo_utils import uuidutils +from tempest import config +from tempest.lib.common.utils import test_utils +from tempest.lib import exceptions as lib_exc +from tempest.scenario import manager + +from ironic_tempest_plugin.services.baremetal import base +from ironic_tempest_plugin.tests.scenario import baremetal_manager as bm + +CONF = config.CONF + + +class BaremetalStandaloneManager(bm.BaremetalScenarioTest, + manager.NetworkScenarioTest): + + credentials = ['primary', 'admin'] + # NOTE(vsaienko): Standalone tests are using v1/node//vifs to + # attach VIF to a node. + min_microversion = '1.28' + + @classmethod + def skip_checks(cls): + """Defines conditions to skip these tests.""" + super(BaremetalStandaloneManager, cls).skip_checks() + if CONF.service_available.nova: + raise cls.skipException('Nova is enabled. Stand-alone tests will ' + 'be skipped.') + + @classmethod + def create_networks(cls): + """Create a network with a subnet connected to a router. + + Return existed network specified in compute/fixed_network_name + config option. + TODO(vsaienko): Add network/subnet/router when we setup + ironic-standalone with multitenancy. + + :returns: network, subnet, router + """ + network = None + subnet = None + router = None + if CONF.network.shared_physical_network: + if not CONF.compute.fixed_network_name: + m = ('Configuration option "[compute]/fixed_network_name" ' + 'must be set.') + raise lib_exc.InvalidConfiguration(m) + network = cls.admin_manager.networks_client.list_networks( + name=CONF.compute.fixed_network_name)['networks'][0] + return network, subnet, router + + @classmethod + def get_available_nodes(cls): + """Get all ironic nodes that can be deployed. + + We can deploy on nodes when the following conditions are met: + * provision_state is 'available' + * maintenance is False + * No instance_uuid is associated to node. + + :returns: a list of Ironic nodes. + """ + fields = ['uuid', 'driver', 'instance_uuid', 'provision_state', + 'name', 'maintenance'] + _, body = cls.baremetal_client.list_nodes(provision_state='available', + associated=False, + maintenance=False, + fields=','.join(fields)) + return body['nodes'] + + @classmethod + def get_random_available_node(cls): + """Randomly pick an available node for deployment.""" + nodes = cls.get_available_nodes() + if nodes: + return random.choice(nodes) + + @classmethod + def create_neutron_port(cls, *args, **kwargs): + """Creates a neutron port. + + For a full list of available parameters, please refer to the official + API reference: + http://developer.openstack.org/api-ref/networking/v2/index.html#create-port + + :returns: server response body. + """ + port = cls.ports_client.create_port(*args, **kwargs)['port'] + return port + + @classmethod + def _associate_instance_with_node(cls, node_id, instance_uuid): + """Update instance_uuid for a given node. + + :param node_id: Name or UUID of the node. + :param instance_uuid: UUID of the instance to associate. + :returns: server response body. + """ + _, body = cls.baremetal_client.update_node( + node_id, instance_uuid=instance_uuid) + return body + + @classmethod + def get_node_vifs(cls, node_id): + """Return a list of VIFs for a given node. + + :param node_id: Name or UUID of the node. + :returns: A list of VIFs associated with the node. + """ + _, body = cls.baremetal_client.vif_list(node_id) + vifs = [v['id'] for v in body['vifs']] + return vifs + + @classmethod + def add_floatingip_to_node(cls, node_id): + """Add floating IP to node. + + Create and associate floating IP with node VIF. + + :param node_id: Name or UUID of the node. + :returns: IP address of associated floating IP. + """ + vif = cls.get_node_vifs(node_id)[0] + body = cls.floating_ips_client.create_floatingip( + floating_network_id=CONF.network.public_network_id) + floating_ip = body['floatingip'] + cls.floating_ips_client.update_floatingip(floating_ip['id'], + port_id=vif) + return floating_ip['floating_ip_address'] + + @classmethod + def cleanup_floating_ip(cls, ip_address): + """Removes floating IP.""" + body = cls.admin_manager.floating_ips_client.list_floatingips() + floating_ip_id = [f['id'] for f in body['floatingips'] if + f['floating_ip_address'] == ip_address][0] + cls.admin_manager.floating_ips_client.delete_floatingip(floating_ip_id) + + @classmethod + @bm.retry_on_conflict + def detach_all_vifs_from_node(cls, node_id): + """Detach all VIFs from a given node. + + :param node_id: Name or UUID of the node. + """ + vifs = cls.get_node_vifs(node_id) + for vif in vifs: + cls.baremetal_client.vif_detach(node_id, vif) + + @classmethod + @bm.retry_on_conflict + def vif_attach(cls, node_id, vif_id): + """Attach VIF to a give node. + + :param node_id: Name or UUID of the node. + :param vif_id: Identifier of the VIF to attach. + """ + cls.baremetal_client.vif_attach(node_id, vif_id) + + @classmethod + def get_and_reserve_node(cls, node=None): + """Pick an available node for deployment and reserve it. + + Only one instance_uuid may be associated, use this behaviour as + reservation node when tests are launched concurrently. If node is + not passed directly pick random available for deployment node. + + :param node: Ironic node to associate instance_uuid with. + :returns: Ironic node. + """ + instance_uuid = uuidutils.generate_uuid() + nodes = [] + + def _try_to_associate_instance(): + n = node or cls.get_random_available_node() + try: + cls._associate_instance_with_node(n['uuid'], instance_uuid) + nodes.append(n) + except lib_exc.Conflict: + return False + return True + + if (not test_utils.call_until_true(_try_to_associate_instance, + duration=CONF.baremetal.association_timeout, sleep_for=1)): + msg = ('Timed out waiting to associate instance to ironic node ' + 'uuid %s' % instance_uuid) + raise lib_exc.TimeoutException(msg) + + return nodes[0] + + @classmethod + def boot_node(cls, driver, image_ref): + """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. + :returns: Ironic node. + """ + node = cls.get_and_reserve_node() + cls.update_node_driver(node['uuid'], driver) + 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']) + patch = [{'path': '/instance_info/image_source', + 'op': 'add', + 'value': image_ref}] + patch.append({'path': '/instance_info/root_gb', + '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'], + bm.BaremetalProvisionStates.ACTIVE, + timeout=CONF.baremetal.active_timeout, + interval=30) + return node + + @classmethod + def terminate_node(cls, node_id): + """Terminate active ironic node. + + The following actions are executed: + * Detach all VIFs from the given node. + * Unprovision node. + * Wait until node become available. + + :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) + + +class BaremetalStandaloneScenarioTest(BaremetalStandaloneManager): + + # API microversion to use among all calls + api_microversion = '1.28' + + # The node driver to use in the test + driver = None + + # User image ref to boot node with. + image_ref = None + + # Boolean value specify if image is wholedisk or not. + wholedisk_image = None + + mandatory_attr = ['driver', 'image_ref', 'wholedisk_image'] + + node = None + node_ip = None + + @classmethod + def skip_checks(cls): + super(BaremetalStandaloneScenarioTest, cls).skip_checks() + if (cls.driver not in CONF.baremetal.enabled_drivers + + CONF.baremetal.enabled_hardware_types): + raise cls.skipException( + 'The driver: %(driver)s used in test is not in the list of ' + 'enabled_drivers %(enabled_drivers)s or ' + 'enabled_hardware_types %(enabled_hw_types)s ' + 'in the tempest config.' % { + 'driver': cls.driver, + 'enabled_drivers': CONF.baremetal.enabled_drivers, + 'enabled_hw_types': CONF.baremetal.enabled_hardware_types}) + if not cls.wholedisk_image and CONF.baremetal.use_provision_network: + raise cls.skipException( + 'Partitioned images are not supported with multitenancy.') + + @classmethod + def resource_setup(cls): + super(BaremetalStandaloneScenarioTest, cls).resource_setup() + base.set_baremetal_api_microversion(cls.api_microversion) + for v in cls.mandatory_attr: + if getattr(cls, v) is None: + raise lib_exc.InvalidConfiguration( + "Mandatory attribute %s not set." % v) + cls.node = cls.boot_node(cls.driver, cls.image_ref) + cls.node_ip = cls.add_floatingip_to_node(cls.node['uuid']) + + @classmethod + def resource_cleanup(cls): + cls.cleanup_floating_ip(cls.node_ip) + vifs = cls.get_node_vifs(cls.node['uuid']) + for vif in vifs: + cls.ports_client.delete_port(vif) + cls.terminate_node(cls.node['uuid']) + base.reset_baremetal_api_microversion() + super(BaremetalStandaloneManager, cls).resource_cleanup() diff --git a/ironic_tempest_plugin/tests/scenario/ironic_standalone/__init__.py b/ironic_tempest_plugin/tests/scenario/ironic_standalone/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..4def3fa4b6 --- /dev/null +++ b/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py @@ -0,0 +1,70 @@ +# +# 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 tempest import config +from tempest import test + +from ironic_tempest_plugin.tests.scenario import \ + baremetal_standalone_manager as bsm + +CONF = config.CONF + + +class BaremetalAgentIpmitoolWholedisk(bsm.BaremetalStandaloneScenarioTest): + + driver = 'agent_ipmitool' + image_ref = CONF.baremetal.whole_disk_image_ref + wholedisk_image = True + + @test.idempotent_id('defff515-a6ff-44f6-9d8d-2ded51196d98') + @test.services('image', 'network', 'object_storage') + def test_ip_access_to_server(self): + self.ping_ip_address(self.node_ip, should_succeed=True) + + +class BaremetalAgentIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest): + + driver = 'agent_ipmitool' + image_ref = CONF.baremetal.partition_image_ref + wholedisk_image = False + + @test.idempotent_id('27b86130-d8dc-419d-880a-fbbbe4ce3f8c') + @test.services('image', 'network', 'object_storage') + def test_ip_access_to_server(self): + self.ping_ip_address(self.node_ip, should_succeed=True) + + +class BaremetalPxeIpmitoolWholedisk(bsm.BaremetalStandaloneScenarioTest): + + driver = 'pxe_ipmitool' + image_ref = CONF.baremetal.whole_disk_image_ref + wholedisk_image = True + + @test.idempotent_id('d8c5badd-45db-4d05-bbe8-35babbed6e86') + @test.services('image', 'network') + def test_ip_access_to_server(self): + self.ping_ip_address(self.node_ip, should_succeed=True) + + +class BaremetalPxeIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest): + + driver = 'pxe_ipmitool' + image_ref = CONF.baremetal.partition_image_ref + wholedisk_image = False + + @test.idempotent_id('ea85e19c-6869-4577-b9bb-2eb150f77c90') + @test.services('image', 'network') + def test_ip_access_to_server(self): + self.ping_ip_address(self.node_ip, should_succeed=True)