From 7e1287f243059b2c46e0136463f1bff277db5456 Mon Sep 17 00:00:00 2001 From: "John L. Villalovos" Date: Wed, 13 Dec 2017 08:20:59 -0800 Subject: [PATCH] Remove ironic_tempest_plugin/ directory We now use the project openstack/ironic-tempest-plugin to store our tempest plugin. All content from the ironic_tempest_plugin/ directory has been ported to that project. We no longer want to have the plugin content stored here so we delete it. Remove check in tools/flake8wrap.sh that prevented changes to the ironic_tempest_plugin/ directory. Change-Id: I700bd7b71472fa91f6bc02aebc055584df08e0ef --- doc/source/conf.py | 3 +- ironic_tempest_plugin/PLUGIN-MOVED | 5 + ironic_tempest_plugin/README.rst | 15 - ironic_tempest_plugin/__init__.py | 0 ironic_tempest_plugin/clients.py | 53 -- ironic_tempest_plugin/common/__init__.py | 0 ironic_tempest_plugin/common/utils.py | 33 - ironic_tempest_plugin/common/waiters.py | 112 --- ironic_tempest_plugin/config.py | 116 ---- ironic_tempest_plugin/manager.py | 559 --------------- ironic_tempest_plugin/plugin.py | 46 -- ironic_tempest_plugin/services/__init__.py | 0 .../services/baremetal/__init__.py | 0 .../services/baremetal/base.py | 264 -------- .../services/baremetal/v1/__init__.py | 0 .../services/baremetal/v1/json/__init__.py | 0 .../baremetal/v1/json/baremetal_client.py | 641 ------------------ ironic_tempest_plugin/tests/__init__.py | 0 ironic_tempest_plugin/tests/api/__init__.py | 0 .../tests/api/admin/__init__.py | 0 .../api/admin/api_microversion_fixture.py | 26 - ironic_tempest_plugin/tests/api/admin/base.py | 351 ---------- .../tests/api/admin/test_api_discovery.py | 43 -- .../tests/api/admin/test_chassis.py | 83 --- .../tests/api/admin/test_drivers.py | 54 -- .../tests/api/admin/test_nodes.py | 403 ----------- .../tests/api/admin/test_nodestates.py | 193 ------ .../tests/api/admin/test_portgroups.py | 74 -- .../tests/api/admin/test_ports.py | 376 ---------- .../tests/api/admin/test_ports_negative.py | 463 ------------- .../tests/api/admin/test_volume_connector.py | 227 ------- .../tests/api/admin/test_volume_target.py | 210 ------ .../tests/scenario/__init__.py | 0 .../tests/scenario/baremetal_manager.py | 231 ------- .../scenario/baremetal_standalone_manager.py | 339 --------- .../scenario/ironic_standalone/__init__.py | 0 .../ironic_standalone/test_basic_ops.py | 145 ---- .../scenario/test_baremetal_basic_ops.py | 146 ---- .../test_baremetal_boot_from_volume.py | 152 ----- .../scenario/test_baremetal_multitenancy.py | 153 ----- ...mpest_plugin_removal-009f9ce8456b16fe.yaml | 9 + setup.cfg | 5 - tools/flake8wrap.sh | 18 - tools/ironic_tempest_plugin.SHA256SUM | 38 -- 44 files changed, 15 insertions(+), 5571 deletions(-) create mode 100644 ironic_tempest_plugin/PLUGIN-MOVED delete mode 100644 ironic_tempest_plugin/README.rst delete mode 100644 ironic_tempest_plugin/__init__.py delete mode 100644 ironic_tempest_plugin/clients.py delete mode 100644 ironic_tempest_plugin/common/__init__.py delete mode 100644 ironic_tempest_plugin/common/utils.py delete mode 100644 ironic_tempest_plugin/common/waiters.py delete mode 100644 ironic_tempest_plugin/config.py delete mode 100644 ironic_tempest_plugin/manager.py delete mode 100644 ironic_tempest_plugin/plugin.py delete mode 100644 ironic_tempest_plugin/services/__init__.py delete mode 100644 ironic_tempest_plugin/services/baremetal/__init__.py delete mode 100644 ironic_tempest_plugin/services/baremetal/base.py delete mode 100644 ironic_tempest_plugin/services/baremetal/v1/__init__.py delete mode 100644 ironic_tempest_plugin/services/baremetal/v1/json/__init__.py delete mode 100644 ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py delete mode 100644 ironic_tempest_plugin/tests/__init__.py delete mode 100644 ironic_tempest_plugin/tests/api/__init__.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/__init__.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/base.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_api_discovery.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_chassis.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_drivers.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_nodes.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_nodestates.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_portgroups.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_ports.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_ports_negative.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_volume_connector.py delete mode 100644 ironic_tempest_plugin/tests/api/admin/test_volume_target.py delete mode 100644 ironic_tempest_plugin/tests/scenario/__init__.py delete mode 100644 ironic_tempest_plugin/tests/scenario/baremetal_manager.py delete mode 100644 ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py delete mode 100644 ironic_tempest_plugin/tests/scenario/ironic_standalone/__init__.py delete mode 100644 ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py delete mode 100644 ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py delete mode 100644 ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py delete mode 100644 ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py create mode 100644 releasenotes/notes/tempest_plugin_removal-009f9ce8456b16fe.yaml delete mode 100644 tools/ironic_tempest_plugin.SHA256SUM diff --git a/doc/source/conf.py b/doc/source/conf.py index 72d30f8863..9c1ffe2781 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -100,8 +100,7 @@ pygments_style = 'sphinx' # A list of glob-style patterns that should be excluded when looking for # source files. They are matched against the source file names relative to the # source directory, using slashes as directory separators on all platforms. -exclude_patterns = ['api/ironic_tempest_plugin.*', - 'api/ironic.drivers.modules.ansible.playbooks.*', +exclude_patterns = ['api/ironic.drivers.modules.ansible.playbooks.*', 'api/ironic.tests.*'] # Ignore the following warning: WARNING: while setting up extension diff --git a/ironic_tempest_plugin/PLUGIN-MOVED b/ironic_tempest_plugin/PLUGIN-MOVED new file mode 100644 index 0000000000..62e9a9be83 --- /dev/null +++ b/ironic_tempest_plugin/PLUGIN-MOVED @@ -0,0 +1,5 @@ +The ironic tempest plugin code has been moved to a new dedicated repository: + + openstack/ironic-tempest-plugin/ + +Please update any dependencies to use this new repository for doing ironic tempest tests. diff --git a/ironic_tempest_plugin/README.rst b/ironic_tempest_plugin/README.rst deleted file mode 100644 index a46e206742..0000000000 --- a/ironic_tempest_plugin/README.rst +++ /dev/null @@ -1,15 +0,0 @@ -===================== -Ironic tempest plugin -===================== - -This directory contains Tempest tests to cover the Ironic project, -as well as a plugin to automatically load these tests into tempest. - -See the tempest plugin documentation for information about creating -a plugin, stable API interface, TempestPlugin class interface, plugin -structure, and how to use plugins: -https://docs.openstack.org/tempest/latest/plugin.html - -See the Ironic documentation for information about how to run the -tempest tests: -https://docs.openstack.org/ironic/latest/contributor/dev-quickstart.html#running-tempest-tests diff --git a/ironic_tempest_plugin/__init__.py b/ironic_tempest_plugin/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/clients.py b/ironic_tempest_plugin/clients.py deleted file mode 100644 index 2b81878002..0000000000 --- a/ironic_tempest_plugin/clients.py +++ /dev/null @@ -1,53 +0,0 @@ -# All Rights Reserved. -# -# 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 clients -from tempest.common import credentials_factory as common_creds -from tempest import config - -from ironic_tempest_plugin.services.baremetal.v1.json.baremetal_client import \ - BaremetalClient - - -CONF = config.CONF - -ADMIN_CREDS = None - - -class Manager(clients.Manager): - def __init__(self, - credentials=None): - """Initialization of Manager class. - - Setup service client and make it available for test cases. - :param credentials: type Credentials or TestResources - """ - if credentials is None: - global ADMIN_CREDS - if ADMIN_CREDS is None: - ADMIN_CREDS = common_creds.get_configured_admin_credentials() - credentials = ADMIN_CREDS - super(Manager, self).__init__(credentials) - default_params_with_timeout_values = { - 'build_interval': CONF.compute.build_interval, - 'build_timeout': CONF.compute.build_timeout - } - default_params_with_timeout_values.update(self.default_params) - - self.baremetal_client = BaremetalClient( - self.auth_provider, - CONF.baremetal.catalog_type, - CONF.identity.region, - endpoint_type=CONF.baremetal.endpoint_type, - **default_params_with_timeout_values) diff --git a/ironic_tempest_plugin/common/__init__.py b/ironic_tempest_plugin/common/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/common/utils.py b/ironic_tempest_plugin/common/utils.py deleted file mode 100644 index 67c4922fca..0000000000 --- a/ironic_tempest_plugin/common/utils.py +++ /dev/null @@ -1,33 +0,0 @@ -# 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. - - -def get_node(client, node_id=None, instance_uuid=None): - """Get a node by its identifier or instance UUID. - - If both node_id and instance_uuid specified, node_id will be used. - - :param client: an instance of tempest plugin BaremetalClient. - :param node_id: identifier (UUID or name) of the node. - :param instance_uuid: UUID of the instance. - :returns: the requested node. - :raises: AssertionError, if neither node_id nor instance_uuid was provided - """ - assert node_id or instance_uuid, ('Either node or instance identifier ' - 'has to be provided.') - if node_id: - _, body = client.show_node(node_id) - return body - elif instance_uuid: - _, body = client.show_node_by_instance_uuid(instance_uuid) - if body['nodes']: - return body['nodes'][0] diff --git a/ironic_tempest_plugin/common/waiters.py b/ironic_tempest_plugin/common/waiters.py deleted file mode 100644 index b706ac0cfb..0000000000 --- a/ironic_tempest_plugin/common/waiters.py +++ /dev/null @@ -1,112 +0,0 @@ -# All Rights Reserved. -# -# 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 six -from tempest import config -from tempest.lib.common.utils import test_utils -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.common import utils - -CONF = config.CONF - - -def _determine_and_check_timeout_interval(timeout, default_timeout, - interval, default_interval): - if timeout is None: - timeout = default_timeout - if interval is None: - interval = default_interval - if (not isinstance(timeout, six.integer_types) or - not isinstance(interval, six.integer_types) or - timeout < 0 or interval < 0): - raise AssertionError( - 'timeout and interval should be >= 0 or None, current values are: ' - '%(timeout)s, %(interval)s respectively. If timeout and/or ' - 'interval are None, the default_timeout and default_interval are ' - 'used, and they should be integers >= 0, current values are: ' - '%(default_timeout)s, %(default_interval)s respectively.' % dict( - timeout=timeout, interval=interval, - default_timeout=default_timeout, - default_interval=default_interval) - ) - return timeout, interval - - -def wait_for_bm_node_status(client, node_id, attr, status, timeout=None, - interval=None): - """Waits for a baremetal node attribute to reach given status. - - :param client: an instance of tempest plugin BaremetalClient. - :param node_id: identifier of the node. - :param attr: node's API-visible attribute to check status of. - :param status: desired status. Can be a list of statuses. - :param timeout: the timeout after which the check is considered as failed. - Defaults to client.build_timeout. - :param interval: an interval between show_node calls for status check. - Defaults to client.build_interval. - - The client should have a show_node(node_id) method to get the node. - """ - timeout, interval = _determine_and_check_timeout_interval( - timeout, client.build_timeout, interval, client.build_interval) - - if not isinstance(status, list): - status = [status] - - def is_attr_in_status(): - node = utils.get_node(client, node_id=node_id) - if node[attr] in status: - return True - return False - - if not test_utils.call_until_true(is_attr_in_status, timeout, - interval): - message = ('Node %(node_id)s failed to reach %(attr)s=%(status)s ' - 'within the required time (%(timeout)s s).' % - {'node_id': node_id, - 'attr': attr, - 'status': status, - 'timeout': timeout}) - caller = test_utils.find_test_caller() - if caller: - message = '(%s) %s' % (caller, message) - raise lib_exc.TimeoutException(message) - - -def wait_node_instance_association(client, instance_uuid, timeout=None, - interval=None): - """Waits for a node to be associated with instance_id. - - :param client: an instance of tempest plugin BaremetalClient. - :param instance_uuid: UUID of the instance. - :param timeout: the timeout after which the check is considered as failed. - Defaults to CONF.baremetal.association_timeout. - :param interval: an interval between show_node calls for status check. - Defaults to client.build_interval. - """ - timeout, interval = _determine_and_check_timeout_interval( - timeout, CONF.baremetal.association_timeout, - interval, client.build_interval) - - def is_some_node_associated(): - node = utils.get_node(client, instance_uuid=instance_uuid) - return node is not None - - if not test_utils.call_until_true(is_some_node_associated, timeout, - interval): - msg = ('Timed out waiting to get Ironic node by instance UUID ' - '%(instance_uuid)s within the required time (%(timeout)s s).' - % {'instance_uuid': instance_uuid, 'timeout': timeout}) - raise lib_exc.TimeoutException(msg) diff --git a/ironic_tempest_plugin/config.py b/ironic_tempest_plugin/config.py deleted file mode 100644 index 661b89c2ca..0000000000 --- a/ironic_tempest_plugin/config.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2015 NEC Corporation -# All Rights Reserved. -# -# 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_config import cfg - -from tempest import config # noqa - - -service_option = cfg.BoolOpt('ironic', - default=False, - help='Whether or not Ironic is expected to be ' - 'available') - - -baremetal_group = cfg.OptGroup(name='baremetal', - title='Baremetal provisioning service options', - help='When enabling baremetal tests, Nova ' - 'must be configured to use the Ironic ' - 'driver. The following parameters for the ' - '[compute] section must be disabled: ' - 'console_output, interface_attach, ' - 'live_migration, pause, rescue, resize, ' - 'shelve, snapshot, and suspend') - -baremetal_features_group = cfg.OptGroup( - name='baremetal_feature_enabled', - title="Enabled Baremetal Service Features") - -BaremetalGroup = [ - cfg.StrOpt('catalog_type', - default='baremetal', - help="Catalog type of the baremetal provisioning service"), - cfg.StrOpt('driver', - default='fake', - help="Driver name which Ironic uses"), - cfg.StrOpt('endpoint_type', - default='publicURL', - choices=['public', 'admin', 'internal', - 'publicURL', 'adminURL', 'internalURL'], - help="The endpoint type to use for the baremetal provisioning" - " service"), - cfg.IntOpt('deploywait_timeout', - default=15, - help="Timeout for Ironic node to reach the " - "wait-callback state after powering on."), - cfg.IntOpt('active_timeout', - default=300, - help="Timeout for Ironic node to completely provision"), - cfg.IntOpt('association_timeout', - default=30, - help="Timeout for association of Nova instance and Ironic " - "node"), - cfg.IntOpt('power_timeout', - default=60, - help="Timeout for Ironic power transitions."), - cfg.IntOpt('unprovision_timeout', - default=300, - help="Timeout for unprovisioning an Ironic node. " - "Takes longer since Kilo as Ironic performs an extra " - "step in Node cleaning."), - cfg.StrOpt('min_microversion', - help="Lower version of the test target microversion range. " - "The format is 'X.Y', where 'X' and 'Y' are int values. " - "Tempest selects tests based on the range between " - "min_microversion and max_microversion. " - "If both values are None, Tempest avoids tests which " - "require a microversion."), - cfg.StrOpt('max_microversion', - default='latest', - help="Upper version of the test target microversion range. " - "The format is 'X.Y', where 'X' and 'Y' are int values. " - "Tempest selects tests based on the range between " - "min_microversion and max_microversion. " - "If both values are None, Tempest avoids tests which " - "require a microversion."), - cfg.BoolOpt('use_provision_network', - default=False, - 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('whole_disk_image_url', - help="An http link to the wholedisk image to use in the " - "tests."), - cfg.StrOpt('whole_disk_image_checksum', - help="An MD5 checksum of the image."), - 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."), -] - -BaremetalFeaturesGroup = [ - cfg.BoolOpt('ipxe_enabled', - default=True, - help="Defines if IPXE is enabled"), -] diff --git a/ironic_tempest_plugin/manager.py b/ironic_tempest_plugin/manager.py deleted file mode 100644 index 9967a5dbb3..0000000000 --- a/ironic_tempest_plugin/manager.py +++ /dev/null @@ -1,559 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# -# 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. - -# NOTE(soliosg) Do not edit this file. It will only stay temporarily -# in ironic, while QA refactors the tempest.scenario interface. This -# file was copied from openstack/tempest/tempest/scenario/manager.py, -# openstack/tempest commit: 82a278e88c9e9f9ba49f81c1f8dba0bca7943daf - -import subprocess - -from oslo_log import log -from oslo_utils import netutils -from tempest.common import compute -from tempest.common.utils.linux import remote_client -from tempest.common.utils import net_utils -from tempest.common import waiters -from tempest import config -from tempest import exceptions -from tempest.lib.common.utils import data_utils -from tempest.lib.common.utils import test_utils -from tempest.lib import exceptions as lib_exc -import tempest.test - -CONF = config.CONF - -LOG = log.getLogger(__name__) - - -class ScenarioTest(tempest.test.BaseTestCase): - """Base class for scenario tests. Uses tempest own clients. """ - - credentials = ['primary'] - - @classmethod - def setup_clients(cls): - super(ScenarioTest, cls).setup_clients() - # Clients (in alphabetical order) - cls.flavors_client = cls.os_primary.flavors_client - cls.compute_floating_ips_client = ( - cls.os_primary.compute_floating_ips_client) - if CONF.service_available.glance: - # Check if glance v1 is available to determine which client to use. - if CONF.image_feature_enabled.api_v1: - cls.image_client = cls.os_primary.image_client - elif CONF.image_feature_enabled.api_v2: - cls.image_client = cls.os_primary.image_client_v2 - else: - raise lib_exc.InvalidConfiguration( - 'Either api_v1 or api_v2 must be True in ' - '[image-feature-enabled].') - # Compute image client - cls.compute_images_client = cls.os_primary.compute_images_client - cls.keypairs_client = cls.os_primary.keypairs_client - # Nova security groups client - cls.compute_security_groups_client = ( - cls.os_primary.compute_security_groups_client) - cls.compute_security_group_rules_client = ( - cls.os_primary.compute_security_group_rules_client) - cls.servers_client = cls.os_primary.servers_client - cls.interface_client = cls.os_primary.interfaces_client - # Neutron network client - cls.networks_client = cls.os_primary.networks_client - cls.ports_client = cls.os_primary.ports_client - cls.routers_client = cls.os_primary.routers_client - cls.subnets_client = cls.os_primary.subnets_client - cls.floating_ips_client = cls.os_primary.floating_ips_client - cls.security_groups_client = cls.os_primary.security_groups_client - 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 - - # ## Test functions library - # - # The create_[resource] functions only return body and discard the - # resp part which is not used in scenario tests - - def _create_port(self, network_id, client=None, namestart='port-quotatest', - **kwargs): - if not client: - client = self.ports_client - name = data_utils.rand_name(namestart) - result = client.create_port( - name=name, - network_id=network_id, - **kwargs) - self.assertIsNotNone(result, 'Unable to allocate port') - port = result['port'] - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - client.delete_port, port['id']) - return port - - def create_keypair(self, client=None): - if not client: - client = self.keypairs_client - name = data_utils.rand_name(self.__class__.__name__) - # We don't need to create a keypair by pubkey in scenario - body = client.create_keypair(name=name) - self.addCleanup(client.delete_keypair, name) - return body['keypair'] - - def create_server(self, name=None, image_id=None, flavor=None, - validatable=False, wait_until='ACTIVE', - clients=None, **kwargs): - """Wrapper utility that returns a test server. - - This wrapper utility calls the common create test server and - returns a test server. The purpose of this wrapper is to minimize - the impact on the code of the tests already using this - function. - """ - - # NOTE(jlanoux): As a first step, ssh checks in the scenario - # tests need to be run regardless of the run_validation and - # validatable parameters and thus until the ssh validation job - # becomes voting in CI. The test resources management and IP - # association are taken care of in the scenario tests. - # Therefore, the validatable parameter is set to false in all - # those tests. In this way create_server just return a standard - # server and the scenario tests always perform ssh checks. - - # Needed for the cross_tenant_traffic test: - if clients is None: - clients = self.os_primary - - if name is None: - name = data_utils.rand_name(self.__class__.__name__ + "-server") - - vnic_type = CONF.network.port_vnic_type - - # If vnic_type is configured create port for - # every network - if vnic_type: - ports = [] - - create_port_body = {'binding:vnic_type': vnic_type, - 'namestart': 'port-smoke'} - if kwargs: - # Convert security group names to security group ids - # to pass to create_port - if 'security_groups' in kwargs: - security_groups = \ - clients.security_groups_client.list_security_groups( - ).get('security_groups') - sec_dict = dict([(s['name'], s['id']) - for s in security_groups]) - - sec_groups_names = [s['name'] for s in kwargs.pop( - 'security_groups')] - security_groups_ids = [sec_dict[s] - for s in sec_groups_names] - - if security_groups_ids: - create_port_body[ - 'security_groups'] = security_groups_ids - networks = kwargs.pop('networks', []) - else: - networks = [] - - # If there are no networks passed to us we look up - # for the project's private networks and create a port. - # The same behaviour as we would expect when passing - # the call to the clients with no networks - if not networks: - networks = clients.networks_client.list_networks( - **{'router:external': False, 'fields': 'id'})['networks'] - - # It's net['uuid'] if networks come from kwargs - # and net['id'] if they come from - # clients.networks_client.list_networks - for net in networks: - net_id = net.get('uuid', net.get('id')) - if 'port' not in net: - port = self._create_port(network_id=net_id, - client=clients.ports_client, - **create_port_body) - ports.append({'port': port['id']}) - else: - ports.append({'port': net['port']}) - if ports: - kwargs['networks'] = ports - self.ports = ports - - tenant_network = self.get_tenant_network() - - body, servers = compute.create_test_server( - clients, - tenant_network=tenant_network, - wait_until=wait_until, - name=name, flavor=flavor, - image_id=image_id, **kwargs) - - self.addCleanup(waiters.wait_for_server_termination, - clients.servers_client, body['id']) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - clients.servers_client.delete_server, body['id']) - server = clients.servers_client.show_server(body['id'])['server'] - return server - - def get_remote_client(self, ip_address, username=None, private_key=None): - """Get a SSH client to a remote server - - @param ip_address the server floating or fixed IP address to use - for ssh validation - @param username name of the Linux account on the remote server - @param private_key the SSH private key to use - @return a RemoteClient object - """ - - if username is None: - username = CONF.validation.image_ssh_user - # Set this with 'keypair' or others to log in with keypair or - # username/password. - if CONF.validation.auth_method == 'keypair': - password = None - if private_key is None: - private_key = self.keypair['private_key'] - else: - password = CONF.validation.image_ssh_password - private_key = None - linux_client = remote_client.RemoteClient(ip_address, username, - pkey=private_key, - password=password) - try: - linux_client.validate_authentication() - except Exception as e: - message = ('Initializing SSH connection to %(ip)s failed. ' - 'Error: %(error)s' % {'ip': ip_address, - 'error': e}) - caller = test_utils.find_test_caller() - if caller: - message = '(%s) %s' % (caller, message) - LOG.exception(message) - self._log_console_output() - raise - - return linux_client - - def _log_console_output(self, servers=None): - if not CONF.compute_feature_enabled.console_output: - LOG.debug('Console output not supported, cannot log') - return - if not servers: - servers = self.servers_client.list_servers() - servers = servers['servers'] - for server in servers: - try: - console_output = self.servers_client.get_console_output( - server['id'])['output'] - LOG.debug('Console output for %s\nbody=\n%s', - server['id'], console_output) - except lib_exc.NotFound: - LOG.debug("Server %s disappeared(deleted) while looking " - "for the console log", server['id']) - - def rebuild_server(self, server_id, image=None, - preserve_ephemeral=False, wait=True, - rebuild_kwargs=None): - if image is None: - image = CONF.compute.image_ref - - rebuild_kwargs = rebuild_kwargs or {} - - LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)", - server_id, image, preserve_ephemeral) - self.servers_client.rebuild_server( - server_id=server_id, image_ref=image, - preserve_ephemeral=preserve_ephemeral, - **rebuild_kwargs) - if wait: - waiters.wait_for_server_status(self.servers_client, - server_id, 'ACTIVE') - - def ping_ip_address(self, ip_address, should_succeed=True, - ping_timeout=None, mtu=None): - timeout = ping_timeout or CONF.validation.ping_timeout - cmd = ['ping', '-c1', '-w1'] - - if mtu: - cmd += [ - # don't fragment - '-M', 'do', - # ping receives just the size of ICMP payload - '-s', str(net_utils.get_ping_payload_size(mtu, 4)) - ] - cmd.append(ip_address) - - def ping(): - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - proc.communicate() - - return (proc.returncode == 0) == should_succeed - - caller = test_utils.find_test_caller() - LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the' - ' expected result is %(should_succeed)s', { - 'caller': caller, 'ip': ip_address, 'timeout': timeout, - 'should_succeed': - 'reachable' if should_succeed else 'unreachable' - }) - result = test_utils.call_until_true(ping, timeout, 1) - LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the ' - 'ping result is %(result)s', { - 'caller': caller, 'ip': ip_address, 'timeout': timeout, - 'result': 'expected' if result else 'unexpected' - }) - return result - - def check_vm_connectivity(self, ip_address, - username=None, - private_key=None, - should_connect=True, - mtu=None): - """Check server connectivity - - :param ip_address: server to test against - :param username: server's ssh username - :param private_key: server's ssh private key to be used - :param should_connect: True/False indicates positive/negative test - positive - attempt ping and ssh - negative - attempt ping and fail if succeed - :param mtu: network MTU to use for connectivity validation - - :raises: AssertError if the result of the connectivity check does - not match the value of the should_connect param - """ - if should_connect: - msg = "Timed out waiting for %s to become reachable" % ip_address - else: - msg = "ip address %s is reachable" % ip_address - self.assertTrue(self.ping_ip_address(ip_address, - should_succeed=should_connect, - mtu=mtu), - msg=msg) - if should_connect: - # no need to check ssh for negative connectivity - self.get_remote_client(ip_address, username, private_key) - - def create_floating_ip(self, thing, pool_name=None): - """Create a floating IP and associates to a server on Nova""" - - if not pool_name: - pool_name = CONF.network.floating_network_name - floating_ip = (self.compute_floating_ips_client. - create_floating_ip(pool=pool_name)['floating_ip']) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - self.compute_floating_ips_client.delete_floating_ip, - floating_ip['id']) - self.compute_floating_ips_client.associate_floating_ip_to_server( - floating_ip['ip'], thing['id']) - return floating_ip - - def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt', - private_key=None): - ssh_client = self.get_remote_client(ip_address, - private_key=private_key) - if dev_name is not None: - ssh_client.make_fs(dev_name) - ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name, - mount_path)) - cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path - ssh_client.exec_command(cmd_timestamp) - timestamp = ssh_client.exec_command('sudo cat %s/timestamp' - % mount_path) - if dev_name is not None: - ssh_client.exec_command('sudo umount %s' % mount_path) - return timestamp - - def get_server_ip(self, server): - """Get the server fixed or floating IP. - - Based on the configuration we're in, return a correct ip - address for validating that a guest is up. - """ - if CONF.validation.connect_method == 'floating': - # The tests calling this method don't have a floating IP - # and can't make use of the validation resources. So the - # method is creating the floating IP there. - return self.create_floating_ip(server)['ip'] - elif CONF.validation.connect_method == 'fixed': - # Determine the network name to look for based on config or creds - # provider network resources. - if CONF.validation.network_for_ssh: - addresses = server['addresses'][ - CONF.validation.network_for_ssh] - else: - creds_provider = self._get_credentials_provider() - net_creds = creds_provider.get_primary_creds() - network = getattr(net_creds, 'network', None) - addresses = (server['addresses'][network['name']] - if network else []) - for address in addresses: - if (address['version'] == CONF.validation.ip_version_for_ssh - and address['OS-EXT-IPS:type'] == 'fixed'): - return address['addr'] - raise exceptions.ServerUnreachable(server_id=server['id']) - else: - raise lib_exc.InvalidConfiguration() - - def _get_router(self, client=None, tenant_id=None): - """Retrieve a router for the given tenant id. - - If a public router has been configured, it will be returned. - - If a public router has not been configured, but a public - network has, a tenant router will be created and returned that - routes traffic to the public network. - """ - if not client: - client = self.routers_client - if not tenant_id: - tenant_id = client.tenant_id - router_id = CONF.network.public_router_id - network_id = CONF.network.public_network_id - if router_id: - body = client.show_router(router_id) - return body['router'] - elif network_id: - router = self._create_router(client, tenant_id) - kwargs = {'external_gateway_info': dict(network_id=network_id)} - router = client.update_router(router['id'], **kwargs)['router'] - return router - else: - raise Exception("Neither of 'public_router_id' or " - "'public_network_id' has been defined.") - - def _create_router(self, client=None, tenant_id=None, - namestart='router-smoke'): - if not client: - client = self.routers_client - if not tenant_id: - tenant_id = client.tenant_id - name = data_utils.rand_name(namestart) - result = client.create_router(name=name, - admin_state_up=True, - tenant_id=tenant_id) - router = result['router'] - self.assertEqual(router['name'], name) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - client.delete_router, - router['id']) - return router - - -class NetworkScenarioTest(ScenarioTest): - """Base class for network scenario tests. - - This class provide helpers for network scenario tests, using the neutron - API. Helpers from ancestor which use the nova network API are overridden - with the neutron API. - - This Class also enforces using Neutron instead of novanetwork. - Subclassed tests will be skipped if Neutron is not enabled - - """ - - credentials = ['primary', 'admin'] - - @classmethod - def skip_checks(cls): - super(NetworkScenarioTest, cls).skip_checks() - if not CONF.service_available.neutron: - raise cls.skipException('Neutron not available') - - def _create_network(self, networks_client=None, - tenant_id=None, - namestart='network-smoke-', - port_security_enabled=True): - if not networks_client: - networks_client = self.networks_client - if not tenant_id: - tenant_id = networks_client.tenant_id - name = data_utils.rand_name(namestart) - network_kwargs = dict(name=name, tenant_id=tenant_id) - # Neutron disables port security by default so we have to check the - # config before trying to create the network with port_security_enabled - if CONF.network_feature_enabled.port_security: - network_kwargs['port_security_enabled'] = port_security_enabled - result = networks_client.create_network(**network_kwargs) - network = result['network'] - - self.assertEqual(network['name'], name) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - networks_client.delete_network, - network['id']) - return network - - def _get_server_port_id_and_ip4(self, server, ip_addr=None): - ports = self.os_admin.ports_client.list_ports( - device_id=server['id'], fixed_ip=ip_addr)['ports'] - # A port can have more than one IP address in some cases. - # If the network is dual-stack (IPv4 + IPv6), this port is associated - # with 2 subnets - p_status = ['ACTIVE'] - # NOTE(vsaienko) With Ironic, instances live on separate hardware - # servers. Neutron does not bind ports for Ironic instances, as a - # result the port remains in the DOWN state. - # TODO(vsaienko) remove once bug: #1599836 is resolved. - if getattr(CONF.service_available, 'ironic', False): - p_status.append('DOWN') - port_map = [(p["id"], fxip["ip_address"]) - for p in ports - for fxip in p["fixed_ips"] - if netutils.is_valid_ipv4(fxip["ip_address"]) - and p['status'] in p_status] - inactive = [p for p in ports if p['status'] != 'ACTIVE'] - if inactive: - LOG.warning("Instance has ports that are not ACTIVE: %s", inactive) - - self.assertNotEqual(0, len(port_map), - "No IPv4 addresses found in: %s" % ports) - self.assertEqual(len(port_map), 1, - "Found multiple IPv4 addresses: %s. " - "Unable to determine which port to target." - % port_map) - return port_map[0] - - def create_floating_ip(self, thing, external_network_id=None, - port_id=None, client=None): - """Create a floating IP and associates to a resource/port on Neutron""" - if not external_network_id: - external_network_id = CONF.network.public_network_id - if not client: - client = self.floating_ips_client - if not port_id: - port_id, ip4 = self._get_server_port_id_and_ip4(thing) - else: - ip4 = None - result = client.create_floatingip( - floating_network_id=external_network_id, - port_id=port_id, - tenant_id=thing['tenant_id'], - fixed_ip_address=ip4 - ) - floating_ip = result['floatingip'] - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - client.delete_floatingip, - floating_ip['id']) - return floating_ip diff --git a/ironic_tempest_plugin/plugin.py b/ironic_tempest_plugin/plugin.py deleted file mode 100644 index 9e9c175c6a..0000000000 --- a/ironic_tempest_plugin/plugin.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2015 NEC Corporation -# All Rights Reserved. -# -# 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 os - -from tempest import config -from tempest.test_discover import plugins - -from ironic_tempest_plugin import config as project_config - -_opts = [ - (project_config.baremetal_group, project_config.BaremetalGroup), - (project_config.baremetal_features_group, - project_config.BaremetalFeaturesGroup) -] - - -class IronicTempestPlugin(plugins.TempestPlugin): - def load_tests(self): - base_path = os.path.split(os.path.dirname( - os.path.abspath(__file__)))[0] - test_dir = "ironic_tempest_plugin/tests" - full_test_dir = os.path.join(base_path, test_dir) - return full_test_dir, base_path - - def register_opts(self, conf): - conf.register_opt(project_config.service_option, - group='service_available') - for group, option in _opts: - config.register_opt_group(conf, group, option) - - def get_opt_lists(self): - return [(group.name, option) for group, option in _opts] diff --git a/ironic_tempest_plugin/services/__init__.py b/ironic_tempest_plugin/services/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/services/baremetal/__init__.py b/ironic_tempest_plugin/services/baremetal/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/services/baremetal/base.py b/ironic_tempest_plugin/services/baremetal/base.py deleted file mode 100644 index 757a77075a..0000000000 --- a/ironic_tempest_plugin/services/baremetal/base.py +++ /dev/null @@ -1,264 +0,0 @@ -# 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 functools - -from oslo_serialization import jsonutils as json -from six.moves import http_client -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.""" - - @functools.wraps(f) - def wrapper(*args, **kwargs): - param_name = 'ignore_errors' - ignored_errors = kwargs.get(param_name, tuple()) - - if param_name in kwargs: - del kwargs[param_name] - - try: - return f(*args, **kwargs) - except ignored_errors: - # Silently ignore errors - pass - - return wrapper - - -class BaremetalClient(rest_client.RestClient): - """Base Tempest REST client for Ironic API.""" - - api_microversion_header_name = 'X-OpenStack-Ironic-API-Version' - uri_prefix = '' - - def get_headers(self): - headers = super(BaremetalClient, self).get_headers() - if BAREMETAL_MICROVERSION: - headers[self.api_microversion_header_name] = BAREMETAL_MICROVERSION - return headers - - def request(self, *args, **kwargs): - resp, resp_body = super(BaremetalClient, self).request(*args, **kwargs) - if (BAREMETAL_MICROVERSION and - BAREMETAL_MICROVERSION != api_version_utils.LATEST_MICROVERSION): - api_version_utils.assert_version_header_matches_request( - self.api_microversion_header_name, - BAREMETAL_MICROVERSION, - resp) - return resp, resp_body - - def serialize(self, object_dict): - """Serialize an Ironic object.""" - - return json.dumps(object_dict) - - def deserialize(self, object_str): - """Deserialize an Ironic object.""" - - return json.loads(object_str) - - def _get_uri(self, resource_name, uuid=None, permanent=False): - """Get URI for a specific resource or object. - - :param resource_name: The name of the REST resource, e.g., 'nodes'. - :param uuid: The unique identifier of an object in UUID format. - :returns: Relative URI for the resource or object. - - """ - prefix = self.uri_prefix if not permanent else '' - - return '{pref}/{res}{uuid}'.format(pref=prefix, - res=resource_name, - uuid='/%s' % uuid if uuid else '') - - def _make_patch(self, allowed_attributes, **kwargs): - """Create a JSON patch according to RFC 6902. - - :param allowed_attributes: An iterable object that contains a set of - allowed attributes for an object. - :param **kwargs: Attributes and new values for them. - :returns: A JSON path that sets values of the specified attributes to - the new ones. - - """ - def get_change(kwargs, path='/'): - for name, value in kwargs.items(): - if isinstance(value, dict): - for ch in get_change(value, path + '%s/' % name): - yield ch - else: - if value is None: - yield {'path': path + name, - 'op': 'remove'} - else: - yield {'path': path + name, - 'value': value, - 'op': 'replace'} - - patch = [ch for ch in get_change(kwargs) - if ch['path'].lstrip('/') in allowed_attributes] - - return patch - - def _list_request(self, resource, permanent=False, headers=None, - extra_headers=False, **kwargs): - """Get the list of objects of the specified type. - - :param resource: The name of the REST resource, e.g., 'nodes'. - :param headers: List of headers to use in request. - :param extra_headers: Specify whether to use headers. - :param **kwargs: Parameters for the request. - :returns: A tuple with the server response and deserialized JSON list - of objects - - """ - uri = self._get_uri(resource, permanent=permanent) - if kwargs: - uri += "?%s" % urllib.urlencode(kwargs) - - resp, body = self.get(uri, headers=headers, - extra_headers=extra_headers) - self.expected_success(http_client.OK, resp.status) - - return resp, self.deserialize(body) - - def _show_request(self, - resource, - uuid=None, - permanent=False, - **kwargs): - """Gets a specific object of the specified type. - - :param uuid: Unique identifier of the object in UUID format. - :returns: Serialized object as a dictionary. - - """ - if 'uri' in kwargs: - uri = kwargs['uri'] - else: - uri = self._get_uri(resource, uuid=uuid, permanent=permanent) - resp, body = self.get(uri) - self.expected_success(http_client.OK, resp.status) - - return resp, self.deserialize(body) - - def _create_request(self, resource, object_dict): - """Create an object of the specified type. - - :param resource: The name of the REST resource, e.g., 'nodes'. - :param object_dict: A Python dict that represents an object of the - specified type. - :returns: A tuple with the server response and the deserialized created - object. - - """ - body = self.serialize(object_dict) - uri = self._get_uri(resource) - - resp, body = self.post(uri, body=body) - self.expected_success(http_client.CREATED, resp.status) - - return resp, self.deserialize(body) - - def _create_request_no_response_body(self, resource, object_dict): - """Create an object of the specified type. - - Do not expect any body in the response. - - :param resource: The name of the REST resource, e.g., 'nodes'. - :param object_dict: A Python dict that represents an object of the - specified type. - :returns: The server response. - """ - - body = self.serialize(object_dict) - uri = self._get_uri(resource) - - resp, body = self.post(uri, body=body) - self.expected_success(http_client.NO_CONTENT, resp.status) - - return resp - - def _delete_request(self, resource, uuid): - """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. - :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) - return resp, body - - def _patch_request(self, resource, uuid, patch_object): - """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. - :returns: A tuple with the server response and the serialized patched - object. - - """ - uri = self._get_uri(resource, uuid) - patch_body = json.dumps(patch_object) - - resp, body = self.patch(uri, body=patch_body) - self.expected_success(http_client.OK, resp.status) - return resp, self.deserialize(body) - - @handle_errors - def get_api_description(self): - """Retrieves all versions of the Ironic API.""" - - return self._list_request('', permanent=True) - - @handle_errors - def get_version_description(self, version='v1'): - """Retrieves the description of the API. - - :param version: The version of the API. Default: 'v1'. - :returns: Serialized description of API resources. - - """ - return self._list_request(version, permanent=True) - - def _put_request(self, resource, put_object): - """Update specified object with JSON-patch.""" - uri = self._get_uri(resource) - put_body = json.dumps(put_object) - - resp, body = self.put(uri, body=put_body) - self.expected_success([http_client.ACCEPTED, http_client.NO_CONTENT], - resp.status) - return resp, body diff --git a/ironic_tempest_plugin/services/baremetal/v1/__init__.py b/ironic_tempest_plugin/services/baremetal/v1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/services/baremetal/v1/json/__init__.py b/ironic_tempest_plugin/services/baremetal/v1/json/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py b/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py deleted file mode 100644 index 9712dba515..0000000000 --- a/ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py +++ /dev/null @@ -1,641 +0,0 @@ -# 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 six.moves import http_client - -from ironic_tempest_plugin.services.baremetal import base - - -class BaremetalClient(base.BaremetalClient): - """Base Tempest REST client for Ironic API v1.""" - version = '1' - uri_prefix = 'v1' - - @base.handle_errors - def list_nodes(self, **kwargs): - """List all existing nodes.""" - return self._list_request('nodes', **kwargs) - - @base.handle_errors - def list_nodes_detail(self, **kwargs): - """Detailed list of all existing nodes.""" - return self._list_request('/nodes/detail', **kwargs) - - @base.handle_errors - def list_chassis(self): - """List all existing chassis.""" - return self._list_request('chassis') - - @base.handle_errors - def list_chassis_nodes(self, chassis_uuid): - """List all nodes associated with a chassis.""" - return self._list_request('/chassis/%s/nodes' % chassis_uuid) - - @base.handle_errors - def list_ports(self, **kwargs): - """List all existing ports.""" - return self._list_request('ports', **kwargs) - - @base.handle_errors - def list_portgroups(self, **kwargs): - """List all existing port groups.""" - return self._list_request('portgroups', **kwargs) - - @base.handle_errors - def list_volume_connectors(self, **kwargs): - """List all existing volume connectors.""" - return self._list_request('volume/connectors', **kwargs) - - @base.handle_errors - def list_volume_targets(self, **kwargs): - """List all existing volume targets.""" - return self._list_request('volume/targets', **kwargs) - - @base.handle_errors - def list_node_ports(self, uuid): - """List all ports associated with the node.""" - return self._list_request('/nodes/%s/ports' % uuid) - - @base.handle_errors - def list_nodestates(self, uuid): - """List all existing states.""" - return self._list_request('/nodes/%s/states' % uuid) - - @base.handle_errors - def list_ports_detail(self, **kwargs): - """Details list all existing ports.""" - return self._list_request('/ports/detail', **kwargs) - - @base.handle_errors - def list_drivers(self): - """List all existing drivers.""" - return self._list_request('drivers') - - @base.handle_errors - def show_node(self, uuid): - """Gets a specific node. - - :param uuid: Unique identifier of the node in UUID format. - :return: Serialized node as a dictionary. - - """ - return self._show_request('nodes', uuid) - - @base.handle_errors - def show_node_by_instance_uuid(self, instance_uuid): - """Gets a node associated with given instance uuid. - - :param instance_uuid: Unique identifier of the instance in UUID format. - :return: Serialized node as a dictionary. - - """ - uri = '/nodes/detail?instance_uuid=%s' % instance_uuid - - return self._show_request('nodes', - uuid=None, - uri=uri) - - @base.handle_errors - def show_chassis(self, uuid): - """Gets a specific chassis. - - :param uuid: Unique identifier of the chassis in UUID format. - :return: Serialized chassis as a dictionary. - - """ - return self._show_request('chassis', uuid) - - @base.handle_errors - def show_port(self, uuid): - """Gets a specific port. - - :param uuid: Unique identifier of the port in UUID format. - :return: Serialized port as a dictionary. - - """ - return self._show_request('ports', uuid) - - @base.handle_errors - def show_portgroup(self, portgroup_ident): - """Gets a specific port group. - - :param portgroup_ident: Name or UUID of the port group. - :return: Serialized port group as a dictionary. - """ - return self._show_request('portgroups', portgroup_ident) - - @base.handle_errors - def show_volume_connector(self, volume_connector_ident): - """Gets a specific volume connector. - - :param volume_connector_ident: UUID of the volume connector. - :return: Serialized volume connector as a dictionary. - """ - return self._show_request('volume/connectors', volume_connector_ident) - - @base.handle_errors - def show_volume_target(self, volume_target_ident): - """Gets a specific volume target. - - :param volume_target_ident: UUID of the volume target. - :return: Serialized volume target as a dictionary. - """ - return self._show_request('volume/targets', volume_target_ident) - - @base.handle_errors - def show_port_by_address(self, address): - """Gets a specific port by address. - - :param address: MAC address of the port. - :return: Serialized port as a dictionary. - - """ - uri = '/ports/detail?address=%s' % address - - return self._show_request('ports', uuid=None, uri=uri) - - def show_driver(self, driver_name): - """Gets a specific driver. - - :param driver_name: Name of driver. - :return: Serialized driver as a dictionary. - """ - return self._show_request('drivers', driver_name) - - @base.handle_errors - def create_node(self, chassis_id=None, **kwargs): - """Create a baremetal node with the specified parameters. - - :param chassis_id: The unique identifier of the chassis. - :param cpu_arch: CPU architecture of the node. Default: x86_64. - :param cpus: Number of CPUs. Default: 8. - :param local_gb: Disk size. Default: 1024. - :param memory_mb: Available RAM. Default: 4096. - :param driver: Driver name. Default: "fake" - :return: A tuple with the server response and the created node. - - """ - node = {} - if kwargs.get('resource_class'): - node['resource_class'] = kwargs['resource_class'] - - node.update( - {'chassis_uuid': chassis_id, - 'properties': {'cpu_arch': kwargs.get('cpu_arch', 'x86_64'), - 'cpus': kwargs.get('cpus', 8), - 'local_gb': kwargs.get('local_gb', 1024), - 'memory_mb': kwargs.get('memory_mb', 4096)}, - 'driver': kwargs.get('driver', 'fake')} - ) - - return self._create_request('nodes', node) - - @base.handle_errors - def create_chassis(self, **kwargs): - """Create a chassis with the specified parameters. - - :param description: The description of the chassis. - Default: test-chassis - :return: A tuple with the server response and the created chassis. - - """ - chassis = {'description': kwargs.get('description', 'test-chassis')} - - if 'uuid' in kwargs: - chassis.update({'uuid': kwargs.get('uuid')}) - - return self._create_request('chassis', chassis) - - @base.handle_errors - def create_port(self, node_id, **kwargs): - """Create a port with the specified parameters. - - :param node_id: The ID of the node which owns the port. - :param address: MAC address of the port. - :param extra: Meta data of the port. Default: {'foo': 'bar'}. - :param uuid: UUID of the port. - :param portgroup_uuid: The UUID of a portgroup of which this port is a - member. - :param physical_network: The physical network to which the port is - attached. - :return: A tuple with the server response and the created port. - - """ - port = {'extra': kwargs.get('extra', {'foo': 'bar'}), - 'uuid': kwargs['uuid']} - - if node_id is not None: - port['node_uuid'] = node_id - - for key in ('address', 'physical_network', 'portgroup_uuid'): - if kwargs.get(key) is not None: - port[key] = kwargs[key] - - return self._create_request('ports', port) - - @base.handle_errors - def create_portgroup(self, node_uuid, **kwargs): - """Create a port group with the specified parameters. - - :param node_uuid: The UUID of the node which owns the port group. - :param kwargs: - address: MAC address of the port group. Optional. - extra: Meta data of the port group. Default: {'foo': 'bar'}. - name: Name of the port group. Optional. - uuid: UUID of the port group. Optional. - :return: A tuple with the server response and the created port group. - """ - portgroup = {'extra': kwargs.get('extra', {'foo': 'bar'})} - - portgroup['node_uuid'] = node_uuid - - if kwargs.get('address'): - portgroup['address'] = kwargs['address'] - - if kwargs.get('name'): - portgroup['name'] = kwargs['name'] - - return self._create_request('portgroups', portgroup) - - @base.handle_errors - def create_volume_connector(self, node_uuid, **kwargs): - """Create a volume connector with the specified parameters. - - :param node_uuid: The UUID of the node which owns the volume connector. - :param kwargs: - type: type of the volume connector. - connector_id: connector_id of the volume connector. - uuid: UUID of the volume connector. Optional. - extra: meta data of the volume connector; a dictionary. Optional. - :return: A tuple with the server response and the created volume - connector. - """ - volume_connector = {'node_uuid': node_uuid} - - for arg in ('type', 'connector_id', 'uuid', 'extra'): - if arg in kwargs: - volume_connector[arg] = kwargs[arg] - - return self._create_request('volume/connectors', volume_connector) - - @base.handle_errors - def create_volume_target(self, node_uuid, **kwargs): - """Create a volume target with the specified parameters. - - :param node_uuid: The UUID of the node which owns the volume target. - :param kwargs: - volume_type: type of the volume target. - volume_id: volume_id of the volume target. - boot_index: boot index of the volume target. - uuid: UUID of the volume target. Optional. - extra: meta data of the volume target; a dictionary. Optional. - properties: properties related to the type of the volume target; - a dictionary. Optional. - :return: A tuple with the server response and the created volume - target. - """ - volume_target = {'node_uuid': node_uuid} - - for arg in ('volume_type', 'volume_id', 'boot_index', 'uuid', 'extra', - 'properties'): - if arg in kwargs: - volume_target[arg] = kwargs[arg] - - return self._create_request('volume/targets', volume_target) - - @base.handle_errors - def delete_node(self, uuid): - """Deletes a node having the specified UUID. - - :param uuid: The unique identifier of the node. - :return: A tuple with the server response and the response body. - - """ - return self._delete_request('nodes', uuid) - - @base.handle_errors - def delete_chassis(self, uuid): - """Deletes a chassis having the specified UUID. - - :param uuid: The unique identifier of the chassis. - :return: A tuple with the server response and the response body. - - """ - return self._delete_request('chassis', uuid) - - @base.handle_errors - def delete_port(self, uuid): - """Deletes a port having the specified UUID. - - :param uuid: The unique identifier of the port. - :return: A tuple with the server response and the response body. - - """ - return self._delete_request('ports', uuid) - - @base.handle_errors - def delete_portgroup(self, portgroup_ident): - """Deletes a port group having the specified UUID or name. - - :param portgroup_ident: Name or UUID of the port group. - :return: A tuple with the server response and the response body. - """ - return self._delete_request('portgroups', portgroup_ident) - - @base.handle_errors - def delete_volume_connector(self, volume_connector_ident): - """Deletes a volume connector having the specified UUID. - - :param volume_connector_ident: UUID of the volume connector. - :return: A tuple with the server response and the response body. - """ - return self._delete_request('volume/connectors', - volume_connector_ident) - - @base.handle_errors - def delete_volume_target(self, volume_target_ident): - """Deletes a volume target having the specified UUID. - - :param volume_target_ident: UUID of the volume target. - :return: A tuple with the server response and the response body. - """ - return self._delete_request('volume/targets', volume_target_ident) - - @base.handle_errors - 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. - - """ - node_attributes = ('properties/cpu_arch', - 'properties/cpus', - 'properties/local_gb', - 'properties/memory_mb', - 'driver', - 'instance_uuid', - 'resource_class') - if not patch: - patch = self._make_patch(node_attributes, **kwargs) - - return self._patch_request('nodes', uuid, patch) - - @base.handle_errors - def update_chassis(self, uuid, **kwargs): - """Update the specified chassis. - - :param uuid: The unique identifier of the chassis. - :return: A tuple with the server response and the updated chassis. - - """ - chassis_attributes = ('description',) - patch = self._make_patch(chassis_attributes, **kwargs) - - return self._patch_request('chassis', uuid, patch) - - @base.handle_errors - def update_port(self, uuid, patch): - """Update the specified port. - - :param uuid: The unique identifier of the port. - :param patch: List of dicts representing json patches. - :return: A tuple with the server response and the updated port. - - """ - - return self._patch_request('ports', uuid, patch) - - @base.handle_errors - def update_volume_connector(self, uuid, patch): - """Update the specified volume connector. - - :param uuid: The unique identifier of the volume connector. - :param patch: List of dicts representing json patches. Each dict - has keys 'path', 'op' and 'value'; to update a field. - :return: A tuple with the server response and the updated volume - connector. - """ - - return self._patch_request('volume/connectors', uuid, patch) - - @base.handle_errors - def update_volume_target(self, uuid, patch): - """Update the specified volume target. - - :param uuid: The unique identifier of the volume target. - :param patch: List of dicts representing json patches. Each dict - has keys 'path', 'op' and 'value'; to update a field. - :return: A tuple with the server response and the updated volume - target. - """ - - return self._patch_request('volume/targets', uuid, patch) - - @base.handle_errors - def set_node_power_state(self, node_uuid, state): - """Set power state of the specified node. - - :param node_uuid: The unique identifier of the node. - :param state: desired state to set (on/off/reboot). - - """ - target = {'target': state} - return self._put_request('nodes/%s/states/power' % node_uuid, - target) - - @base.handle_errors - 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. - :param state: desired state to set - (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} - # 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) - - @base.handle_errors - def set_node_raid_config(self, node_uuid, target_raid_config): - """Set raid config of the specified node. - - :param node_uuid: The unique identifier of the node. - :param target_raid_config: desired RAID configuration of the node. - """ - return self._put_request('nodes/%s/states/raid' % node_uuid, - target_raid_config) - - @base.handle_errors - def validate_driver_interface(self, node_uuid): - """Get all driver interfaces of a specific node. - - :param node_uuid: Unique identifier of the node in UUID format. - - """ - - uri = '{pref}/{res}/{uuid}/{postf}'.format(pref=self.uri_prefix, - res='nodes', - uuid=node_uuid, - postf='validate') - - return self._show_request('nodes', node_uuid, uri=uri) - - @base.handle_errors - def set_node_boot_device(self, node_uuid, boot_device, persistent=False): - """Set the boot device of the specified node. - - :param node_uuid: The unique identifier of the node. - :param boot_device: The boot device name. - :param persistent: Boolean value. True if the boot device will - persist to all future boots, False if not. - Default: False. - - """ - request = {'boot_device': boot_device, 'persistent': persistent} - resp, body = self._put_request('nodes/%s/management/boot_device' % - node_uuid, request) - self.expected_success(http_client.NO_CONTENT, resp.status) - return body - - @base.handle_errors - def get_node_boot_device(self, node_uuid): - """Get the current boot device of the specified node. - - :param node_uuid: The unique identifier of the node. - - """ - path = 'nodes/%s/management/boot_device' % node_uuid - resp, body = self._list_request(path) - self.expected_success(http_client.OK, resp.status) - return body - - @base.handle_errors - def get_node_supported_boot_devices(self, node_uuid): - """Get the supported boot devices of the specified node. - - :param node_uuid: The unique identifier of the node. - - """ - path = 'nodes/%s/management/boot_device/supported' % node_uuid - resp, body = self._list_request(path) - self.expected_success(http_client.OK, resp.status) - return body - - @base.handle_errors - def get_console(self, node_uuid): - """Get connection information about the console. - - :param node_uuid: Unique identifier of the node in UUID format. - - """ - - resp, body = self._show_request('nodes/states/console', node_uuid) - self.expected_success(http_client.OK, resp.status) - return resp, body - - @base.handle_errors - def set_console_mode(self, node_uuid, enabled): - """Start and stop the node console. - - :param node_uuid: Unique identifier of the node in UUID format. - :param enabled: Boolean value; whether to enable or disable the - console. - - """ - - enabled = {'enabled': enabled} - resp, body = self._put_request('nodes/%s/states/console' % node_uuid, - enabled) - self.expected_success(http_client.ACCEPTED, resp.status) - return resp, body - - @base.handle_errors - def vif_list(self, node_uuid, api_version=None): - """Get list of attached VIFs. - - :param node_uuid: Unique identifier of the node in UUID format. - :param api_version: Ironic API version to use. - """ - extra_headers = False - headers = None - if api_version is not None: - extra_headers = True - headers = {'x-openstack-ironic-api-version': api_version} - return self._list_request('nodes/%s/vifs' % node_uuid, - headers=headers, - extra_headers=extra_headers) - - @base.handle_errors - def vif_attach(self, node_uuid, vif_id): - """Attach a VIF to a node - - :param node_uuid: Unique identifier of the node in UUID format. - :param vif_id: An ID representing the VIF - """ - vif = {'id': vif_id} - resp = self._create_request_no_response_body( - 'nodes/%s/vifs' % node_uuid, vif) - - return resp - - @base.handle_errors - def vif_detach(self, node_uuid, vif_id): - """Detach a VIF from a node - - :param node_uuid: Unique identifier of the node in UUID format. - :param vif_id: An ID representing the VIF - """ - resp, body = self._delete_request('nodes/%s/vifs' % node_uuid, vif_id) - self.expected_success(http_client.NO_CONTENT, resp.status) - return resp, body - - @base.handle_errors - def get_driver_properties(self, driver_name): - """Get properties information about driver. - - :param driver_name: Name of driver. - :return: tuple of response and serialized properties as a dictionary. - - """ - uri = 'drivers/%s/properties' % driver_name - resp, body = self.get(uri) - self.expected_success(200, resp.status) - return resp, self.deserialize(body) - - @base.handle_errors - def get_driver_logical_disk_properties(self, driver_name): - """Get driver logical disk properties. - - :param driver_name: Name of driver. - :return: tuple of response and serialized logical disk properties as - a dictionary. - - """ - uri = 'drivers/%s/raid/logical_disk_properties' % driver_name - resp, body = self.get(uri) - self.expected_success(200, resp.status) - return resp, self.deserialize(body) diff --git a/ironic_tempest_plugin/tests/__init__.py b/ironic_tempest_plugin/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/tests/api/__init__.py b/ironic_tempest_plugin/tests/api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/tests/api/admin/__init__.py b/ironic_tempest_plugin/tests/api/admin/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py b/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py deleted file mode 100644 index ff7e09a8a4..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 fixtures - -from ironic_tempest_plugin.services.baremetal import base - - -class APIMicroversionFixture(fixtures.Fixture): - - def __init__(self, baremetal_microversion): - self.baremetal_microversion = baremetal_microversion - - def _setUp(self): - super(APIMicroversionFixture, self)._setUp() - base.set_baremetal_api_microversion(self.baremetal_microversion) - self.addCleanup(base.reset_baremetal_api_microversion) diff --git a/ironic_tempest_plugin/tests/api/admin/base.py b/ironic_tempest_plugin/tests/api/admin/base.py deleted file mode 100644 index 2e7a4ff1d5..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/base.py +++ /dev/null @@ -1,351 +0,0 @@ -# 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 functools - -from tempest import config -from tempest.lib.common import api_version_utils -from tempest.lib.common.utils import data_utils -from tempest.lib import exceptions as lib_exc -from tempest import test - -from ironic_tempest_plugin import clients -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture - -CONF = config.CONF - - -# NOTE(adam_g): The baremetal API tests exercise operations such as enroll -# node, power on, power off, etc. Testing against real drivers (ie, IPMI) -# will require passing driver-specific data to Tempest (addresses, -# credentials, etc). Until then, only support testing against the fake driver, -# which has no external dependencies. -SUPPORTED_DRIVERS = ['fake'] - -# NOTE(jroll): resources must be deleted in a specific order, this list -# defines the resource types to clean up, and the correct order. -RESOURCE_TYPES = ['port', 'portgroup', 'volume_connector', 'volume_target', - 'node', 'chassis'] - - -def creates(resource): - """Decorator that adds resources to the appropriate cleanup list.""" - - def decorator(f): - @functools.wraps(f) - def wrapper(cls, *args, **kwargs): - resp, body = f(cls, *args, **kwargs) - - if 'uuid' in body: - cls.created_objects[resource].add(body['uuid']) - - return resp, body - return wrapper - return decorator - - -class BaseBaremetalTest(api_version_utils.BaseMicroversionTest, - test.BaseTestCase): - """Base class for Baremetal API tests.""" - - credentials = ['admin'] - - @classmethod - def skip_checks(cls): - super(BaseBaremetalTest, cls).skip_checks() - if not CONF.service_available.ironic: - raise cls.skipException('Ironic is not enabled.') - if CONF.baremetal.driver not in SUPPORTED_DRIVERS: - skip_msg = ('%s skipped as Ironic driver %s is not supported for ' - 'testing.' % - (cls.__name__, CONF.baremetal.driver)) - raise cls.skipException(skip_msg) - - 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_credentials(cls): - cls.request_microversion = ( - api_version_utils.select_request_microversion( - cls.min_microversion, - CONF.baremetal.min_microversion)) - cls.services_microversion = { - CONF.baremetal.catalog_type: cls.request_microversion} - super(BaseBaremetalTest, cls).setup_credentials() - - @classmethod - def setup_clients(cls): - super(BaseBaremetalTest, cls).setup_clients() - cls.client = clients.Manager().baremetal_client - - @classmethod - def resource_setup(cls): - super(BaseBaremetalTest, cls).resource_setup() - cls.request_microversion = ( - api_version_utils.select_request_microversion( - cls.min_microversion, - CONF.baremetal.min_microversion)) - cls.driver = CONF.baremetal.driver - cls.power_timeout = CONF.baremetal.power_timeout - cls.unprovision_timeout = CONF.baremetal.unprovision_timeout - cls.created_objects = {} - for resource in RESOURCE_TYPES: - cls.created_objects[resource] = set() - - @classmethod - def resource_cleanup(cls): - """Ensure that all created objects get destroyed.""" - - try: - for resource in RESOURCE_TYPES: - uuids = cls.created_objects[resource] - delete_method = getattr(cls.client, 'delete_%s' % resource) - for u in uuids: - delete_method(u, ignore_errors=lib_exc.NotFound) - finally: - super(BaseBaremetalTest, cls).resource_cleanup() - - def _assertExpected(self, expected, actual): - """Check if expected keys/values exist in actual response body. - - Check if the expected keys and values are in the actual response body. - It will not check the keys 'created_at' and 'updated_at', since they - will always have different values. Asserts if any expected key (or - corresponding value) is not in the actual response. - - Note: this method has an underscore even though it is used outside of - this class, in order to distinguish this method from the more standard - assertXYZ methods. - - :param expected: dict of key-value pairs that are expected to be in - 'actual' dict. - :param actual: dict of key-value pairs. - - """ - for key, value in expected.items(): - if key not in ('created_at', 'updated_at'): - self.assertIn(key, actual) - self.assertEqual(value, actual[key]) - - def setUp(self): - super(BaseBaremetalTest, self).setUp() - self.useFixture(api_microversion_fixture.APIMicroversionFixture( - self.request_microversion)) - - @classmethod - @creates('chassis') - def create_chassis(cls, description=None, **kwargs): - """Wrapper utility for creating test chassis. - - :param description: A description of the chassis. If not supplied, - a random value will be generated. - :return: A tuple with the server response and the created chassis. - - """ - description = description or data_utils.rand_name('test-chassis') - resp, body = cls.client.create_chassis(description=description, - **kwargs) - return resp, body - - @classmethod - @creates('node') - def create_node(cls, chassis_id, cpu_arch='x86', 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 cpus: Number of CPUs. Default: 8. - :param local_gb: Disk size. Default: 10. - :param memory_mb: Available RAM. Default: 4096. - :param resource_class: Node resource class. - :return: A tuple with the server response and the created node. - - """ - resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch, - cpus=cpus, local_gb=local_gb, - memory_mb=memory_mb, - driver=cls.driver, - resource_class=resource_class) - - return resp, body - - @classmethod - @creates('port') - def create_port(cls, node_id, address, extra=None, uuid=None, - portgroup_uuid=None, physical_network=None): - """Wrapper utility for creating test ports. - - :param node_id: The unique identifier of the node. - :param address: MAC address of the port. - :param extra: Meta data of the port. If not supplied, an empty - dictionary will be created. - :param uuid: UUID of the port. - :param portgroup_uuid: The UUID of a portgroup of which this port is a - member. - :param physical_network: The physical network to which the port is - attached. - :return: A tuple with the server response and the created port. - - """ - extra = extra or {} - resp, body = cls.client.create_port(address=address, node_id=node_id, - extra=extra, uuid=uuid, - portgroup_uuid=portgroup_uuid, - physical_network=physical_network) - - return resp, body - - @classmethod - @creates('portgroup') - def create_portgroup(cls, node_uuid, **kwargs): - """Wrapper utility for creating test port groups. - - :param node_uuid: The unique identifier of the node. - :return: A tuple with the server response and the created port group. - """ - resp, body = cls.client.create_portgroup(node_uuid=node_uuid, **kwargs) - - return resp, body - - @classmethod - @creates('volume_connector') - def create_volume_connector(cls, node_uuid, **kwargs): - """Wrapper utility for creating test volume connector. - - :param node_uuid: The unique identifier of the node. - :return: A tuple with the server response and the created volume - connector. - """ - resp, body = cls.client.create_volume_connector(node_uuid=node_uuid, - **kwargs) - - return resp, body - - @classmethod - @creates('volume_target') - def create_volume_target(cls, node_uuid, **kwargs): - """Wrapper utility for creating test volume target. - - :param node_uuid: The unique identifier of the node. - :return: A tuple with the server response and the created volume - target. - """ - resp, body = cls.client.create_volume_target(node_uuid=node_uuid, - **kwargs) - - return resp, body - - @classmethod - def delete_chassis(cls, chassis_id): - """Deletes a chassis having the specified UUID. - - :param chassis_id: The unique identifier of the chassis. - :return: Server response. - - """ - - resp, body = cls.client.delete_chassis(chassis_id) - - if chassis_id in cls.created_objects['chassis']: - cls.created_objects['chassis'].remove(chassis_id) - - return resp - - @classmethod - def delete_node(cls, node_id): - """Deletes a node having the specified UUID. - - :param node_id: The unique identifier of the node. - :return: Server response. - - """ - - resp, body = cls.client.delete_node(node_id) - - if node_id in cls.created_objects['node']: - cls.created_objects['node'].remove(node_id) - - return resp - - @classmethod - def delete_port(cls, port_id): - """Deletes a port having the specified UUID. - - :param port_id: The unique identifier of the port. - :return: Server response. - - """ - - resp, body = cls.client.delete_port(port_id) - - if port_id in cls.created_objects['port']: - cls.created_objects['port'].remove(port_id) - - return resp - - @classmethod - def delete_portgroup(cls, portgroup_ident): - """Deletes a port group having the specified UUID or name. - - :param portgroup_ident: The name or UUID of the port group. - :return: Server response. - """ - resp, body = cls.client.delete_portgroup(portgroup_ident) - - if portgroup_ident in cls.created_objects['portgroup']: - cls.created_objects['portgroup'].remove(portgroup_ident) - - return resp - - @classmethod - def delete_volume_connector(cls, volume_connector_id): - """Deletes a volume connector having the specified UUID. - - :param volume_connector_id: The UUID of the volume connector. - :return: Server response. - """ - resp, body = cls.client.delete_volume_connector(volume_connector_id) - - if volume_connector_id in cls.created_objects['volume_connector']: - cls.created_objects['volume_connector'].remove( - volume_connector_id) - - return resp - - @classmethod - def delete_volume_target(cls, volume_target_id): - """Deletes a volume target having the specified UUID. - - :param volume_target_id: The UUID of the volume target. - :return: Server response. - """ - resp, body = cls.client.delete_volume_target(volume_target_id) - - if volume_target_id in cls.created_objects['volume_target']: - cls.created_objects['volume_target'].remove(volume_target_id) - - return resp - - def validate_self_link(self, resource, uuid, link): - """Check whether the given self link formatted correctly.""" - expected_link = "{base}/{pref}/{res}/{uuid}".format( - base=self.client.base_url.rstrip('/'), - pref=self.client.uri_prefix, - res=resource, - uuid=uuid) - self.assertEqual(expected_link, link) diff --git a/ironic_tempest_plugin/tests/api/admin/test_api_discovery.py b/ironic_tempest_plugin/tests/api/admin/test_api_discovery.py deleted file mode 100644 index de2dada325..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_api_discovery.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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.lib import decorators - -from ironic_tempest_plugin.tests.api.admin import base - - -class TestApiDiscovery(base.BaseBaremetalTest): - """Tests for API discovery features.""" - - @decorators.idempotent_id('a3c27e94-f56c-42c4-8600-d6790650b9c5') - def test_api_versions(self): - _, descr = self.client.get_api_description() - expected_versions = ('v1',) - versions = [version['id'] for version in descr['versions']] - - for v in expected_versions: - self.assertIn(v, versions) - - @decorators.idempotent_id('896283a6-488e-4f31-af78-6614286cbe0d') - def test_default_version(self): - _, descr = self.client.get_api_description() - default_version = descr['default_version'] - self.assertEqual('v1', default_version['id']) - - @decorators.idempotent_id('abc0b34d-e684-4546-9728-ab7a9ad9f174') - def test_version_1_resources(self): - _, descr = self.client.get_version_description(version='v1') - expected_resources = ('nodes', 'chassis', - 'ports', 'links', 'media_types') - - for res in expected_resources: - self.assertIn(res, descr) diff --git a/ironic_tempest_plugin/tests/api/admin/test_chassis.py b/ironic_tempest_plugin/tests/api/admin/test_chassis.py deleted file mode 100644 index 19bdc136af..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_chassis.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# 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.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.tests.api.admin import base - - -class TestChassis(base.BaseBaremetalTest): - """Tests for chassis.""" - - @classmethod - def resource_setup(cls): - super(TestChassis, cls).resource_setup() - _, cls.chassis = cls.create_chassis() - - @decorators.idempotent_id('7c5a2e09-699c-44be-89ed-2bc189992d42') - def test_create_chassis(self): - descr = data_utils.rand_name('test-chassis') - _, chassis = self.create_chassis(description=descr) - self.assertEqual(descr, chassis['description']) - - @decorators.idempotent_id('cabe9c6f-dc16-41a7-b6b9-0a90c212edd5') - def test_create_chassis_unicode_description(self): - # Use a unicode string for testing: - # 'We ♡ OpenStack in Ukraine' - descr = u'В Україні ♡ OpenStack!' - _, chassis = self.create_chassis(description=descr) - self.assertEqual(descr, chassis['description']) - - @decorators.idempotent_id('c84644df-31c4-49db-a307-8942881f41c0') - def test_show_chassis(self): - _, chassis = self.client.show_chassis(self.chassis['uuid']) - self._assertExpected(self.chassis, chassis) - - @decorators.idempotent_id('29c9cd3f-19b5-417b-9864-99512c3b33b3') - def test_list_chassis(self): - _, body = self.client.list_chassis() - self.assertIn(self.chassis['uuid'], - [i['uuid'] for i in body['chassis']]) - - @decorators.idempotent_id('5ae649ad-22d1-4fe1-bbc6-97227d199fb3') - def test_delete_chassis(self): - _, body = self.create_chassis() - uuid = body['uuid'] - - self.delete_chassis(uuid) - self.assertRaises(lib_exc.NotFound, self.client.show_chassis, uuid) - - @decorators.idempotent_id('cda8a41f-6be2-4cbf-840c-994b00a89b44') - def test_update_chassis(self): - _, body = self.create_chassis() - uuid = body['uuid'] - - new_description = data_utils.rand_name('new-description') - _, body = (self.client.update_chassis(uuid, - description=new_description)) - _, chassis = self.client.show_chassis(uuid) - self.assertEqual(new_description, chassis['description']) - - @decorators.idempotent_id('76305e22-a4e2-4ab3-855c-f4e2368b9335') - def test_chassis_node_list(self): - _, node = self.create_node(self.chassis['uuid']) - _, body = self.client.list_chassis_nodes(self.chassis['uuid']) - self.assertIn(node['uuid'], [n['uuid'] for n in body['nodes']]) - - @decorators.idempotent_id('dd52bd5d-610c-4f2c-8fa3-d5e59269325f') - def test_create_chassis_uuid(self): - uuid = data_utils.rand_uuid() - _, chassis = self.create_chassis(uuid=uuid) - self.assertEqual(uuid, chassis['uuid']) diff --git a/ironic_tempest_plugin/tests/api/admin/test_drivers.py b/ironic_tempest_plugin/tests/api/admin/test_drivers.py deleted file mode 100644 index e80a96360e..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_drivers.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright 2014 NEC Corporation. All rights reserved. -# -# 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.lib import decorators - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - -CONF = config.CONF - - -class TestDrivers(base.BaseBaremetalTest): - """Tests for drivers.""" - - @classmethod - def resource_setup(cls): - super(TestDrivers, cls).resource_setup() - cls.driver_name = CONF.baremetal.driver - - @decorators.idempotent_id('5aed2790-7592-4655-9b16-99abcc2e6ec5') - def test_list_drivers(self): - _, drivers = self.client.list_drivers() - self.assertIn(self.driver_name, - [d['name'] for d in drivers['drivers']]) - - @decorators.idempotent_id('fb3287a3-c4d7-44bf-ae9d-1eef906d78ce') - def test_show_driver(self): - _, driver = self.client.show_driver(self.driver_name) - self.assertEqual(self.driver_name, driver['name']) - - @decorators.idempotent_id('6efa976f-78a2-4859-b3aa-97d960d6e5e5') - def test_driver_properties(self): - _, properties = self.client.get_driver_properties(self.driver_name) - self.assertNotEmpty(properties) - - @decorators.idempotent_id('fdf61f5a-f59d-4235-ad6c-cc718740e3e3') - def test_driver_logical_disk_properties(self): - self.useFixture( - api_microversion_fixture.APIMicroversionFixture('1.12')) - _, properties = self.client.get_driver_logical_disk_properties( - self.driver_name) - self.assertNotEmpty(properties) diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodes.py b/ironic_tempest_plugin/tests/api/admin/test_nodes.py deleted file mode 100644 index d992a65e24..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_nodes.py +++ /dev/null @@ -1,403 +0,0 @@ -# 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 six -from tempest import config -from tempest.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.common import waiters -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - -CONF = config.CONF - - -class TestNodes(base.BaseBaremetalTest): - """Tests for baremetal nodes.""" - - def setUp(self): - super(TestNodes, self).setUp() - - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - - def _associate_node_with_instance(self): - self.client.set_node_power_state(self.node['uuid'], 'power off') - waiters.wait_for_bm_node_status(self.client, self.node['uuid'], - 'power_state', 'power off') - instance_uuid = data_utils.rand_uuid() - self.client.update_node(self.node['uuid'], - instance_uuid=instance_uuid) - self.addCleanup(self.client.update_node, - uuid=self.node['uuid'], instance_uuid=None) - return instance_uuid - - @decorators.idempotent_id('4e939eb2-8a69-4e84-8652-6fffcbc9db8f') - def test_create_node(self): - params = {'cpu_arch': 'x86_64', - 'cpus': '12', - 'local_gb': '10', - 'memory_mb': '1024'} - - _, body = self.create_node(self.chassis['uuid'], **params) - self._assertExpected(params, body['properties']) - - @decorators.idempotent_id('9ade60a4-505e-4259-9ec4-71352cbbaf47') - def test_delete_node(self): - _, node = self.create_node(self.chassis['uuid']) - - self.delete_node(node['uuid']) - - self.assertRaises(lib_exc.NotFound, self.client.show_node, - node['uuid']) - - @decorators.idempotent_id('55451300-057c-4ecf-8255-ba42a83d3a03') - def test_show_node(self): - _, loaded_node = self.client.show_node(self.node['uuid']) - self._assertExpected(self.node, loaded_node) - - @decorators.idempotent_id('4ca123c4-160d-4d8d-a3f7-15feda812263') - def test_list_nodes(self): - _, body = self.client.list_nodes() - self.assertIn(self.node['uuid'], - [i['uuid'] for i in body['nodes']]) - - @decorators.idempotent_id('85b1f6e0-57fd-424c-aeff-c3422920556f') - def test_list_nodes_association(self): - _, body = self.client.list_nodes(associated=True) - self.assertNotIn(self.node['uuid'], - [n['uuid'] for n in body['nodes']]) - - self._associate_node_with_instance() - - _, body = self.client.list_nodes(associated=True) - self.assertIn(self.node['uuid'], [n['uuid'] for n in body['nodes']]) - - _, body = self.client.list_nodes(associated=False) - self.assertNotIn(self.node['uuid'], [n['uuid'] for n in body['nodes']]) - - @decorators.idempotent_id('18c4ebd8-f83a-4df7-9653-9fb33a329730') - def test_node_port_list(self): - _, port = self.create_port(self.node['uuid'], - data_utils.rand_mac_address()) - _, body = self.client.list_node_ports(self.node['uuid']) - self.assertIn(port['uuid'], - [p['uuid'] for p in body['ports']]) - - @decorators.idempotent_id('72591acb-f215-49db-8395-710d14eb86ab') - def test_node_port_list_no_ports(self): - _, node = self.create_node(self.chassis['uuid']) - _, body = self.client.list_node_ports(node['uuid']) - self.assertEmpty(body['ports']) - - @decorators.idempotent_id('4fed270a-677a-4d19-be87-fd38ae490320') - def test_update_node(self): - props = {'cpu_arch': 'x86_64', - 'cpus': '12', - 'local_gb': '10', - 'memory_mb': '128'} - - _, node = self.create_node(self.chassis['uuid'], **props) - - new_p = {'cpu_arch': 'x86', - 'cpus': '1', - 'local_gb': '10000', - 'memory_mb': '12300'} - - _, body = self.client.update_node(node['uuid'], properties=new_p) - _, node = self.client.show_node(node['uuid']) - self._assertExpected(new_p, node['properties']) - - @decorators.idempotent_id('cbf1f515-5f4b-4e49-945c-86bcaccfeb1d') - def test_validate_driver_interface(self): - _, body = self.client.validate_driver_interface(self.node['uuid']) - core_interfaces = ['power', 'deploy'] - for interface in core_interfaces: - self.assertIn(interface, body) - - @decorators.idempotent_id('5519371c-26a2-46e9-aa1a-f74226e9d71f') - def test_set_node_boot_device(self): - self.client.set_node_boot_device(self.node['uuid'], 'pxe') - - @decorators.idempotent_id('9ea73775-f578-40b9-bc34-efc639c4f21f') - def test_get_node_boot_device(self): - body = self.client.get_node_boot_device(self.node['uuid']) - self.assertIn('boot_device', body) - self.assertIn('persistent', body) - self.assertIsInstance(body['boot_device'], six.string_types) - self.assertIsInstance(body['persistent'], bool) - - @decorators.idempotent_id('3622bc6f-3589-4bc2-89f3-50419c66b133') - def test_get_node_supported_boot_devices(self): - body = self.client.get_node_supported_boot_devices(self.node['uuid']) - self.assertIn('supported_boot_devices', body) - self.assertIsInstance(body['supported_boot_devices'], list) - - @decorators.idempotent_id('f63b6288-1137-4426-8cfe-0d5b7eb87c06') - def test_get_console(self): - _, body = self.client.get_console(self.node['uuid']) - con_info = ['console_enabled', 'console_info'] - for key in con_info: - self.assertIn(key, body) - - @decorators.idempotent_id('80504575-9b21-4670-92d1-143b948f9437') - def test_set_console_mode(self): - self.client.set_console_mode(self.node['uuid'], True) - waiters.wait_for_bm_node_status(self.client, self.node['uuid'], - 'console_enabled', True) - - @decorators.idempotent_id('b02a4f38-5e8b-44b2-aed2-a69a36ecfd69') - def test_get_node_by_instance_uuid(self): - instance_uuid = self._associate_node_with_instance() - _, body = self.client.show_node_by_instance_uuid(instance_uuid) - self.assertEqual(1, len(body['nodes'])) - self.assertIn(self.node['uuid'], [n['uuid'] for n in body['nodes']]) - - -class TestNodesResourceClass(base.BaseBaremetalTest): - - min_microversion = '1.21' - - def setUp(self): - super(TestNodesResourceClass, self).setUp() - self.useFixture( - api_microversion_fixture.APIMicroversionFixture( - TestNodesResourceClass.min_microversion) - ) - _, self.chassis = self.create_chassis() - self.resource_class = data_utils.rand_name(name='Resource_Class') - _, self.node = self.create_node( - self.chassis['uuid'], resource_class=self.resource_class) - - @decorators.idempotent_id('2a00340c-8152-4a61-9fc5-0b3cdefec258') - def test_create_node_resource_class_long(self): - """Create new node with specified longest name of resource class.""" - res_class_long_name = data_utils.arbitrary_string(80) - _, body = self.create_node( - self.chassis['uuid'], - resource_class=res_class_long_name) - self.assertEqual(res_class_long_name, body['resource_class']) - - @decorators.idempotent_id('142db00d-ac0f-415b-8da8-9095fbb561f7') - def test_update_node_resource_class(self): - """Update existing node with specified resource class.""" - new_res_class_name = data_utils.rand_name(name='Resource_Class') - _, body = self.client.update_node( - self.node['uuid'], resource_class=new_res_class_name) - _, body = self.client.show_node(self.node['uuid']) - self.assertEqual(new_res_class_name, body['resource_class']) - - @decorators.idempotent_id('73e6f7b5-3e51-49ea-af5b-146cd49f40ee') - def test_show_node_resource_class(self): - """Show resource class field of specified node.""" - _, body = self.client.show_node(self.node['uuid']) - self.assertEqual(self.resource_class, body['resource_class']) - - @decorators.idempotent_id('f2bf4465-280c-4fdc-bbf7-fcf5188befa4') - def test_list_nodes_resource_class(self): - """List nodes of specified resource class only.""" - res_class = 'ResClass-{0}'.format(data_utils.rand_uuid()) - for node in range(3): - _, body = self.create_node( - self.chassis['uuid'], resource_class=res_class) - - _, body = self.client.list_nodes(resource_class=res_class) - self.assertEqual(3, len([i['uuid'] for i in body['nodes']])) - - @decorators.idempotent_id('40733bad-bb79-445e-a094-530a44042995') - def test_list_nodes_detail_resource_class(self): - """Get detailed nodes list of specified resource class only.""" - res_class = 'ResClass-{0}'.format(data_utils.rand_uuid()) - for node in range(3): - _, body = self.create_node( - self.chassis['uuid'], resource_class=res_class) - - _, body = self.client.list_nodes_detail(resource_class=res_class) - self.assertEqual(3, len([i['uuid'] for i in body['nodes']])) - - for node in body['nodes']: - self.assertEqual(res_class, node['resource_class']) - - @decorators.attr(type='negative') - @decorators.idempotent_id('e75136d4-0690-48a5-aef3-75040aee73ad') - def test_create_node_resource_class_too_long(self): - """Try to create a node with too long resource class name.""" - resource_class = data_utils.arbitrary_string(81) - self.assertRaises(lib_exc.BadRequest, self.create_node, - self.chassis['uuid'], resource_class=resource_class) - - @decorators.attr(type='negative') - @decorators.idempotent_id('f0aeece4-8671-44ea-a482-b4047fc4cf74') - def test_update_node_resource_class_too_long(self): - """Try to update a node with too long resource class name.""" - resource_class = data_utils.arbitrary_string(81) - self.assertRaises(lib_exc.BadRequest, self.client.update_node, - self.node['uuid'], resource_class=resource_class) - - -class TestNodesResourceClassOldApi(base.BaseBaremetalTest): - - def setUp(self): - super(TestNodesResourceClassOldApi, self).setUp() - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - - @decorators.attr(type='negative') - @decorators.idempotent_id('2c364408-4746-4b3c-9821-20d47b57bdec') - def test_create_node_resource_class_old_api(self): - """Try to create a node with resource class using older api version.""" - resource_class = data_utils.arbitrary_string() - self.assertRaises(lib_exc.UnexpectedResponseCode, self.create_node, - self.chassis['uuid'], resource_class=resource_class) - - @decorators.attr(type='negative') - @decorators.idempotent_id('666f3c1a-4922-4a3d-b6d9-dea7c74d30bc') - def test_update_node_resource_class_old_api(self): - """Try to update a node with resource class using older api version.""" - resource_class = data_utils.arbitrary_string() - self.assertRaises(lib_exc.UnexpectedResponseCode, - self.client.update_node, - self.node['uuid'], resource_class=resource_class) - - @decorators.attr(type='negative') - @decorators.idempotent_id('95903480-f16d-4774-8775-6c7f87b27c59') - def test_list_nodes_by_resource_class_old_api(self): - """Try to list nodes with resource class using older api version.""" - resource_class = data_utils.arbitrary_string() - self.assertRaises( - lib_exc.UnexpectedResponseCode, - self.client.list_nodes, resource_class=resource_class) - self.assertRaises( - lib_exc.UnexpectedResponseCode, - self.client.list_nodes_detail, resource_class=resource_class) - - -class TestNodesVif(base.BaseBaremetalTest): - - min_microversion = '1.28' - - @classmethod - def skip_checks(cls): - super(TestNodesVif, cls).skip_checks() - if not CONF.service_available.neutron: - raise cls.skipException('Neutron is not enabled.') - - def setUp(self): - super(TestNodesVif, self).setUp() - - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - if CONF.network.shared_physical_network: - self.net = self.os_admin.networks_client.list_networks( - name=CONF.compute.fixed_network_name)['networks'][0] - else: - self.net = self.os_admin.networks_client.\ - create_network()['network'] - self.addCleanup(self.os_admin.networks_client.delete_network, - self.net['id']) - - self.nport_id = self.os_admin.ports_client.create_port( - network_id=self.net['id'])['port']['id'] - self.addCleanup(self.os_admin.ports_client.delete_port, - self.nport_id) - - @decorators.idempotent_id('a3d319d0-cacb-4e55-a3dc-3fa8b74880f1') - def test_vif_on_port(self): - """Test attachment and detachment of VIFs on the node with port. - - Test steps: - 1) Create chassis and node in setUp. - 2) Create port for the node. - 3) Attach VIF to the node. - 4) Check VIF info in VIFs list and port internal_info. - 5) Detach VIF from the node. - 6) Check that no more VIF info in VIFs list and port internal_info. - """ - self.useFixture( - api_microversion_fixture.APIMicroversionFixture('1.28')) - _, self.port = self.create_port(self.node['uuid'], - data_utils.rand_mac_address()) - self.client.vif_attach(self.node['uuid'], self.nport_id) - _, body = self.client.vif_list(self.node['uuid']) - self.assertEqual({'vifs': [{'id': self.nport_id}]}, body) - _, port = self.client.show_port(self.port['uuid']) - self.assertEqual(self.nport_id, - port['internal_info']['tenant_vif_port_id']) - self.client.vif_detach(self.node['uuid'], self.nport_id) - _, body = self.client.vif_list(self.node['uuid']) - self.assertEqual({'vifs': []}, body) - _, port = self.client.show_port(self.port['uuid']) - self.assertNotIn('tenant_vif_port_id', port['internal_info']) - - @decorators.idempotent_id('95279515-7d0a-4f5f-987f-93e36aae5585') - def test_vif_on_portgroup(self): - """Test attachment and detachment of VIFs on the node with port group. - - Test steps: - 1) Create chassis and node in setUp. - 2) Create port for the node. - 3) Create port group for the node. - 4) Plug port into port group. - 5) Attach VIF to the node. - 6) Check VIF info in VIFs list and port group internal_info, but - not in port internal_info. - 7) Detach VIF from the node. - 8) Check that no VIF info in VIFs list and port group internal_info. - """ - self.useFixture( - api_microversion_fixture.APIMicroversionFixture('1.28')) - _, self.port = self.create_port(self.node['uuid'], - data_utils.rand_mac_address()) - _, self.portgroup = self.create_portgroup( - self.node['uuid'], address=data_utils.rand_mac_address()) - - patch = [{'path': '/portgroup_uuid', - 'op': 'add', - 'value': self.portgroup['uuid']}] - self.client.update_port(self.port['uuid'], patch) - - self.client.vif_attach(self.node['uuid'], self.nport_id) - _, body = self.client.vif_list(self.node['uuid']) - self.assertEqual({'vifs': [{'id': self.nport_id}]}, body) - - _, port = self.client.show_port(self.port['uuid']) - self.assertNotIn('tenant_vif_port_id', port['internal_info']) - _, portgroup = self.client.show_portgroup(self.portgroup['uuid']) - self.assertEqual(self.nport_id, - portgroup['internal_info']['tenant_vif_port_id']) - - self.client.vif_detach(self.node['uuid'], self.nport_id) - _, body = self.client.vif_list(self.node['uuid']) - self.assertEqual({'vifs': []}, body) - _, portgroup = self.client.show_portgroup(self.portgroup['uuid']) - self.assertNotIn('tenant_vif_port_id', portgroup['internal_info']) - - @decorators.idempotent_id('a3d319d0-cacb-4e55-a3dc-3fa8b74880f2') - def test_vif_already_set_on_extra(self): - self.useFixture( - api_microversion_fixture.APIMicroversionFixture('1.28')) - _, self.port = self.create_port(self.node['uuid'], - data_utils.rand_mac_address()) - patch = [{'path': '/extra/vif_port_id', - 'op': 'add', - 'value': self.nport_id}] - self.client.update_port(self.port['uuid'], patch) - - _, body = self.client.vif_list(self.node['uuid']) - self.assertEqual({'vifs': [{'id': self.nport_id}]}, body) - - self.assertRaises(lib_exc.Conflict, self.client.vif_attach, - self.node['uuid'], self.nport_id) - - self.client.vif_detach(self.node['uuid'], self.nport_id) diff --git a/ironic_tempest_plugin/tests/api/admin/test_nodestates.py b/ironic_tempest_plugin/tests/api/admin/test_nodestates.py deleted file mode 100644 index f2a33eb32b..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_nodestates.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright 2014 NEC Corporation. All rights reserved. -# -# 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_utils import timeutils -from tempest.lib import decorators -from tempest.lib import exceptions - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - - -class TestNodeStatesMixin(object): - """Mixin for for baremetal node states tests.""" - - @classmethod - def resource_setup(cls): - super(TestNodeStatesMixin, cls).resource_setup() - _, cls.chassis = cls.create_chassis() - - def _validate_power_state(self, node_uuid, power_state): - # Validate that power state is set within timeout - if power_state == 'rebooting': - power_state = 'power on' - start = timeutils.utcnow() - while timeutils.delta_seconds( - start, timeutils.utcnow()) < self.power_timeout: - _, node = self.client.show_node(node_uuid) - if node['power_state'] == power_state: - return - message = ('Failed to set power state within ' - 'the required time: %s sec.' % self.power_timeout) - raise exceptions.TimeoutException(message) - - def _validate_provision_state(self, node_uuid, target_state): - # Validate that provision state is set within timeout - start = timeutils.utcnow() - while timeutils.delta_seconds( - start, timeutils.utcnow()) < self.unprovision_timeout: - _, node = self.client.show_node(node_uuid) - if node['provision_state'] == target_state: - return - message = ('Failed to set provision state %(state)s within ' - 'the required time: %(timeout)s sec.', - {'state': target_state, - 'timeout': self.unprovision_timeout}) - raise exceptions.TimeoutException(message) - - @decorators.idempotent_id('cd8afa5e-3f57-4e43-8185-beb83d3c9015') - def test_list_nodestates(self): - _, node = self.create_node(self.chassis['uuid']) - _, nodestates = self.client.list_nodestates(node['uuid']) - for key in nodestates: - self.assertEqual(nodestates[key], node[key]) - - @decorators.idempotent_id('fc5b9320-0c98-4e5a-8848-877fe5a0322c') - def test_set_node_power_state(self): - _, node = self.create_node(self.chassis['uuid']) - states = ["power on", "rebooting", "power off"] - for state in states: - # Set power state - self.client.set_node_power_state(node['uuid'], state) - # Check power state after state is set - self._validate_power_state(node['uuid'], state) - - -class TestNodeStatesV1_1(TestNodeStatesMixin, base.BaseBaremetalTest): - - @decorators.idempotent_id('ccb8fca9-2ba0-480c-a037-34c3bd09dc74') - def test_set_node_provision_state(self): - _, node = self.create_node(self.chassis['uuid']) - # Nodes appear in NONE state by default until v1.1 - self.assertIsNone(node['provision_state']) - provision_states_list = ['active', 'deleted'] - target_states_list = ['active', None] - for (provision_state, target_state) in zip(provision_states_list, - target_states_list): - self.client.set_node_provision_state(node['uuid'], provision_state) - self._validate_provision_state(node['uuid'], target_state) - - -class TestNodeStatesV1_2(TestNodeStatesMixin, base.BaseBaremetalTest): - - def setUp(self): - super(TestNodeStatesV1_2, self).setUp() - self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.2')) - - @decorators.idempotent_id('9c414984-f3b6-4b3d-81da-93b60d4662fb') - def test_set_node_provision_state(self): - _, node = self.create_node(self.chassis['uuid']) - # Nodes appear in AVAILABLE state by default from v1.2 to v1.10 - self.assertEqual('available', node['provision_state']) - provision_states_list = ['active', 'deleted'] - target_states_list = ['active', 'available'] - for (provision_state, target_state) in zip(provision_states_list, - target_states_list): - self.client.set_node_provision_state(node['uuid'], provision_state) - self._validate_provision_state(node['uuid'], target_state) - - -class TestNodeStatesV1_4(TestNodeStatesMixin, base.BaseBaremetalTest): - - def setUp(self): - super(TestNodeStatesV1_4, self).setUp() - self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.4')) - - @decorators.idempotent_id('3d606003-05ce-4b5a-964d-bdee382fafe9') - def test_set_node_provision_state(self): - _, node = self.create_node(self.chassis['uuid']) - # Nodes appear in AVAILABLE state by default from v1.2 to v1.10 - self.assertEqual('available', node['provision_state']) - # MANAGEABLE state and PROVIDE transition have been added in v1.4 - provision_states_list = [ - 'manage', 'provide', 'active', 'deleted'] - target_states_list = [ - 'manageable', 'available', 'active', 'available'] - for (provision_state, target_state) in zip(provision_states_list, - target_states_list): - self.client.set_node_provision_state(node['uuid'], provision_state) - self._validate_provision_state(node['uuid'], target_state) - - -class TestNodeStatesV1_6(TestNodeStatesMixin, base.BaseBaremetalTest): - - def setUp(self): - super(TestNodeStatesV1_6, self).setUp() - self.useFixture(api_microversion_fixture.APIMicroversionFixture('1.6')) - - @decorators.idempotent_id('6c9ce4a3-713b-4c76-91af-18c48d01f1bb') - def test_set_node_provision_state(self): - _, node = self.create_node(self.chassis['uuid']) - # Nodes appear in AVAILABLE state by default from v1.2 to v1.10 - self.assertEqual('available', node['provision_state']) - # INSPECT* states have been added in v1.6 - provision_states_list = [ - 'manage', 'inspect', 'provide', 'active', 'deleted'] - target_states_list = [ - 'manageable', 'manageable', 'available', 'active', 'available'] - for (provision_state, target_state) in zip(provision_states_list, - target_states_list): - self.client.set_node_provision_state(node['uuid'], provision_state) - self._validate_provision_state(node['uuid'], target_state) - - -class TestNodeStatesV1_11(TestNodeStatesMixin, base.BaseBaremetalTest): - - def setUp(self): - super(TestNodeStatesV1_11, self).setUp() - self.useFixture( - api_microversion_fixture.APIMicroversionFixture('1.11') - ) - - @decorators.idempotent_id('31f53828-b83d-40c7-98e5-843e28a1b6b9') - def test_set_node_provision_state(self): - _, node = self.create_node(self.chassis['uuid']) - # Nodes appear in ENROLL state by default from v1.11 - self.assertEqual('enroll', node['provision_state']) - provision_states_list = [ - 'manage', 'inspect', 'provide', 'active', 'deleted'] - target_states_list = [ - 'manageable', 'manageable', 'available', 'active', 'available'] - for (provision_state, target_state) in zip(provision_states_list, - target_states_list): - self.client.set_node_provision_state(node['uuid'], provision_state) - self._validate_provision_state(node['uuid'], target_state) - - -class TestNodeStatesV1_12(TestNodeStatesMixin, base.BaseBaremetalTest): - - def setUp(self): - super(TestNodeStatesV1_12, self).setUp() - self.useFixture( - api_microversion_fixture.APIMicroversionFixture('1.12') - ) - - @decorators.idempotent_id('4427b1ca-8e79-4139-83d6-77dfac03e61e') - def test_set_node_raid_config(self): - _, node = self.create_node(self.chassis['uuid']) - target_raid_config = {'logical_disks': [{'size_gb': 100, - 'raid_level': '1'}]} - self.client.set_node_raid_config(node['uuid'], target_raid_config) - _, ret = self.client.show_node(node['uuid']) - self.assertEqual(target_raid_config, ret['target_raid_config']) diff --git a/ironic_tempest_plugin/tests/api/admin/test_portgroups.py b/ironic_tempest_plugin/tests/api/admin/test_portgroups.py deleted file mode 100644 index 4a5b84bd6b..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_portgroups.py +++ /dev/null @@ -1,74 +0,0 @@ -# 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.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - - -class TestPortGroups(base.BaseBaremetalTest): - """Basic positive test cases for port groups.""" - - min_microversion = '1.23' - - def setUp(self): - super(TestPortGroups, self).setUp() - self.useFixture( - api_microversion_fixture.APIMicroversionFixture( - self.min_microversion)) - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - _, self.portgroup = self.create_portgroup( - self.node['uuid'], address=data_utils.rand_mac_address(), - name=data_utils.rand_name('portgroup')) - - @decorators.idempotent_id('110cd302-256b-4ddc-be10-fc6c9ad8e649') - def test_create_portgroup_with_address(self): - """Create a port group with specific MAC address.""" - _, body = self.client.show_portgroup(self.portgroup['uuid']) - self.assertEqual(self.portgroup['address'], body['address']) - - @decorators.idempotent_id('4336fa0f-da86-4cec-b788-89f59a7635a5') - def test_create_portgroup_no_address(self): - """Create a port group without setting MAC address.""" - _, portgroup = self.create_portgroup(self.node['uuid']) - _, body = self.client.show_portgroup(portgroup['uuid']) - - self._assertExpected(portgroup, body) - self.assertIsNone(body['address']) - - @decorators.idempotent_id('8378c69f-f806-454b-8ddd-6b7fd93ab12b') - def test_delete_portgroup(self): - """Delete a port group.""" - self.delete_portgroup(self.portgroup['uuid']) - self.assertRaises(lib_exc.NotFound, self.client.show_portgroup, - self.portgroup['uuid']) - - @decorators.idempotent_id('f6be5e70-3e3b-435c-b2fc-bbb2cc9b3185') - def test_show_portgroup(self): - """Show a specified port group.""" - _, portgroup = self.client.show_portgroup(self.portgroup['uuid']) - self._assertExpected(self.portgroup, portgroup) - - @decorators.idempotent_id('cf2dfd95-5ea1-4109-8ad3-297cd76aa5d3') - def test_list_portgroups(self): - """List port groups.""" - _, body = self.client.list_portgroups() - self.assertIn(self.portgroup['uuid'], - [i['uuid'] for i in body['portgroups']]) - self.assertIn(self.portgroup['address'], - [i['address'] for i in body['portgroups']]) - self.assertIn(self.portgroup['name'], - [i['name'] for i in body['portgroups']]) diff --git a/ironic_tempest_plugin/tests/api/admin/test_ports.py b/ironic_tempest_plugin/tests/api/admin/test_ports.py deleted file mode 100644 index a4aea4f268..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_ports.py +++ /dev/null @@ -1,376 +0,0 @@ -# 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.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - - -class TestPorts(base.BaseBaremetalTest): - """Tests for ports.""" - - def setUp(self): - super(TestPorts, self).setUp() - - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - _, self.port = self.create_port(self.node['uuid'], - data_utils.rand_mac_address()) - - @decorators.idempotent_id('83975898-2e50-42ed-b5f0-e510e36a0b56') - def test_create_port(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - - _, body = self.client.show_port(port['uuid']) - - self._assertExpected(port, body) - - @decorators.idempotent_id('d1f6b249-4cf6-4fe6-9ed6-a6e84b1bf67b') - def test_create_port_specifying_uuid(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - uuid = data_utils.rand_uuid() - - _, port = self.create_port(node_id=node_id, - address=address, uuid=uuid) - - _, body = self.client.show_port(uuid) - self._assertExpected(port, body) - - @decorators.idempotent_id('4a02c4b0-6573-42a4-a513-2e36ad485b62') - def test_create_port_with_extra(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'str': 'value', 'int': 123, 'float': 0.123, - 'bool': True, 'list': [1, 2, 3], 'dict': {'foo': 'bar'}} - - _, port = self.create_port(node_id=node_id, address=address, - extra=extra) - - _, body = self.client.show_port(port['uuid']) - self._assertExpected(port, body) - - @decorators.idempotent_id('1bf257a9-aea3-494e-89c0-63f657ab4fdd') - def test_delete_port(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - _, port = self.create_port(node_id=node_id, address=address) - - self.delete_port(port['uuid']) - - self.assertRaises(lib_exc.NotFound, self.client.show_port, - port['uuid']) - - @decorators.idempotent_id('9fa77ab5-ce59-4f05-baac-148904ba1597') - def test_show_port(self): - _, port = self.client.show_port(self.port['uuid']) - self._assertExpected(self.port, port) - - @decorators.idempotent_id('7c1114ff-fc3f-47bb-bc2f-68f61620ba8b') - def test_show_port_by_address(self): - _, port = self.client.show_port_by_address(self.port['address']) - self._assertExpected(self.port, port['ports'][0]) - - @decorators.idempotent_id('bd773405-aea5-465d-b576-0ab1780069e5') - def test_show_port_with_links(self): - _, port = self.client.show_port(self.port['uuid']) - self.assertIn('links', port.keys()) - self.assertEqual(2, len(port['links'])) - self.assertIn(port['uuid'], port['links'][0]['href']) - - @decorators.idempotent_id('b5e91854-5cd7-4a8e-bb35-3e0a1314606d') - def test_list_ports(self): - _, body = self.client.list_ports() - self.assertIn(self.port['uuid'], - [i['uuid'] for i in body['ports']]) - # Verify self links. - for port in body['ports']: - self.validate_self_link('ports', port['uuid'], - port['links'][0]['href']) - - @decorators.idempotent_id('324a910e-2f80-4258-9087-062b5ae06240') - def test_list_with_limit(self): - _, body = self.client.list_ports(limit=3) - - next_marker = body['ports'][-1]['uuid'] - self.assertIn(next_marker, body['next']) - - @decorators.idempotent_id('8a94b50f-9895-4a63-a574-7ecff86e5875') - def test_list_ports_details(self): - node_id = self.node['uuid'] - - uuids = [ - self.create_port(node_id=node_id, - address=data_utils.rand_mac_address()) - [1]['uuid'] for i in range(0, 5)] - - _, body = self.client.list_ports_detail() - - ports_dict = dict((port['uuid'], port) for port in body['ports'] - if port['uuid'] in uuids) - - for uuid in uuids: - self.assertIn(uuid, ports_dict) - port = ports_dict[uuid] - self.assertIn('extra', port) - self.assertIn('node_uuid', port) - # never expose the node_id - self.assertNotIn('node_id', port) - # Verify self link. - self.validate_self_link('ports', port['uuid'], - port['links'][0]['href']) - - @decorators.idempotent_id('8a03f688-7d75-4ecd-8cbc-e06b8f346738') - def test_list_ports_details_with_address(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - self.create_port(node_id=node_id, address=address) - for i in range(0, 5): - self.create_port(node_id=node_id, - address=data_utils.rand_mac_address()) - - _, body = self.client.list_ports_detail(address=address) - self.assertEqual(1, len(body['ports'])) - self.assertEqual(address, body['ports'][0]['address']) - - @decorators.idempotent_id('9c26298b-1bcb-47b7-9b9e-8bdd6e3c4aba') - def test_update_port_replace(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} - - _, port = self.create_port(node_id=node_id, address=address, - extra=extra) - - new_address = data_utils.rand_mac_address() - new_extra = {'key1': 'new-value1', 'key2': 'new-value2', - 'key3': 'new-value3'} - - patch = [{'path': '/address', - 'op': 'replace', - 'value': new_address}, - {'path': '/extra/key1', - 'op': 'replace', - 'value': new_extra['key1']}, - {'path': '/extra/key2', - 'op': 'replace', - 'value': new_extra['key2']}, - {'path': '/extra/key3', - 'op': 'replace', - 'value': new_extra['key3']}] - - self.client.update_port(port['uuid'], patch) - - _, body = self.client.show_port(port['uuid']) - self.assertEqual(new_address, body['address']) - self.assertEqual(new_extra, body['extra']) - - @decorators.idempotent_id('d7e7fece-6ed9-460a-9ebe-9267217e8580') - def test_update_port_remove(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} - - _, port = self.create_port(node_id=node_id, address=address, - extra=extra) - - # Removing one item from the collection - self.client.update_port(port['uuid'], - [{'path': '/extra/key2', - 'op': 'remove'}]) - extra.pop('key2') - _, body = self.client.show_port(port['uuid']) - self.assertEqual(extra, body['extra']) - - # Removing the collection - self.client.update_port(port['uuid'], [{'path': '/extra', - 'op': 'remove'}]) - _, body = self.client.show_port(port['uuid']) - self.assertEqual({}, body['extra']) - - # Assert nothing else was changed - self.assertEqual(node_id, body['node_uuid']) - self.assertEqual(address, body['address']) - - @decorators.idempotent_id('241288b3-e98a-400f-a4d7-d1f716146361') - def test_update_port_add(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - - extra = {'key1': 'value1', 'key2': 'value2'} - - patch = [{'path': '/extra/key1', - 'op': 'add', - 'value': extra['key1']}, - {'path': '/extra/key2', - 'op': 'add', - 'value': extra['key2']}] - - self.client.update_port(port['uuid'], patch) - - _, body = self.client.show_port(port['uuid']) - self.assertEqual(extra, body['extra']) - - @decorators.idempotent_id('5309e897-0799-4649-a982-0179b04c3876') - def test_update_port_mixed_ops(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'key1': 'value1', 'key2': 'value2'} - - _, port = self.create_port(node_id=node_id, address=address, - extra=extra) - - new_address = data_utils.rand_mac_address() - new_extra = {'key1': 0.123, 'key3': {'cat': 'meow'}} - - patch = [{'path': '/address', - 'op': 'replace', - 'value': new_address}, - {'path': '/extra/key1', - 'op': 'replace', - 'value': new_extra['key1']}, - {'path': '/extra/key2', - 'op': 'remove'}, - {'path': '/extra/key3', - 'op': 'add', - 'value': new_extra['key3']}] - - self.client.update_port(port['uuid'], patch) - - _, body = self.client.show_port(port['uuid']) - self.assertEqual(new_address, body['address']) - self.assertEqual(new_extra, body['extra']) - - -class TestPortsWithPhysicalNetwork(base.BaseBaremetalTest): - """Tests for ports with physical network information.""" - - min_microversion = '1.34' - - def setUp(self): - super(TestPortsWithPhysicalNetwork, self).setUp() - - self.useFixture( - api_microversion_fixture.APIMicroversionFixture( - TestPortsWithPhysicalNetwork.min_microversion) - ) - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - - @decorators.idempotent_id('f1a5d279-c456-4311-ad31-fea09f61c22b') - def test_create_port_with_physical_network(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address, - physical_network='physnet1') - - _, body = self.client.show_port(port['uuid']) - - self._assertExpected(port, body) - self.assertEqual('physnet1', port['physical_network']) - - @decorators.idempotent_id('9c26298b-1bcb-47b7-9b9e-8bdd6e3c4aba') - def test_update_port_replace_physical_network(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address, - physical_network='physnet1') - - new_physnet = 'physnet2' - - patch = [{'path': '/physical_network', - 'op': 'replace', - 'value': new_physnet}] - - self.client.update_port(port['uuid'], patch) - - _, body = self.client.show_port(port['uuid']) - self.assertEqual(new_physnet, body['physical_network']) - - @decorators.idempotent_id('6503309c-b2c7-4f59-b15a-0d92b5de9210') - def test_update_port_remove_physical_network(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address, - physical_network='physnet1') - - patch = [{'path': '/physical_network', - 'op': 'remove'}] - - self.client.update_port(port['uuid'], patch) - - _, body = self.client.show_port(port['uuid']) - self.assertIsNone(body['physical_network']) - - @decorators.idempotent_id('4155c24d-8474-4b53-a320-aee475f85a68') - def test_create_ports_in_portgroup_with_physical_network(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, portgroup = self.create_portgroup(node_id, address=address) - - _, port1 = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - address = data_utils.rand_mac_address() - _, port2 = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - _, body = self.client.show_port(port1['uuid']) - self.assertEqual('physnet1', body['physical_network']) - self.assertEqual(portgroup['uuid'], body['portgroup_uuid']) - - _, body = self.client.show_port(port2['uuid']) - self.assertEqual('physnet1', body['physical_network']) - self.assertEqual(portgroup['uuid'], body['portgroup_uuid']) - - @decorators.idempotent_id('cf05a3ef-3bc4-4db7-bb4c-4eb871eb9f81') - def test_update_ports_in_portgroup_with_physical_network(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, portgroup = self.create_portgroup(node_id, address=address) - - _, port1 = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - address = data_utils.rand_mac_address() - _, port2 = self.create_port(node_id=node_id, address=address, - physical_network='physnet1') - - patch = [{'path': '/portgroup_uuid', - 'op': 'replace', - 'value': portgroup['uuid']}] - - self.client.update_port(port2['uuid'], patch) - - _, body = self.client.show_port(port1['uuid']) - self.assertEqual('physnet1', body['physical_network']) - self.assertEqual(portgroup['uuid'], body['portgroup_uuid']) - - _, body = self.client.show_port(port2['uuid']) - self.assertEqual('physnet1', body['physical_network']) - self.assertEqual(portgroup['uuid'], body['portgroup_uuid']) diff --git a/ironic_tempest_plugin/tests/api/admin/test_ports_negative.py b/ironic_tempest_plugin/tests/api/admin/test_ports_negative.py deleted file mode 100644 index c25020b258..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_ports_negative.py +++ /dev/null @@ -1,463 +0,0 @@ -# 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.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - - -class TestPortsNegative(base.BaseBaremetalTest): - """Negative tests for ports.""" - - def setUp(self): - super(TestPortsNegative, self).setUp() - - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('0a6ee1f7-d0d9-4069-8778-37f3aa07303a') - def test_create_port_malformed_mac(self): - node_id = self.node['uuid'] - address = 'malformed:mac' - - self.assertRaises(lib_exc.BadRequest, - self.create_port, node_id=node_id, address=address) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('30277ee8-0c60-4f1d-b125-0e51c2f43369') - def test_create_port_nonexsistent_node_id(self): - node_id = str(data_utils.rand_uuid()) - address = data_utils.rand_mac_address() - self.assertRaises(lib_exc.BadRequest, self.create_port, - node_id=node_id, address=address) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('029190f6-43e1-40a3-b64a-65173ba653a3') - def test_show_port_malformed_uuid(self): - self.assertRaises(lib_exc.BadRequest, self.client.show_port, - 'malformed:uuid') - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('0d00e13d-e2e0-45b1-bcbc-55a6d90ca793') - def test_show_port_nonexistent_uuid(self): - self.assertRaises(lib_exc.NotFound, self.client.show_port, - data_utils.rand_uuid()) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('4ad85266-31e9-4942-99ac-751897dc9e23') - def test_show_port_by_mac_not_allowed(self): - self.assertRaises(lib_exc.BadRequest, self.client.show_port, - data_utils.rand_mac_address()) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('89a34380-3c61-4c32-955c-2cd9ce94da21') - def test_create_port_duplicated_port_uuid(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - uuid = data_utils.rand_uuid() - - self.create_port(node_id=node_id, address=address, uuid=uuid) - self.assertRaises(lib_exc.Conflict, self.create_port, node_id=node_id, - address=address, uuid=uuid) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('65e84917-733c-40ae-ae4b-96a4adff931c') - def test_create_port_no_mandatory_field_node_id(self): - address = data_utils.rand_mac_address() - - self.assertRaises(lib_exc.BadRequest, self.create_port, node_id=None, - address=address) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('bcea3476-7033-4183-acfe-e56a30809b46') - def test_create_port_no_mandatory_field_mac(self): - node_id = self.node['uuid'] - - self.assertRaises(lib_exc.BadRequest, self.create_port, - node_id=node_id, address=None) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('2b51cd18-fb95-458b-9780-e6257787b649') - def test_create_port_malformed_port_uuid(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - uuid = 'malformed:uuid' - - self.assertRaises(lib_exc.BadRequest, self.create_port, - node_id=node_id, address=address, uuid=uuid) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('583a6856-6a30-4ac4-889f-14e2adff8105') - def test_create_port_malformed_node_id(self): - address = data_utils.rand_mac_address() - self.assertRaises(lib_exc.BadRequest, self.create_port, - node_id='malformed:nodeid', address=address) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('e27f8b2e-42c6-4a43-a3cd-accff716bc5c') - def test_create_port_duplicated_mac(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - self.create_port(node_id=node_id, address=address) - self.assertRaises(lib_exc.Conflict, - self.create_port, node_id=node_id, - address=address) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('8907082d-ac5e-4be3-b05f-d072ede82020') - def test_update_port_by_mac_not_allowed(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'key': 'value'} - - self.create_port(node_id=node_id, address=address, extra=extra) - - patch = [{'path': '/extra/key', - 'op': 'replace', - 'value': 'new-value'}] - - self.assertRaises(lib_exc.BadRequest, - self.client.update_port, address, - patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('df1ac70c-db9f-41d9-90f1-78cd6b905718') - def test_update_port_nonexistent(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'key': 'value'} - - _, port = self.create_port(node_id=node_id, address=address, - extra=extra) - port_id = port['uuid'] - - _, body = self.client.delete_port(port_id) - - patch = [{'path': '/extra/key', - 'op': 'replace', - 'value': 'new-value'}] - self.assertRaises(lib_exc.NotFound, - self.client.update_port, port_id, patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('c701e315-aa52-41ea-817c-65c5ca8ca2a8') - def test_update_port_malformed_port_uuid(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - self.create_port(node_id=node_id, address=address) - - new_address = data_utils.rand_mac_address() - self.assertRaises(lib_exc.BadRequest, self.client.update_port, - uuid='malformed:uuid', - patch=[{'path': '/address', 'op': 'replace', - 'value': new_address}]) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('f8f15803-34d6-45dc-b06f-e5e04bf1b38b') - def test_update_port_add_nonexistent_property(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id, - [{'path': '/nonexistent', ' op': 'add', - 'value': 'value'}]) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('898ec904-38b1-4fcb-9584-1187d4263a2a') - def test_update_port_replace_node_id_with_malformed(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - patch = [{'path': '/node_uuid', - 'op': 'replace', - 'value': 'malformed:node_uuid'}] - self.assertRaises(lib_exc.BadRequest, - self.client.update_port, port_id, patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('2949f30f-5f59-43fa-a6d9-4eac578afab4') - def test_update_port_replace_mac_with_duplicated(self): - node_id = self.node['uuid'] - address1 = data_utils.rand_mac_address() - address2 = data_utils.rand_mac_address() - - _, port1 = self.create_port(node_id=node_id, address=address1) - - _, port2 = self.create_port(node_id=node_id, address=address2) - port_id = port2['uuid'] - - patch = [{'path': '/address', - 'op': 'replace', - 'value': address1}] - self.assertRaises(lib_exc.Conflict, - self.client.update_port, port_id, patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('97f6e048-6e4f-4eba-a09d-fbbc78b77a77') - def test_update_port_replace_node_id_with_nonexistent(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - patch = [{'path': '/node_uuid', - 'op': 'replace', - 'value': data_utils.rand_uuid()}] - self.assertRaises(lib_exc.BadRequest, - self.client.update_port, port_id, patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('375022c5-9e9e-4b11-9ca4-656729c0c9b2') - def test_update_port_replace_mac_with_malformed(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - patch = [{'path': '/address', - 'op': 'replace', - 'value': 'malformed:mac'}] - - self.assertRaises(lib_exc.BadRequest, - self.client.update_port, port_id, patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('5722b853-03fc-4854-8308-2036a1b67d85') - def test_update_port_replace_nonexistent_property(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - patch = [{'path': '/nonexistent', ' op': 'replace', 'value': 'value'}] - - self.assertRaises(lib_exc.BadRequest, - self.client.update_port, port_id, patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('ae2696ca-930a-4a7f-918f-30ae97c60f56') - def test_update_port_remove_mandatory_field_mac(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id, - [{'path': '/address', 'op': 'remove'}]) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('5392c1f0-2071-4697-9064-ec2d63019018') - def test_update_port_remove_mandatory_field_port_uuid(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id, - [{'path': '/uuid', 'op': 'remove'}]) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('06b50d82-802a-47ef-b079-0a3311cf85a2') - def test_update_port_remove_nonexistent_property(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, port = self.create_port(node_id=node_id, address=address) - port_id = port['uuid'] - - self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id, - [{'path': '/nonexistent', 'op': 'remove'}]) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('03d42391-2145-4a6c-95bf-63fe55eb64fd') - def test_delete_port_by_mac_not_allowed(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - self.create_port(node_id=node_id, address=address) - self.assertRaises(lib_exc.BadRequest, self.client.delete_port, address) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('0629e002-818e-4763-b25b-ae5e07b1cb23') - def test_update_port_mixed_ops_integrity(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - extra = {'key1': 'value1', 'key2': 'value2'} - - _, port = self.create_port(node_id=node_id, address=address, - extra=extra) - port_id = port['uuid'] - - new_address = data_utils.rand_mac_address() - new_extra = {'key1': 'new-value1', 'key3': 'new-value3'} - - patch = [{'path': '/address', - 'op': 'replace', - 'value': new_address}, - {'path': '/extra/key1', - 'op': 'replace', - 'value': new_extra['key1']}, - {'path': '/extra/key2', - 'op': 'remove'}, - {'path': '/extra/key3', - 'op': 'add', - 'value': new_extra['key3']}, - {'path': '/nonexistent', - 'op': 'replace', - 'value': 'value'}] - - self.assertRaises(lib_exc.BadRequest, self.client.update_port, port_id, - patch) - - # patch should not be applied - _, body = self.client.show_port(port_id) - self.assertEqual(address, body['address']) - self.assertEqual(extra, body['extra']) - - -class TestPortsWithPhysicalNetworkOldAPI(base.BaseBaremetalTest): - """Negative tests for ports with physical network information.""" - - def setUp(self): - super(TestPortsWithPhysicalNetworkOldAPI, self).setUp() - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('307e57e9-082f-4830-9480-91affcbfda08') - def test_create_port_with_physical_network_old_api(self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - self.assertRaises((lib_exc.BadRequest, lib_exc.UnexpectedResponseCode), - self.create_port, - node_id=node_id, address=address, - physical_network='physnet1') - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('0b278c0a-d334-424e-a5c5-b6d001c2a715') - def test_update_port_replace_physical_network_old_api(self): - _, port = self.create_port(self.node['uuid'], - data_utils.rand_mac_address()) - - new_physnet = 'physnet1' - - patch = [{'path': '/physical_network', - 'op': 'replace', - 'value': new_physnet}] - - self.assertRaises((lib_exc.BadRequest, lib_exc.UnexpectedResponseCode), - self.client.update_port, - port['uuid'], patch) - - -class TestPortsNegativeWithPhysicalNetwork(base.BaseBaremetalTest): - """Negative tests for ports with physical network information.""" - - min_microversion = '1.34' - - def setUp(self): - super(TestPortsNegativeWithPhysicalNetwork, self).setUp() - - self.useFixture( - api_microversion_fixture.APIMicroversionFixture( - TestPortsNegativeWithPhysicalNetwork.min_microversion) - ) - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('e20156fb-956b-4d5b-89a4-f379044a1d3c') - def test_create_ports_in_portgroup_with_inconsistent_physical_network( - self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, portgroup = self.create_portgroup(node_id, address=address) - - _, _ = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - address = data_utils.rand_mac_address() - self.assertRaises(lib_exc.Conflict, - self.create_port, - node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet2') - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('050e792c-22c9-4e4a-ae89-dfbfc52ad00d') - def test_update_ports_in_portgroup_with_inconsistent_physical_network( - self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, portgroup = self.create_portgroup(node_id, address=address) - - _, _ = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - address = data_utils.rand_mac_address() - _, port2 = self.create_port(node_id=node_id, address=address, - physical_network='physnet2') - - patch = [{'path': '/portgroup_uuid', - 'op': 'replace', - 'value': portgroup['uuid']}] - - self.assertRaises(lib_exc.Conflict, - self.client.update_port, - port2['uuid'], patch) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('3cd1c8ec-57d1-40cb-922b-dd02431beea3') - def test_update_ports_in_portgroup_with_inconsistent_physical_network_2( - self): - node_id = self.node['uuid'] - address = data_utils.rand_mac_address() - - _, portgroup = self.create_portgroup(node_id, address=address) - - _, _ = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - address = data_utils.rand_mac_address() - _, port2 = self.create_port(node_id=node_id, address=address, - portgroup_uuid=portgroup['uuid'], - physical_network='physnet1') - - patch = [{'path': '/physical_network', - 'op': 'replace', - 'value': 'physnet2'}] - - self.assertRaises(lib_exc.Conflict, - self.client.update_port, - port2['uuid'], patch) diff --git a/ironic_tempest_plugin/tests/api/admin/test_volume_connector.py b/ironic_tempest_plugin/tests/api/admin/test_volume_connector.py deleted file mode 100644 index 0b936a208b..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_volume_connector.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright 2017 FUJITSU LIMITED -# -# 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.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - - -class TestVolumeConnector(base.BaseBaremetalTest): - """Basic test cases for volume connector.""" - - min_microversion = '1.32' - extra = {'key1': 'value1', 'key2': 'value2'} - - def setUp(self): - super(TestVolumeConnector, self).setUp() - self.useFixture( - api_microversion_fixture.APIMicroversionFixture( - self.min_microversion)) - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - _, self.volume_connector = self.create_volume_connector( - self.node['uuid'], type='iqn', - connector_id=data_utils.rand_name('connector_id'), - extra=self.extra) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('3c3cbf45-488a-4386-a811-bf0aa2589c58') - def test_create_volume_connector_error(self): - """Create a volume connector. - - Fail when creating a volume connector with same connector_id - & type as an existing volume connector. - """ - regex_str = (r'.*A volume connector .*already exists') - - self.assertRaisesRegex( - lib_exc.Conflict, regex_str, - self.create_volume_connector, - self.node['uuid'], - type=self.volume_connector['type'], - connector_id=self.volume_connector['connector_id']) - - @decorators.idempotent_id('5795f816-0789-42e6-bb9c-91b4876ad13f') - def test_delete_volume_connector(self): - """Delete a volume connector.""" - # Powering off the Node before deleting a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - self.delete_volume_connector(self.volume_connector['uuid']) - self.assertRaises(lib_exc.NotFound, self.client.show_volume_connector, - self.volume_connector['uuid']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('ccbda5e6-52b7-400c-94d7-25eec1d590f0') - def test_delete_volume_connector_error(self): - """Delete a volume connector - - Fail when deleting a volume connector on node - with powered on state. - """ - - # Powering on the Node before deleting a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power on') - - regex_str = (r'.*The requested action \\\\"volume connector ' - r'deletion\\\\" can not be performed on node*') - - self.assertRaisesRegex(lib_exc.BadRequest, - regex_str, - self.delete_volume_connector, - self.volume_connector['uuid']) - - @decorators.idempotent_id('6e4f50b7-0f4f-41c2-971e-d751abcac4e0') - def test_show_volume_connector(self): - """Show a specified volume connector.""" - _, volume_connector = self.client.show_volume_connector( - self.volume_connector['uuid']) - self._assertExpected(self.volume_connector, volume_connector) - - @decorators.idempotent_id('a4725778-e164-4ee5-96a0-66119a35f783') - def test_list_volume_connectors(self): - """List volume connectors.""" - _, body = self.client.list_volume_connectors() - self.assertIn(self.volume_connector['uuid'], - [i['uuid'] for i in body['connectors']]) - self.assertIn(self.volume_connector['type'], - [i['type'] for i in body['connectors']]) - self.assertIn(self.volume_connector['connector_id'], - [i['connector_id'] for i in body['connectors']]) - - @decorators.idempotent_id('1d0459ad-01c0-46db-b930-7301bc2a3c98') - def test_list_with_limit(self): - """List volume connectors with limit.""" - _, body = self.client.list_volume_connectors(limit=3) - - next_marker = body['connectors'][-1]['uuid'] - self.assertIn(next_marker, body['next']) - - @decorators.idempotent_id('3c6f8354-e9bd-4f21-aae2-6deb96b04be7') - def test_update_volume_connector_replace(self): - """Update a volume connector with new connector id.""" - new_connector_id = data_utils.rand_name('connector_id') - - patch = [{'path': '/connector_id', - 'op': 'replace', - 'value': new_connector_id}] - - # Powering off the Node before updating a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - self.client.update_volume_connector( - self.volume_connector['uuid'], patch) - - _, body = self.client.show_volume_connector( - self.volume_connector['uuid']) - self.assertEqual(new_connector_id, body['connector_id']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('5af8dc7a-9965-4787-8184-e60aeaf30957') - def test_update_volume_connector_replace_error(self): - """Updating a volume connector. - - Fail when updating a volume connector on node - with power on state. - """ - - new_connector_id = data_utils.rand_name('connector_id') - - patch = [{'path': '/connector_id', - 'op': 'replace', - 'value': new_connector_id}] - - # Powering on the Node before updating a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power on') - - regex_str = (r'.*The requested action \\\\"volume connector ' - r'update\\\\" can not be performed on node*') - self.assertRaisesRegex(lib_exc.BadRequest, - regex_str, - self.client.update_volume_connector, - self.volume_connector['uuid'], - patch) - - @decorators.idempotent_id('b95c75eb-4048-482e-99ff-fe1d32538383') - def test_update_volume_connector_remove_item(self): - """Update a volume connector by removing one item from collection.""" - new_extra = {'key1': 'value1'} - _, body = self.client.show_volume_connector( - self.volume_connector['uuid']) - connector_id = body['connector_id'] - connector_type = body['type'] - - # Powering off the Node before updating a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - # Removing one item from the collection - self.client.update_volume_connector(self.volume_connector['uuid'], - [{'path': '/extra/key2', - 'op': 'remove'}]) - _, body = self.client.show_volume_connector( - self.volume_connector['uuid']) - self.assertEqual(new_extra, body['extra']) - - # Assert nothing else was changed - self.assertEqual(connector_id, body['connector_id']) - self.assertEqual(connector_type, body['type']) - - @decorators.idempotent_id('8de03acd-532a-476f-8bc9-0e8b23bfe609') - def test_update_volume_connector_remove_collection(self): - """Update a volume connector by removing collection.""" - _, body = self.client.show_volume_connector( - self.volume_connector['uuid']) - connector_id = body['connector_id'] - connector_type = body['type'] - - # Powering off the Node before updating a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - # Removing the collection - self.client.update_volume_connector(self.volume_connector['uuid'], - [{'path': '/extra', - 'op': 'remove'}]) - _, body = self.client.show_volume_connector( - self.volume_connector['uuid']) - self.assertEqual({}, body['extra']) - - # Assert nothing else was changed - self.assertEqual(connector_id, body['connector_id']) - self.assertEqual(connector_type, body['type']) - - @decorators.idempotent_id('bfb0ca6b-086d-4663-9b25-e0eaf42da55b') - def test_update_volume_connector_add(self): - """Update a volume connector by adding one item to collection.""" - new_extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} - - patch = [{'path': '/extra/key3', - 'op': 'add', - 'value': new_extra['key3']}, - {'path': '/extra/key3', - 'op': 'add', - 'value': new_extra['key3']}] - - # Powering off the Node before updating a volume connector. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - self.client.update_volume_connector( - self.volume_connector['uuid'], patch) - - _, body = self.client.show_volume_connector( - self.volume_connector['uuid']) - self.assertEqual(new_extra, body['extra']) diff --git a/ironic_tempest_plugin/tests/api/admin/test_volume_target.py b/ironic_tempest_plugin/tests/api/admin/test_volume_target.py deleted file mode 100644 index 731467ca06..0000000000 --- a/ironic_tempest_plugin/tests/api/admin/test_volume_target.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright 2017 FUJITSU LIMITED -# -# 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.lib.common.utils import data_utils -from tempest.lib import decorators -from tempest.lib import exceptions as lib_exc - -from ironic_tempest_plugin.tests.api.admin import api_microversion_fixture -from ironic_tempest_plugin.tests.api.admin import base - - -class TestVolumeTarget(base.BaseBaremetalTest): - """Basic test cases for volume target.""" - - min_microversion = '1.32' - extra = {'key1': 'value1', 'key2': 'value2'} - - def setUp(self): - super(TestVolumeTarget, self).setUp() - self.useFixture( - api_microversion_fixture.APIMicroversionFixture( - self.min_microversion)) - _, self.chassis = self.create_chassis() - _, self.node = self.create_node(self.chassis['uuid']) - _, self.volume_target = self.create_volume_target( - self.node['uuid'], volume_type=data_utils.rand_name('volume_type'), - volume_id=data_utils.rand_name('volume_id'), - boot_index=10, - extra=self.extra) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('da5c27d4-68cc-499f-b8ab-3048b87d3bca') - def test_create_volume_target_error(self): - """Create a volume target. - - Fail when creating a volume target with same boot index as the - existing volume target. - """ - regex_str = (r'.*A volume target .*already exists') - - self.assertRaisesRegex( - lib_exc.Conflict, regex_str, - self.create_volume_target, - self.node['uuid'], - volume_type=data_utils.rand_name('volume_type'), - volume_id=data_utils.rand_name('volume_id'), - boot_index=self.volume_target['boot_index']) - - @decorators.idempotent_id('ea3a9b2e-8971-4830-9274-abaf0239f1ce') - def test_delete_volume_target(self): - """Delete a volume target.""" - # Powering off the Node before deleting a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - self.delete_volume_target(self.volume_target['uuid']) - self.assertRaises(lib_exc.NotFound, self.client.show_volume_target, - self.volume_target['uuid']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('532a06bc-a9b2-44b0-828a-c53279c87cb2') - def test_delete_volume_target_error(self): - """Fail when deleting a volume target on node with power on state.""" - # Powering on the Node before deleting a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power on') - - regex_str = (r'.*The requested action \\\\"volume target ' - r'deletion\\\\" can not be performed on node*') - - self.assertRaisesRegex(lib_exc.BadRequest, - regex_str, - self.delete_volume_target, - self.volume_target['uuid']) - - @decorators.idempotent_id('a2598388-8f61-4b7e-944f-f37e4f60e1e2') - def test_show_volume_target(self): - """Show a specified volume target.""" - _, volume_target = self.client.show_volume_target( - self.volume_target['uuid']) - self._assertExpected(self.volume_target, volume_target) - - @decorators.idempotent_id('ae99a986-d93c-4324-9cdc-41d89e3a659f') - def test_list_volume_targets(self): - """List volume targets.""" - _, body = self.client.list_volume_targets() - self.assertIn(self.volume_target['uuid'], - [i['uuid'] for i in body['targets']]) - self.assertIn(self.volume_target['volume_type'], - [i['volume_type'] for i in body['targets']]) - self.assertIn(self.volume_target['volume_id'], - [i['volume_id'] for i in body['targets']]) - - @decorators.idempotent_id('9da25447-0370-4b33-9c1f-d4503f5950ae') - def test_list_with_limit(self): - """List volume targets with limit.""" - _, body = self.client.list_volume_targets(limit=3) - - next_marker = body['targets'][-1]['uuid'] - self.assertIn(next_marker, body['next']) - - @decorators.idempotent_id('8559cd08-feae-4f1a-a0ad-5bad8ea12b76') - def test_update_volume_target_replace(self): - """Update a volume target by replacing volume id.""" - new_volume_id = data_utils.rand_name('volume_id') - - patch = [{'path': '/volume_id', - 'op': 'replace', - 'value': new_volume_id}] - - # Powering off the Node before updating a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - self.client.update_volume_target(self.volume_target['uuid'], patch) - - _, body = self.client.show_volume_target(self.volume_target['uuid']) - self.assertEqual(new_volume_id, body['volume_id']) - - @decorators.attr(type=['negative']) - @decorators.idempotent_id('fd5266d3-4f3c-4dce-9c87-bfdea2b756c7') - def test_update_volume_target_replace_error(self): - """Fail when updating a volume target on node with power on state.""" - new_volume_id = data_utils.rand_name('volume_id') - - patch = [{'path': '/volume_id', - 'op': 'replace', - 'value': new_volume_id}] - - # Powering on the Node before updating a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power on') - - regex_str = (r'.*The requested action \\\\"volume target ' - r'update\\\\" can not be performed on node*') - - self.assertRaisesRegex(lib_exc.BadRequest, - regex_str, - self.client.update_volume_target, - self.volume_target['uuid'], - patch) - - @decorators.idempotent_id('1c13a4ee-1a49-4739-8c19-77960fbd1af8') - def test_update_volume_target_remove_item(self): - """Update a volume target by removing one item from the collection.""" - new_extra = {'key1': 'value1'} - _, body = self.client.show_volume_target(self.volume_target['uuid']) - volume_id = body['volume_id'] - volume_type = body['volume_type'] - - # Powering off the Node before updating a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - # Removing one item from the collection - self.client.update_volume_target(self.volume_target['uuid'], - [{'path': '/extra/key2', - 'op': 'remove'}]) - - _, body = self.client.show_volume_target(self.volume_target['uuid']) - self.assertEqual(new_extra, body['extra']) - - # Assert nothing else was changed - self.assertEqual(volume_id, body['volume_id']) - self.assertEqual(volume_type, body['volume_type']) - - @decorators.idempotent_id('6784ddb0-9144-41ea-b8a0-f888ad5c5b62') - def test_update_volume_target_remove_collection(self): - """Update a volume target by removing the collection.""" - _, body = self.client.show_volume_target(self.volume_target['uuid']) - volume_id = body['volume_id'] - volume_type = body['volume_type'] - - # Powering off the Node before updating a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - # Removing the collection - self.client.update_volume_target(self.volume_target['uuid'], - [{'path': '/extra', - 'op': 'remove'}]) - _, body = self.client.show_volume_target(self.volume_target['uuid']) - self.assertEqual({}, body['extra']) - - # Assert nothing else was changed - self.assertEqual(volume_id, body['volume_id']) - self.assertEqual(volume_type, body['volume_type']) - - @decorators.idempotent_id('9629715d-57ba-423b-b985-232674cc3a25') - def test_update_volume_target_add(self): - """Update a volume target by adding to the collection.""" - new_extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'} - - patch = [{'path': '/extra/key3', - 'op': 'add', - 'value': new_extra['key3']}] - - # Powering off the Node before updating a volume target. - self.client.set_node_power_state(self.node['uuid'], 'power off') - - self.client.update_volume_target(self.volume_target['uuid'], patch) - - _, body = self.client.show_volume_target(self.volume_target['uuid']) - self.assertEqual(new_extra, body['extra']) diff --git a/ironic_tempest_plugin/tests/scenario/__init__.py b/ironic_tempest_plugin/tests/scenario/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_manager.py deleted file mode 100644 index bd6f1bde23..0000000000 --- a/ironic_tempest_plugin/tests/scenario/baremetal_manager.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# Copyright 2013 IBM Corp. -# All Rights Reserved. -# -# 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 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 ironic_tempest_plugin import clients -from ironic_tempest_plugin.common import utils -from ironic_tempest_plugin.common import waiters as ironic_waiters -from ironic_tempest_plugin import manager - -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.""" - POWER_ON = 'power on' - POWER_OFF = 'power off' - REBOOT = 'rebooting' - SUSPEND = 'suspended' - - -class BaremetalProvisionStates(object): - """Possible provision states of an Ironic node.""" - ENROLL = 'enroll' - NOSTATE = None - AVAILABLE = 'available' - INIT = 'initializing' - ACTIVE = 'active' - BUILDING = 'building' - DEPLOYWAIT = 'wait call-back' - DEPLOYING = 'deploying' - DEPLOYFAIL = 'deploy failed' - DEPLOYDONE = 'deploy complete' - 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): - super(BaremetalScenarioTest, cls).setup_clients() - - cls.baremetal_client = clients.Manager().baremetal_client - - @classmethod - def resource_setup(cls): - super(BaremetalScenarioTest, cls).resource_setup() - # allow any issues obtaining the node list to raise early - cls.baremetal_client.list_nodes() - - @classmethod - def wait_provisioning_state(cls, node_id, state, timeout=10, interval=1): - ironic_waiters.wait_for_bm_node_status( - cls.baremetal_client, node_id=node_id, attr='provision_state', - status=state, timeout=timeout, interval=interval) - - @classmethod - def wait_power_state(cls, node_id, state): - ironic_waiters.wait_for_bm_node_status( - cls.baremetal_client, node_id=node_id, attr='power_state', - status=state, timeout=CONF.baremetal.power_timeout) - - def wait_node(self, instance_id): - """Waits for a node to be associated with instance_id.""" - ironic_waiters.wait_node_instance_association(self.baremetal_client, - 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 = [] - _, body = self.baremetal_client.list_node_ports(node_uuid) - for port in body['ports']: - _, p = self.baremetal_client.show_port(port['uuid']) - ports.append(p) - return ports - - def get_node_vifs(self, node_uuid, api_version='1.28'): - _, body = self.baremetal_client.vif_list(node_uuid, - api_version=api_version) - return body['vifs'] - - 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) - else: - dest = self.get_remote_client(self.instance) - dest.validate_authentication() - - def boot_instance(self, clients=None, keypair=None, - net_id=None, fixed_ip=None, **create_kwargs): - if clients is None: - servers_client = self.servers_client - else: - servers_client = clients.servers_client - if keypair is None: - keypair = self.keypair - - if any([net_id, fixed_ip]): - network = {} - if net_id: - network['uuid'] = net_id - if fixed_ip: - network['fixed_ip'] = fixed_ip - instance = self.create_server( - key_name=keypair['name'], - networks=[network], - clients=clients, - **create_kwargs - ) - else: - instance = self.create_server( - key_name=keypair['name'], - clients=clients, - **create_kwargs - ) - - self.wait_node(instance['id']) - node = self.get_node(instance_id=instance['id']) - - self.wait_power_state(node['uuid'], BaremetalPowerStates.POWER_ON) - - self.wait_provisioning_state( - node['uuid'], - [BaremetalProvisionStates.DEPLOYWAIT, - BaremetalProvisionStates.ACTIVE], - timeout=CONF.baremetal.deploywait_timeout) - - self.wait_provisioning_state(node['uuid'], - BaremetalProvisionStates.ACTIVE, - timeout=CONF.baremetal.active_timeout, - interval=30) - - waiters.wait_for_server_status(servers_client, - instance['id'], 'ACTIVE') - node = self.get_node(instance_id=instance['id']) - instance = servers_client.show_server(instance['id'])['server'] - - return instance, node - - def terminate_instance(self, instance, servers_client=None): - if servers_client is None: - servers_client = self.servers_client - - node = self.get_node(instance_id=instance['id']) - servers_client.delete_server(instance['id']) - self.wait_power_state(node['uuid'], - BaremetalPowerStates.POWER_OFF) - self.wait_provisioning_state( - node['uuid'], - [BaremetalProvisionStates.NOSTATE, - BaremetalProvisionStates.AVAILABLE], - timeout=CONF.baremetal.unprovision_timeout, - interval=30) diff --git a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py b/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py deleted file mode 100644 index 1d19c47366..0000000000 --- a/ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py +++ /dev/null @@ -1,339 +0,0 @@ -# -# 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.os_admin.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.os_admin.floating_ips_client.list_floatingips() - floating_ip_id = [f['id'] for f in body['floatingips'] if - f['floating_ip_address'] == ip_address][0] - cls.os_admin.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, 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. - :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}] - if image_checksum is not None: - patch.append({'path': '/instance_info/image_checksum', - 'op': 'add', - 'value': image_checksum}) - 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 - - # Image checksum, required when image is stored on HTTP server. - image_checksum = 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) - image_checksum = None - if not uuidutils.is_uuid_like(cls.image_ref): - image_checksum = cls.image_checksum - cls.node = cls.boot_node(cls.driver, cls.image_ref, - image_checksum=image_checksum) - 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']) - # 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) - 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 deleted file mode 100644 index e69de29bb2..0000000000 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 deleted file mode 100644 index f6e02e16bb..0000000000 --- a/ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py +++ /dev/null @@ -1,145 +0,0 @@ -# -# 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.common import utils -from tempest import config -from tempest.lib import decorators - -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 - - @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)) - - -class BaremetalAgentIpmitoolWholediskHttpLink( - bsm.BaremetalStandaloneScenarioTest): - - driver = 'agent_ipmitool' - image_ref = CONF.baremetal.whole_disk_image_url - image_checksum = CONF.baremetal.whole_disk_image_checksum - wholedisk_image = True - - @classmethod - def skip_checks(cls): - super(BaremetalAgentIpmitoolWholediskHttpLink, cls).skip_checks() - if not CONF.baremetal_feature_enabled.ipxe_enabled: - skip_msg = ("HTTP server is not available when ipxe is disabled.") - raise cls.skipException(skip_msg) - - @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)) - - -class BaremetalAgentIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest): - - driver = 'agent_ipmitool' - image_ref = CONF.baremetal.partition_image_ref - wholedisk_image = False - - @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)) - - -class BaremetalPxeIpmitoolWholedisk(bsm.BaremetalStandaloneScenarioTest): - - driver = 'pxe_ipmitool' - image_ref = CONF.baremetal.whole_disk_image_ref - wholedisk_image = True - - @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)) - - -class BaremetalPxeIpmitoolWholediskHttpLink( - bsm.BaremetalStandaloneScenarioTest): - - driver = 'pxe_ipmitool' - image_ref = CONF.baremetal.whole_disk_image_url - image_checksum = CONF.baremetal.whole_disk_image_checksum - wholedisk_image = True - - @classmethod - def skip_checks(cls): - super(BaremetalPxeIpmitoolWholediskHttpLink, cls).skip_checks() - if not CONF.baremetal_feature_enabled.ipxe_enabled: - skip_msg = ("HTTP server is not available when ipxe is disabled.") - raise cls.skipException(skip_msg) - - @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)) - - -class BaremetalPxeIpmitoolPartitioned(bsm.BaremetalStandaloneScenarioTest): - - driver = 'pxe_ipmitool' - image_ref = CONF.baremetal.partition_image_ref - wholedisk_image = False - - @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)) - - -class BaremetalIpmiWholedisk(bsm.BaremetalStandaloneScenarioTest): - - driver = 'ipmi' - image_ref = CONF.baremetal.whole_disk_image_ref - wholedisk_image = True - - @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)) - - -class BaremetalIpmiPartitioned(bsm.BaremetalStandaloneScenarioTest): - - driver = 'ipmi' - image_ref = CONF.baremetal.partition_image_ref - wholedisk_image = False - - @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)) diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py deleted file mode 100644 index 47fc07e7ae..0000000000 --- a/ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py +++ /dev/null @@ -1,146 +0,0 @@ -# -# Copyright 2014 Hewlett-Packard Development Company, L.P. -# -# 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.common import waiters -from tempest import config -from tempest.lib.common import api_version_request -from tempest.lib import decorators - -from ironic_tempest_plugin.tests.scenario import baremetal_manager - -LOG = logging.getLogger(__name__) -CONF = config.CONF - - -class BaremetalBasicOps(baremetal_manager.BaremetalScenarioTest): - """This smoke test tests the pxe_ssh Ironic driver. - - It follows this basic set of operations: - * Creates a keypair - * Boots an instance using the keypair - * Monitors the associated Ironic node for power and - expected state transitions - * Validates Ironic node's port data has been properly updated - * Verifies SSH connectivity using created keypair via fixed IP - * Associates a floating ip - * Verifies SSH connectivity using created keypair via floating IP - * Verifies instance rebuild with ephemeral partition preservation - * Deletes instance - * Monitors the associated Ironic node for power and - expected state transitions - """ - - def rebuild_instance(self, preserve_ephemeral=False): - self.rebuild_server(server_id=self.instance['id'], - preserve_ephemeral=preserve_ephemeral, - wait=False) - - node = self.get_node(instance_id=self.instance['id']) - - # We should remain on the same node - self.assertEqual(self.node['uuid'], node['uuid']) - self.node = node - - waiters.wait_for_server_status( - self.servers_client, - server_id=self.instance['id'], - status='REBUILD', - ready_wait=False) - waiters.wait_for_server_status( - self.servers_client, - server_id=self.instance['id'], - status='ACTIVE') - - def verify_partition(self, client, label, mount, gib_size): - """Verify a labeled partition's mount point and size.""" - LOG.info("Looking for partition %s mounted on %s", label, mount) - - # Validate we have a device with the given partition label - cmd = "/sbin/blkid | grep '%s' | cut -d':' -f1" % label - device = client.exec_command(cmd).rstrip('\n') - LOG.debug("Partition device is %s", device) - self.assertNotEqual('', device) - - # Validate the mount point for the device - cmd = "mount | grep '%s' | cut -d' ' -f3" % device - actual_mount = client.exec_command(cmd).rstrip('\n') - LOG.debug("Partition mount point is %s", actual_mount) - self.assertEqual(actual_mount, mount) - - # Validate the partition size matches what we expect - numbers = '0123456789' - devnum = device.replace('/dev/', '') - cmd = "cat /sys/block/%s/%s/size" % (devnum.rstrip(numbers), devnum) - num_bytes = client.exec_command(cmd).rstrip('\n') - num_bytes = int(num_bytes) * 512 - actual_gib_size = num_bytes / (1024 * 1024 * 1024) - LOG.debug("Partition size is %d GiB", actual_gib_size) - self.assertEqual(actual_gib_size, gib_size) - - def get_flavor_ephemeral_size(self): - """Returns size of the ephemeral partition in GiB.""" - f_id = self.instance['flavor']['id'] - flavor = self.flavors_client.show_flavor(f_id)['flavor'] - ephemeral = flavor.get('OS-FLV-EXT-DATA:ephemeral') - if not ephemeral or ephemeral == 'N/A': - return None - return int(ephemeral) - - def validate_ports(self): - node_uuid = self.node['uuid'] - vifs = [] - # TODO(vsaienko) switch to get_node_vifs() when all stable releases - # supports Ironic API 1.28 - if (api_version_request.APIVersionRequest( - CONF.baremetal.max_microversion) >= - api_version_request.APIVersionRequest('1.28')): - vifs = self.get_node_vifs(node_uuid) - else: - for port in self.get_ports(self.node['uuid']): - vif = port['extra'].get('vif_port_id') - if vif: - vifs.append({'id': vif}) - - ir_ports = self.get_ports(node_uuid) - ir_ports_addresses = [x['address'] for x in ir_ports] - for vif in vifs: - n_port_id = vif['id'] - body = self.ports_client.show_port(n_port_id) - n_port = body['port'] - self.assertEqual(n_port['device_id'], self.instance['id']) - self.assertIn(n_port['mac_address'], ir_ports_addresses) - - @decorators.idempotent_id('549173a5-38ec-42bb-b0e2-c8b9f4a08943') - @utils.services('compute', 'image', 'network') - def test_baremetal_server_ops(self): - self.add_keypair() - self.instance, self.node = self.boot_instance() - self.validate_ports() - 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 - # the same size as our flavor definition. - eph_size = self.get_flavor_ephemeral_size() - if eph_size: - self.verify_partition(vm_client, 'ephemeral0', '/mnt', eph_size) - # Create the test file - self.create_timestamp( - ip_address, private_key=self.keypair['private_key']) - - self.terminate_instance(self.instance) diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py deleted file mode 100644 index 99082703bb..0000000000 --- a/ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright 2017 FUJITSU LIMITED -# -# 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.common import utils -from tempest.common import waiters -from tempest import config -from tempest.lib.common.utils import data_utils -from tempest.lib.common.utils import test_utils -from tempest.lib import decorators - -from ironic_tempest_plugin.tests.scenario import baremetal_manager - -CONF = config.CONF - - -class BaremetalBFV(baremetal_manager.BaremetalScenarioTest): - """Check baremetal instance that can boot from Cinder volume: - - * Create a volume from an image - * Create a keypair - * Boot an instance from the volume using the keypair - * Verify instance IP address - * Delete instance - """ - - credentials = ['primary', 'admin'] - - min_microversion = '1.32' - - @classmethod - def skip_checks(cls): - super(BaremetalBFV, cls).skip_checks() - if CONF.baremetal.use_provision_network: - msg = 'Ironic boot-from-volume requires a flat network.' - raise cls.skipException(msg) - - def create_volume(self, size=None, name=None, snapshot_id=None, - image_id=None, volume_type=None): - if size is None: - size = CONF.volume.volume_size - if image_id is None: - image = self.compute_images_client.show_image(image_id)['image'] - min_disk = image.get('minDisk') - size = max(size, min_disk) - if name is None: - name = data_utils.rand_name(self.__class__.__name__ + "-volume") - kwargs = {'display_name': name, - 'snapshot_id': snapshot_id, - 'imageRef': image_id, - 'volume_type': volume_type, - 'size': size} - volume = self.volumes_client.create_volume(**kwargs)['volume'] - - self.addCleanup(self.volumes_client.wait_for_resource_deletion, - volume['id']) - self.addCleanup(test_utils.call_and_ignore_notfound_exc, - self.volumes_client.delete_volume, volume['id']) - self.assertEqual(name, volume['name']) - waiters.wait_for_volume_resource_status(self.volumes_client, - volume['id'], 'available') - # The volume retrieved on creation has a non-up-to-date status. - # Retrieval after it becomes active ensures correct details. - volume = self.volumes_client.show_volume(volume['id'])['volume'] - return volume - - def _create_volume_from_image(self): - """Create a cinder volume from the default image.""" - image_id = CONF.compute.image_ref - vol_name = data_utils.rand_name( - self.__class__.__name__ + '-volume-origin') - return self.create_volume(name=vol_name, image_id=image_id) - - def _get_bdm(self, source_id, source_type, delete_on_termination=False): - """Create block device mapping config options dict. - - :param source_id: id of the source device. - :param source_type: type of the source device. - :param delete_on_termination: what to do with the volume when - the instance is terminated. - :return: a dictionary of configuration options for block - device mapping. - """ - bd_map_v2 = [{ - 'uuid': source_id, - 'source_type': source_type, - 'destination_type': 'volume', - 'boot_index': 0, - 'delete_on_termination': delete_on_termination}] - return {'block_device_mapping_v2': bd_map_v2} - - def _boot_instance_from_resource(self, source_id, - source_type, - keypair=None, - delete_on_termination=False): - """Boot instance from the specified resource.""" - # NOTE(tiendc): Boot to the volume, use image_id=''. - # We don't pass image_id=None as that will cause the default image - # to be used. - create_kwargs = {'image_id': ''} - create_kwargs.update(self._get_bdm( - source_id, - source_type, - delete_on_termination=delete_on_termination)) - - return self.boot_instance( - clients=self.os_primary, - keypair=keypair, - **create_kwargs - ) - - @decorators.idempotent_id('d6e05e61-8221-44ac-b785-57545f8e0fcf') - @utils.services('compute', 'image', 'network', 'volume') - def test_baremetal_boot_from_volume(self): - """Test baremetal node can boot from a cinder volume. - - This test function first creates a cinder volume from an image. - Then it executes "server create" action with appropriate block - device mapping config options, the baremetal node will boot - from the newly created volume. This requires a volume connector - is created for the node, and the node capabilities flag - iscsi_boot is set to true. - """ - # Create volume from image - volume_origin = self._create_volume_from_image() - - # NOTE: node properties/capabilities for flag iscsi_boot=true, - # and volume connector should be added by devstack already. - - # Boot instance - self.keypair = self.create_keypair() - self.instance, self.node = self._boot_instance_from_resource( - source_id=volume_origin['id'], - source_type='volume', - keypair=self.keypair - ) - - # Get server ip and validate authentication - ip_address = self.get_server_ip(self.instance) - self.get_remote_client(ip_address).validate_authentication() - - self.terminate_instance(instance=self.instance) diff --git a/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py b/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py deleted file mode 100644 index 013eb2ee2d..0000000000 --- a/ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py +++ /dev/null @@ -1,153 +0,0 @@ -# -# Copyright (c) 2015 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.common import utils -from tempest import config -from tempest.lib.common.utils import data_utils -from tempest.lib import decorators - -from ironic_tempest_plugin import manager -from ironic_tempest_plugin.tests.scenario import baremetal_manager - -CONF = config.CONF - - -class BaremetalMultitenancy(baremetal_manager.BaremetalScenarioTest, - manager.NetworkScenarioTest): - """Check L2 isolation of baremetal instances in different tenants: - - * Create a keypair, network, subnet and router for the primary tenant - * Boot 2 instances in the different tenant's network using the keypair - * Associate floating ips to both instance - * Verify there is no L3 connectivity between instances of different tenants - * Verify connectivity between instances floating IP's - * Delete both instances - """ - - credentials = ['primary', 'alt', 'admin'] - - @classmethod - def skip_checks(cls): - super(BaremetalMultitenancy, cls).skip_checks() - if not CONF.baremetal.use_provision_network: - msg = 'Ironic/Neutron tenant isolation is not configured.' - raise cls.skipException(msg) - - def create_tenant_network(self, clients, tenant_cidr): - network = self._create_network( - networks_client=clients.networks_client, - tenant_id=clients.credentials.tenant_id) - router = self._get_router( - client=clients.routers_client, - tenant_id=clients.credentials.tenant_id) - - result = clients.subnets_client.create_subnet( - name=data_utils.rand_name('subnet'), - network_id=network['id'], - tenant_id=clients.credentials.tenant_id, - ip_version=4, - cidr=tenant_cidr) - subnet = result['subnet'] - clients.routers_client.add_router_interface(router['id'], - subnet_id=subnet['id']) - self.addCleanup(clients.subnets_client.delete_subnet, subnet['id']) - self.addCleanup(clients.routers_client.remove_router_interface, - router['id'], subnet_id=subnet['id']) - - return network, subnet, router - - def verify_l3_connectivity(self, source_ip, private_key, - destination_ip, conn_expected=True): - remote = self.get_remote_client(source_ip, private_key=private_key) - remote.validate_authentication() - - cmd = 'ping %s -c4 -w4 || exit 0' % destination_ip - success_substring = "64 bytes from %s" % destination_ip - output = remote.exec_command(cmd) - if conn_expected: - self.assertIn(success_substring, output) - else: - self.assertNotIn(success_substring, output) - - @decorators.idempotent_id('26e2f145-2a8e-4dc7-8457-7f2eb2c6749d') - @utils.services('compute', 'image', 'network') - def test_baremetal_multitenancy(self): - - tenant_cidr = '10.0.100.0/24' - fixed_ip1 = '10.0.100.3' - fixed_ip2 = '10.0.100.5' - keypair = self.create_keypair() - network, subnet, router = self.create_tenant_network( - self.os_primary, tenant_cidr) - - # Boot 2 instances in the primary tenant network - # and check L2 connectivity between them - instance1, node1 = self.boot_instance( - clients=self.os_primary, - keypair=keypair, - net_id=network['id'], - fixed_ip=fixed_ip1 - ) - floating_ip1 = self.create_floating_ip( - instance1, - )['floating_ip_address'] - self.check_vm_connectivity(ip_address=floating_ip1, - private_key=keypair['private_key']) - - # Boot instance in the alt tenant network and ensure there is no - # L2 connectivity between instances of the different tenants - alt_keypair = self.create_keypair(self.os_alt.keypairs_client) - alt_network, alt_subnet, alt_router = self.create_tenant_network( - self.os_alt, tenant_cidr) - - alt_instance, alt_node = self.boot_instance( - keypair=alt_keypair, - clients=self.os_alt, - net_id=alt_network['id'], - fixed_ip=fixed_ip2 - ) - alt_floating_ip = self.create_floating_ip( - alt_instance, - client=self.os_alt.floating_ips_client - )['floating_ip_address'] - - self.check_vm_connectivity(ip_address=alt_floating_ip, - private_key=alt_keypair['private_key']) - - self.verify_l3_connectivity( - alt_floating_ip, - alt_keypair['private_key'], - fixed_ip1, - conn_expected=False - ) - - self.verify_l3_connectivity( - floating_ip1, - keypair['private_key'], - fixed_ip2, - conn_expected=False - ) - - self.verify_l3_connectivity( - floating_ip1, - keypair['private_key'], - alt_floating_ip, - conn_expected=True - ) - - self.terminate_instance( - instance=alt_instance, - servers_client=self.os_alt.servers_client) - self.terminate_instance(instance=instance1) diff --git a/releasenotes/notes/tempest_plugin_removal-009f9ce8456b16fe.yaml b/releasenotes/notes/tempest_plugin_removal-009f9ce8456b16fe.yaml new file mode 100644 index 0000000000..13ebb01915 --- /dev/null +++ b/releasenotes/notes/tempest_plugin_removal-009f9ce8456b16fe.yaml @@ -0,0 +1,9 @@ +--- +other: + - | + The tempest plugin code that was in ``ironic_tempest_plugin/`` has been + removed. Tempest plugin code has been migrated to the project + `openstack/ironic-tempest-plugin + `_. This + was an OpenStack wide `goal for the Queens cycle + `_. diff --git a/setup.cfg b/setup.cfg index 9ed647b558..bcc06bfc13 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,6 @@ data_files = etc/ironic/rootwrap.d = etc/ironic/rootwrap.d/* packages = ironic - ironic_tempest_plugin [entry_points] oslo.config.opts = @@ -179,15 +178,11 @@ ironic.hardware.types = ironic.database.migration_backend = sqlalchemy = ironic.db.sqlalchemy.migration -tempest.test_plugins = - ironic_tests = ironic_tempest_plugin.plugin:IronicTempestPlugin - [pbr] autodoc_index_modules = True autodoc_exclude_modules = ironic.db.sqlalchemy.alembic.env ironic.db.sqlalchemy.alembic.versions.* - ironic_tempest_plugin.* ironic.drivers.modules.ansible.playbooks* api_doc_dir = contributor/api diff --git a/tools/flake8wrap.sh b/tools/flake8wrap.sh index bcd47b22dd..531b407d6a 100755 --- a/tools/flake8wrap.sh +++ b/tools/flake8wrap.sh @@ -9,24 +9,6 @@ # tox -epep8 -- -HEAD # -TEMP_SHA_FILE="plugin.$$.SHA256SUM" -find ironic_tempest_plugin/ -type f | xargs sha256sum | sort > ${TEMP_SHA_FILE} -if ! diff -q ${TEMP_SHA_FILE} tools/ironic_tempest_plugin.SHA256SUM; -then - rm ${TEMP_SHA_FILE} - echo "" - echo "*******************************************************" - echo "ERROR: Detected changes made to the ironic_tempest_plugin/ directory" - echo "ERROR: Changes to the ironic_tempest_plugin/ are not allowed as" - echo "ERROR: we no longer use that content and it will be removed" - echo "ERROR: Please add changes to the tempest tests in the repository:" - echo "ERROR: openstack/ironic-tempest-plugin" - echo "*******************************************************" - echo "" - exit 1 -fi -rm ${TEMP_SHA_FILE} - if test "x$1" = "x-HEAD" ; then shift files=$(git diff --name-only HEAD~1 | tr '\n' ' ') diff --git a/tools/ironic_tempest_plugin.SHA256SUM b/tools/ironic_tempest_plugin.SHA256SUM deleted file mode 100644 index 3dc646db51..0000000000 --- a/tools/ironic_tempest_plugin.SHA256SUM +++ /dev/null @@ -1,38 +0,0 @@ -0ab102bcc2208285e9390ba5582648eb90be9a78e46ca847832caa7364a4b71d ironic_tempest_plugin/tests/api/admin/test_ports.py -0e298a785d0d76cfc0bd35460e9cf20e68d06275f4af4e6cc534c86b0c77bfba ironic_tempest_plugin/tests/scenario/test_baremetal_basic_ops.py -1217c75e72b8d2ed84f1f34ca9886032bbf0923d21a9930b4f8ea7520723b0ae ironic_tempest_plugin/tests/api/admin/test_portgroups.py -20c648134441fd39a72a24b011b012f0e58057560ff713eb261eebfa9a8f402c ironic_tempest_plugin/README.rst -3226a0449cae731d9706c6d036e1cc4d189d52ab854e0a5f7321122deba87c67 ironic_tempest_plugin/tests/scenario/ironic_standalone/test_basic_ops.py -41d9589c33d784c7d0b1c094f8052ce742a32fd5c6c1ecbfd45c23a42277fcf5 ironic_tempest_plugin/tests/api/admin/test_chassis.py -49446e3dfe214cdecec7121fe0d8a4f83a1db9c5b1dc6cd370427fff19bac072 ironic_tempest_plugin/tests/scenario/baremetal_manager.py -5ef9ed14ee2bdd1e0c94f35f2e7b9daf633ac4ca347f008fac224db245a20697 ironic_tempest_plugin/services/baremetal/base.py -727c722b1012c006226904456a96ac891e23be2ecbd9f301c4dfb7cf4b2412d6 ironic_tempest_plugin/clients.py -79fc7402060de6bdbb198148314bcdead6bae6350ccdee6407acb88b72990eff ironic_tempest_plugin/common/utils.py -891e10aac2728237718f9116d721ef61cb5c8ffa431fa3d2da55e125934e8c25 ironic_tempest_plugin/tests/scenario/test_baremetal_multitenancy.py -9836f4687f371942db76d987b3c9e6aa7cf72b98e040ac7f7844133f6ac371f1 ironic_tempest_plugin/tests/api/admin/api_microversion_fixture.py -9b82de2d28ca5cf76d03bcf2c6ff72898718c4516fa65040cdda63ee7e1482a1 ironic_tempest_plugin/tests/api/admin/test_nodes.py -9e69376e083d1abcb288aabe35e8d8903c1afa47796c65357fd193e681c30743 ironic_tempest_plugin/manager.py -ac50364f648c8b4d581c428aa65ef530f4382f07cb2c01a87589234a8373c5a3 ironic_tempest_plugin/tests/api/admin/test_volume_target.py -b5f2aed1f89b4c716d4b3915a388ccf3ed9c0eb72be20ec5b8857259e9db8664 ironic_tempest_plugin/common/waiters.py -bc899847544846a0413c25cc6656c7bdec7d1a772b4681f8bb09d3e71bdae61a ironic_tempest_plugin/config.py -bdcf5b98ee3c63d5d54bd824e650be19b5b991ba54ce60dc951a942c44f9fcd8 ironic_tempest_plugin/tests/scenario/baremetal_standalone_manager.py -c396d4d00f4d3078cbf5f89c06ac5db107d82f1fa5bae00da5bff5e3ddd56cb5 ironic_tempest_plugin/tests/api/admin/base.py -ccb280293b155925fec767a8e3c9add917f9e14a5fccd73dde2fe57ce62ce367 ironic_tempest_plugin/tests/scenario/test_baremetal_boot_from_volume.py -d7390c03610c91a71f2b9864a213c2751b9b269c2701982942054fd5f3b0a7cd ironic_tempest_plugin/tests/api/admin/test_api_discovery.py -d9543670e0de470f6a2ed2957cd712fcef1bf32006fc07dc4152f546bd5dd175 ironic_tempest_plugin/tests/api/admin/test_ports_negative.py -db55378fdc5a4a3c00b8901d25ba83e82cd02e8232ae0627ba31f46edc5f5f9c ironic_tempest_plugin/services/baremetal/v1/json/baremetal_client.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/common/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/services/baremetal/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/services/baremetal/v1/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/services/baremetal/v1/json/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/services/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/tests/api/admin/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/tests/api/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/tests/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/tests/scenario/__init__.py -e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ironic_tempest_plugin/tests/scenario/ironic_standalone/__init__.py -eb4fddf42a72dce35bfc42a50367708e596ecdc158a14052d1c5f226e13332ed ironic_tempest_plugin/plugin.py -f60331afe7291c0068a42589b756ad51ec4941c8c1231b7b657a0a82ade6f958 ironic_tempest_plugin/tests/api/admin/test_drivers.py -f8ee75c2687ae8b99832201f459ffe5f7b6eac16b50a5713ee224039bd997110 ironic_tempest_plugin/tests/api/admin/test_volume_connector.py -fdf8369063f1fb2b357012b9d864421be98e3a2c4f52987b240b1980a66cbedc ironic_tempest_plugin/tests/api/admin/test_nodestates.py