cloudroast/cloudroast/compute/fixtures.py

369 lines
15 KiB
Python

"""
Copyright 2013 Rackspace
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
import sys
from cafe.drivers.unittest.fixtures import BaseTestFixture
from cloudcafe.common.resources import ResourcePool
from cloudcafe.compute.config import ComputeEndpointConfig, \
MarshallingConfig
from cloudcafe.compute.composites import ComputeComposite, \
ComputeAdminComposite, ComputeIntegrationComposite
from cloudcafe.compute.common.exception_handler import ExceptionHandler
from cloudcafe.compute.common.clients.ping import PingClient
from cloudcafe.compute.common.exceptions import ServerUnreachable
from cloudcafe.objectstorage.composites import ObjectStorageComposite
from cloudcafe.blockstorage.volumes_api.common.config import VolumesAPIConfig
class ComputeFixture(BaseTestFixture):
"""
@summary: Base fixture for compute tests
"""
@classmethod
def setUpClass(cls):
super(ComputeFixture, cls).setUpClass()
cls.compute = ComputeComposite()
# Configs
cls.flavors_config = cls.compute.flavors.config
cls.images_config = cls.compute.images.config
cls.servers_config = cls.compute.servers.config
cls.volumes_config = VolumesAPIConfig()
cls.compute_endpoint = ComputeEndpointConfig()
cls.marshalling = MarshallingConfig()
cls.config_drive_config = cls.compute.config_drive.config
cls.cloud_init_config = cls.compute.config_drive.cloud_init_config
cls.user_config = cls.compute.user
cls.security_groups_config = cls.compute.security_groups.config
# Common config values
cls.flavor_ref = cls.flavors_config.primary_flavor
cls.flavor_ref_alt = cls.flavors_config.secondary_flavor
cls.image_ref = cls.images_config.primary_image
cls.image_ref_alt = cls.images_config.secondary_image
cls.bootable_volume_ref = cls.volumes_config.primary_bootable_volume
cls.disk_path = cls.servers_config.instance_disk_path
cls.split_ephemeral_disk_enabled = \
cls.servers_config.split_ephemeral_disk_enabled
cls.ephemeral_disk_max_size = \
cls.servers_config.ephemeral_disk_max_size
cls.disk_format_type = cls.servers_config.disk_format_type
cls.expected_networks = cls.servers_config.expected_networks
cls.file_injection_enabled = \
cls.servers_config.personality_file_injection_enabled
cls.keep_resources_on_failure =\
cls.servers_config.keep_resources_on_failure
cls.compute_exception_handler = ExceptionHandler()
# Clients
cls.flavors_client = cls.compute.flavors.client
cls.servers_client = cls.compute.servers.client
cls.boot_from_volume_client = cls.compute.boot_from_volume.client
cls.images_client = cls.compute.images.client
cls.keypairs_client = cls.compute.keypairs.client
cls.security_groups_client = cls.compute.security_groups.client
cls.security_group_rule_client = \
cls.compute.security_groups.rules_client
cls.volume_attachments_client = cls.compute.volume_attachments.client
cls.rescue_client = cls.compute.rescue.client
cls.vnc_client = cls.compute.vnc_console.client
cls.console_output_client = cls.compute.console_output.client
cls.limits_client = cls.compute.limits.client
cls.server_behaviors = cls.compute.servers.behaviors
cls.volume_server_behaviors = cls.compute.boot_from_volume.behaviors
cls.image_behaviors = cls.compute.images.behaviors
cls.config_drive_behaviors = cls.compute.config_drive.behaviors
cls.flavors_client.add_exception_handler(cls.compute_exception_handler)
cls.resources = ResourcePool()
cls.addClassCleanup(cls.resources.release)
@classmethod
def tearDownClass(cls):
super(ComputeFixture, cls).tearDownClass()
cls.flavors_client.delete_exception_handler(
cls.compute_exception_handler)
def tearDown(self):
super(ComputeFixture, self).tearDown()
if self.keep_resources_on_failure:
if (self._resultForDoCleanups.failures or
self._resultForDoCleanups.errors):
self.resources.resources = []
super(ComputeFixture, self).tearDownClass()
@classmethod
def parse_image_id(cls, image_response):
"""
@summary: Extract Image Id from Image response
@param image_response: Image response
@type image_response: string
@return: Image id
@rtype: string
"""
image_ref = image_response.headers['location']
return image_ref.rsplit('/')[-1]
def validate_instance_action(self, action, server_id,
user_id, project_id, request_id):
message = "Expected {0} to be {1}, was {2}."
self.assertEqual(action.instance_uuid, server_id,
msg=message.format('instance id',
action.instance_uuid,
server_id))
self.assertEqual(action.user_id, user_id,
msg=message.format('user id',
action.user_id,
user_id))
self.assertEqual(action.project_id, project_id,
msg=message.format('project id',
action.project_id,
project_id))
self.assertIsNotNone(action.start_time)
self.assertEquals(action.request_id, request_id,
msg=message.format('request id',
action.request_id,
request_id))
self.assertIsNone(action.message)
@classmethod
def get_accessible_ip_address(cls, server):
"""
@summary: Get the IP address of an instance based on the configuration
(IPv4 or IPv6) and check if the ip is reachable
@param server: Server instance
@type server: Server object
@return: IP
@rtype: string
"""
address = server.addresses.get_by_name(
cls.servers_config.network_for_ssh)
version = cls.servers_config.ip_address_version_for_ssh
ping_ip = address.ipv4 if version == 4 else address.ipv6
cls.verify_server_reachable(ping_ip)
return ping_ip
@classmethod
def verify_server_reachable(cls, ip=None):
"""
@summary: Verify server connectivity with a basic ping check.
@param ip: IP address to ping.
Defaults to SSH IPv4 address if no IP is provided
@type ip: string
"""
if not ip:
ip = cls.server.addresses.get_by_name(
cls.servers_config.network_for_ssh).ipv4
if not PingClient.ping(ip):
raise ServerUnreachable(
"Server {id} was not pingable at {ip}".format(
id=cls.server.id, ip=ip))
def _verify_ephemeral_disk_size(self, disks, flavor,
split_ephemeral_disk_enabled=False,
ephemeral_disk_max_size=sys.maxint):
ephemeral_disk_size = flavor.ephemeral_disk
# If ephemeral disk splitting is enabled, determine the number of
# ephemeral disks that should be present
if split_ephemeral_disk_enabled:
instance_ephemeral_disks = len(disks.keys())
self.assertEqual(
instance_ephemeral_disks,
int(flavor.extra_specs.get('number_of_data_disks')))
# If the ephemeral disk size exceeds the max size,
# set the ephemeral_disk_size to the maximum ephemeral disk size
ephemeral_disk_size = min(ephemeral_disk_max_size,
ephemeral_disk_size)
# Validate the size of each disk
for disk, size in disks.iteritems():
self.assertEqual(size, ephemeral_disk_size)
@classmethod
def _format_disk(cls, remote_client, disk, disk_format):
remote_client.format_disk(filesystem_type=disk_format, disk=disk)
@classmethod
def _mount_disk(cls, remote_client, disk, mount_point):
remote_client.create_directory(mount_point)
remote_client.mount_disk(disk, mount_point)
class ComputeAdminFixture(ComputeFixture):
"""
@summary: Base fixture for compute tests
"""
@classmethod
def setUpClass(cls):
super(ComputeAdminFixture, cls).setUpClass()
cls.compute_admin = ComputeAdminComposite()
cls.admin_rescue_client = cls.compute_admin.rescue.client
cls.admin_flavors_client = cls.compute_admin.flavors.client
cls.admin_servers_client = cls.compute_admin.servers.client
cls.admin_images_client = cls.compute_admin.images.client
cls.admin_hosts_client = cls.compute_admin.hosts.client
cls.admin_quotas_client = cls.compute_admin.quotas.client
cls.admin_hypervisors_client = cls.compute_admin.hypervisors.client
cls.admin_server_behaviors = cls.compute_admin.servers.behaviors
cls.admin_images_behaviors = cls.compute_admin.images.behaviors
cls.admin_servers_client.add_exception_handler(ExceptionHandler())
@classmethod
def tearDownClass(cls):
super(ComputeAdminFixture, cls).tearDownClass()
cls.flavors_client.delete_exception_handler(ExceptionHandler())
cls.resources.release()
class BlockstorageIntegrationFixture(ComputeFixture):
@classmethod
def setUpClass(cls):
super(BlockstorageIntegrationFixture, cls).setUpClass()
cls.compute_integration = ComputeIntegrationComposite()
volumes = cls.compute_integration.volumes
cls.poll_frequency = volumes.config.volume_status_poll_frequency
cls.volume_size = int(volumes.config.min_volume_from_image_size)
cls.volume_type = volumes.config.default_volume_type
cls.volume_delete_timeout = volumes.config.volume_delete_max_timeout
cls.volume_create_timeout = volumes.config.volume_create_max_timeout
cls.blockstorage_client = volumes.client
cls.blockstorage_behavior = volumes.behaviors
class ObjectstorageIntegrationFixture(ComputeFixture):
@classmethod
def setUpClass(cls):
super(ObjectstorageIntegrationFixture, cls).setUpClass()
cls.object_storage_api = ObjectStorageComposite()
cls.object_storage_client = cls.object_storage_api.client
cls.object_storage_behaviors = cls.object_storage_api.behaviors
class ServerFromImageFixture(ComputeFixture):
@classmethod
def create_server(cls, flavor_ref=None, key_name=None, image_ref=None):
"""
@summary:Creates a server from image and waits for active status
@param flavor_ref: The flavor used to build the server.
@type key_name: String
@param key_name: Generated key for the instance
@type image_ref: String
@param image_ref: Image id used to build an instance
@type flavor_ref: String
@return: Response Object containing response code and
the server domain object
@rtype: Request Response Object
"""
cls.server_response = cls.server_behaviors.create_active_server(
flavor_ref=flavor_ref, key_name=key_name, image_ref=image_ref)
cls.server = cls.server_response.entity
cls.resources.add(cls.server.id, cls.servers_client.delete_server)
return cls.server
class ServerFromVolumeV1Fixture(BlockstorageIntegrationFixture):
@classmethod
def create_server(cls, flavor_ref=None, key_name=None):
"""
@summary:Creates a server from volume V1 and waits for active status
@param flavor_ref: The flavor used to build the server.
@type key_name: String
@param key_name: Generated key for the instance
@type flavor_ref: String
@return: Response Object containing response code and
the server domain object
@rtype: Request Response Object
"""
# Creating a volume for the block device mapping
failures = []
attempts = cls.servers_config.resource_build_attempts
for attempt in range(attempts):
try:
cls.volume = cls.blockstorage_behavior.create_available_volume(
size=cls.volume_size,
volume_type=cls.volume_type,
image_ref=cls.image_ref)
except Exception as ex:
if '507' in ex.message:
time.sleep(cls.servers_config.server_status_interval)
failures.append(ex.message)
# Creating block device mapping used for server creation
cls.block_device_mapping_matrix = [{
"volume_id": cls.volume.id_,
"delete_on_termination": True,
"device_name": cls.images_config.primary_image_default_device,
"size": cls.volume_size,
"type": ''}]
# Creating the Boot from Volume Version 1 Instance
cls.server_response = cls.server_behaviors.create_active_server(
block_device_mapping=cls.block_device_mapping_matrix,
flavor_ref=flavor_ref, key_name=key_name)
cls.server = cls.server_response.entity
cls.resources.add(cls.server.id, cls.servers_client.delete_server)
return cls.server
class ServerFromVolumeV2Fixture(BlockstorageIntegrationFixture):
@classmethod
def create_server(cls, flavor_ref=None, key_name=None):
"""
@summary: Base fixture for compute tests creating the Boot from Volume
Version 2 Instance Changes between the two versions are block device
mapping is deprecated in favor of block device which is now creating
the volume behind the scenes
@param flavor_ref: The flavor used to build the server.
@type key_name: String
@param key_name: Generated key for the instance
@type flavor_ref: String
@return: Response Object containing response code and
the server domain object
@rtype: Request Response Object
"""
# Creating block device used for server creation
cls.block_device_matrix = [{
"boot_index": 0,
"uuid": cls.image_ref,
"volume_size": cls.volume_size,
"source_type": 'image',
"destination_type": 'volume',
"delete_on_termination": True}]
# Creating the Boot from Volume Version 2 Instance
cls.server_response = cls.volume_server_behaviors.create_active_server(
block_device=cls.block_device_matrix, flavor_ref=flavor_ref,
key_name=key_name)
cls.server = cls.server_response.entity
cls.resources.add(cls.server.id, cls.servers_client.delete_server)
return cls.server