|  | #    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 clients | 
|  | from tempest.common.utils import data_utils | 
|  | from tempest import config | 
|  | from tempest import exceptions as exc | 
|  | from tempest import test | 
|  |  | 
|  | 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'] | 
|  |  | 
|  |  | 
|  | 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(test.BaseTestCase): | 
|  | """Base class for Baremetal API tests.""" | 
|  |  | 
|  | @classmethod | 
|  | def setUpClass(cls): | 
|  | super(BaseBaremetalTest, cls).setUpClass() | 
|  |  | 
|  | if not CONF.service_available.ironic: | 
|  | skip_msg = ('%s skipped as Ironic is not available' % cls.__name__) | 
|  | raise cls.skipException(skip_msg) | 
|  |  | 
|  | 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) | 
|  | cls.driver = CONF.baremetal.driver | 
|  |  | 
|  | mgr = clients.AdminManager() | 
|  | cls.client = mgr.baremetal_client | 
|  | cls.power_timeout = CONF.baremetal.power_timeout | 
|  | cls.created_objects = {'chassis': set(), | 
|  | 'port': set(), | 
|  | 'node': set()} | 
|  |  | 
|  | @classmethod | 
|  | def tearDownClass(cls): | 
|  | """Ensure that all created objects get destroyed.""" | 
|  |  | 
|  | try: | 
|  | for resource, uuids in cls.created_objects.iteritems(): | 
|  | delete_method = getattr(cls.client, 'delete_%s' % resource) | 
|  | for u in uuids: | 
|  | delete_method(u, ignore_errors=exc.NotFound) | 
|  | finally: | 
|  | super(BaseBaremetalTest, cls).tearDownClass() | 
|  |  | 
|  | @classmethod | 
|  | @creates('chassis') | 
|  | def create_chassis(cls, description=None, expect_errors=False): | 
|  | """ | 
|  | Wrapper utility for creating test chassis. | 
|  |  | 
|  | :param description: A description of the chassis. if not supplied, | 
|  | a random value will be generated. | 
|  | :return: Created chassis. | 
|  |  | 
|  | """ | 
|  | description = description or data_utils.rand_name('test-chassis-') | 
|  | resp, body = cls.client.create_chassis(description=description) | 
|  | return resp, body | 
|  |  | 
|  | @classmethod | 
|  | @creates('node') | 
|  | def create_node(cls, chassis_id, cpu_arch='x86', cpu_num=8, storage=1024, | 
|  | memory=4096): | 
|  | """ | 
|  | Wrapper utility for creating test baremetal nodes. | 
|  |  | 
|  | :param cpu_arch: CPU architecture of the node. Default: x86. | 
|  | :param cpu_num: Number of CPUs. Default: 8. | 
|  | :param storage: Disk size. Default: 1024. | 
|  | :param memory: Available RAM. Default: 4096. | 
|  | :return: Created node. | 
|  |  | 
|  | """ | 
|  | resp, body = cls.client.create_node(chassis_id, cpu_arch=cpu_arch, | 
|  | cpu_num=cpu_num, storage=storage, | 
|  | memory=memory, driver=cls.driver) | 
|  |  | 
|  | return resp, body | 
|  |  | 
|  | @classmethod | 
|  | @creates('port') | 
|  | def create_port(cls, node_id, address, extra=None, uuid=None): | 
|  | """ | 
|  | Wrapper utility for creating test ports. | 
|  |  | 
|  | :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. | 
|  | :return: Created port. | 
|  |  | 
|  | """ | 
|  | extra = extra or {} | 
|  | resp, body = cls.client.create_port(address=address, node_id=node_id, | 
|  | extra=extra, uuid=uuid) | 
|  |  | 
|  | return resp, body | 
|  |  | 
|  | @classmethod | 
|  | def delete_chassis(cls, chassis_id): | 
|  | """ | 
|  | Deletes a chassis having the specified UUID. | 
|  |  | 
|  | :param uuid: 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 uuid: 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 uuid: 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 | 
|  |  | 
|  | 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, | 
|  | pref=self.client.uri_prefix, | 
|  | res=resource, | 
|  | uuid=uuid) | 
|  | self.assertEqual(expected_link, link) |