369 lines
15 KiB
Python
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
|