Add start of the EC2/S3 API testing to tempest
Continues the effort of the https://review.openstack.org/#/c/3064/ * add EC2 keypair and volume tests * add EC2 snapshot from volume test * add EC2 floating ip disasscioation * add EC2 operation on security group * add EC2/S3 image registration * add EC2 instance run test * add Integration test with ssh, console, volume * add S3 object and bucket tests Change-Id: I0dff9b05f215b56456272f22aa1c014cd53b4f4b
This commit is contained in:
parent
c8521f2c18
commit
a23f500725
|
@ -5,5 +5,6 @@ include/swift_objects/swift_medium
|
|||
include/swift_objects/swift_large
|
||||
*.log
|
||||
*.swp
|
||||
*.swo
|
||||
*.egg-info
|
||||
.tox
|
||||
|
|
|
@ -221,3 +221,48 @@ tenant_name = admin
|
|||
# custom Keystone service catalog implementation, you probably want to leave
|
||||
# this value as "object-store"
|
||||
catalog_type = object-store
|
||||
|
||||
[boto]
|
||||
# This section contains configuration options used when executing tests
|
||||
# with boto.
|
||||
|
||||
# EC2 URL
|
||||
ec2_url = http://localhost:8773/services/Cloud
|
||||
# S3 URL
|
||||
s3_url = http://localhost:3333
|
||||
|
||||
# Use keystone ec2-* command to get those values for your test user and tenant
|
||||
aws_access =
|
||||
aws_secret =
|
||||
|
||||
#Region
|
||||
aws_region = RegionOne
|
||||
|
||||
#Image materials for S3 upload
|
||||
# ALL content of the specified directory will be uploaded to S3
|
||||
s3_materials_path = /opt/stack/devstack/files/images/s3-materials/cirros-0.3.0
|
||||
|
||||
# The manifest.xml files, must be in the s3_materials_path directory
|
||||
# Subdirectories not allowed!
|
||||
# The filenames will be used as a Keys in the S3 Buckets
|
||||
|
||||
#ARI Ramdisk manifest. Must be in the above s3_materials_path
|
||||
ari_manifest = cirros-0.3.0-x86_64-initrd.manifest.xml
|
||||
|
||||
#AMI Machine Image manifest. Must be in the above s3_materials_path
|
||||
ami_manifest = cirros-0.3.0-x86_64-blank.img.manifest.xml
|
||||
|
||||
#AKI Kernel Image manifest, Must be in the above s3_materials_path
|
||||
aki_manifest = cirros-0.3.0-x86_64-vmlinuz.manifest.xml
|
||||
|
||||
#Instance type
|
||||
instance_type = m1.tiny
|
||||
|
||||
#TCP/IP connection timeout
|
||||
http_socket_timeout = 5
|
||||
|
||||
# Status change wait timout
|
||||
build_timeout = 120
|
||||
|
||||
# Status change wait interval
|
||||
build_interval = 1
|
||||
|
|
|
@ -191,3 +191,48 @@ tenant_name = %TENANT_NAME%
|
|||
# custom Keystone service catalog implementation, you probably want to leave
|
||||
# this value as "object-store"
|
||||
catalog_type = %OBJECT_CATALOG_TYPE%
|
||||
|
||||
[boto]
|
||||
# This section contains configuration options used when executing tests
|
||||
# with boto.
|
||||
|
||||
# EC2 URL
|
||||
ec2_url = %BOTO_EC2_URL%
|
||||
# S3 URL
|
||||
s3_url = %BOTO_S3_URL%
|
||||
|
||||
# Use keystone ec2-* command to get those values for your test user and tenant
|
||||
aws_access = %BOTO_AWS_ACCESS%
|
||||
aws_secret = %BOTO_AWS_SECRET%
|
||||
|
||||
#Region
|
||||
aws_region = %BOTO_AWS_REGION%
|
||||
|
||||
#Image materials for S3 upload
|
||||
# ALL content of the specified directory will be uploaded to S3
|
||||
s3_materials_path = %BOTO_S3_MATERIALS_PATH%
|
||||
|
||||
# The manifest.xml files, must be in the s3_materials_path directory
|
||||
# Subdirectories not allowed!
|
||||
# The filenames will be used as a Keys in the S3 Buckets
|
||||
|
||||
#ARI Ramdisk manifest. Must be in the above s3_materials_path directory
|
||||
ari_manifest = %BOTO_ARI_MANIFEST%
|
||||
|
||||
#AMI Machine Image manifest. Must be in the above s3_materials_path directory
|
||||
ami_manifest = %BOTO_AMI_MANIFEST%
|
||||
|
||||
#AKI Kernel Image manifest, Must be in the above s3_materials_path directory
|
||||
aki_manifest = %BOTO_AKI_MANIFEST%
|
||||
|
||||
#Instance type
|
||||
instance_type = %BOTO_FLAVOR_NAME%
|
||||
|
||||
#TCP/IP connection timeout
|
||||
http_socket_timeout = %BOTO_SOCKET_TIMEOUT%
|
||||
|
||||
# Status change wait timout
|
||||
build_timeout = %BOTO_BUILD_TIMEOUT%
|
||||
|
||||
# Status change wait interval
|
||||
build_interval = %BOTO_BUILD_INTERVAL%
|
||||
|
|
|
@ -20,21 +20,26 @@ import socket
|
|||
import warnings
|
||||
import select
|
||||
|
||||
from cStringIO import StringIO
|
||||
from tempest import exceptions
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
import paramiko
|
||||
from paramiko import RSAKey
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
||||
def __init__(self, host, username, password=None, timeout=300,
|
||||
def __init__(self, host, username, password=None, timeout=300, pkey=None,
|
||||
channel_timeout=10, look_for_keys=False, key_filename=None):
|
||||
self.host = host
|
||||
self.username = username
|
||||
self.password = password
|
||||
if isinstance(pkey, basestring):
|
||||
pkey = RSAKey.from_private_key(StringIO(str(pkey)))
|
||||
self.pkey = pkey
|
||||
self.look_for_keys = look_for_keys
|
||||
self.key_filename = key_filename
|
||||
self.timeout = int(timeout)
|
||||
|
@ -55,7 +60,7 @@ class Client(object):
|
|||
password=self.password,
|
||||
look_for_keys=self.look_for_keys,
|
||||
key_filename=self.key_filename,
|
||||
timeout=self.timeout)
|
||||
timeout=self.timeout, pkey=self.pkey)
|
||||
_timeout = False
|
||||
break
|
||||
except socket.error:
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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.
|
||||
|
||||
|
||||
def have_effective_read_access(path):
|
||||
try:
|
||||
fh = open(path, "rb")
|
||||
except IOError:
|
||||
return False
|
||||
fh.close()
|
||||
return True
|
|
@ -4,25 +4,29 @@ from tempest.common import utils
|
|||
from tempest.exceptions import SSHTimeout, ServerUnreachable
|
||||
|
||||
import time
|
||||
import re
|
||||
|
||||
|
||||
class RemoteClient():
|
||||
|
||||
def __init__(self, server, username, password):
|
||||
#Note(afazekas): It should always get an address instead of server
|
||||
def __init__(self, server, username, password=None, pkey=None):
|
||||
ssh_timeout = TempestConfig().compute.ssh_timeout
|
||||
network = TempestConfig().compute.network_for_ssh
|
||||
ip_version = TempestConfig().compute.ip_version_for_ssh
|
||||
if isinstance(server, basestring):
|
||||
ip_address = server
|
||||
else:
|
||||
addresses = server['addresses'][network]
|
||||
|
||||
for address in addresses:
|
||||
if address['version'] == ip_version:
|
||||
ip_address = address['addr']
|
||||
break
|
||||
|
||||
if ip_address is None:
|
||||
else:
|
||||
raise ServerUnreachable()
|
||||
|
||||
self.ssh_client = Client(ip_address, username, password, ssh_timeout)
|
||||
self.ssh_client = Client(ip_address, username, password, ssh_timeout,
|
||||
pkey=pkey)
|
||||
if not self.ssh_client.test_connection_auth():
|
||||
raise SSHTimeout()
|
||||
|
||||
|
@ -62,3 +66,9 @@ class RemoteClient():
|
|||
boot_time_string = self.ssh_client.exec_command(cmd)
|
||||
boot_time_string = boot_time_string.replace('\n', '')
|
||||
return time.strptime(boot_time_string, utils.LAST_REBOOT_TIME_FORMAT)
|
||||
|
||||
def write_to_console(self, message):
|
||||
message = re.sub("([$\\`])", "\\\\\\\\\\1", message)
|
||||
# usually to /dev/ttyS0
|
||||
cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
|
||||
return self.ssh_client.exec_command(cmd)
|
||||
|
|
|
@ -37,6 +37,12 @@ class BaseConfig(object):
|
|||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
return default_value
|
||||
|
||||
def getboolean(self, item_name, default_value=None):
|
||||
try:
|
||||
return self.conf.getboolean(self.SECTION_NAME, item_name)
|
||||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
return default_value
|
||||
|
||||
|
||||
class IdentityConfig(BaseConfig):
|
||||
|
||||
|
@ -414,6 +420,80 @@ class ObjectStorageConfig(BaseConfig):
|
|||
return self.get("catalog_type", 'object-store')
|
||||
|
||||
|
||||
class BotoConfig(BaseConfig):
|
||||
"""Provides configuration information for connecting to EC2/S3."""
|
||||
SECTION_NAME = "boto"
|
||||
|
||||
@property
|
||||
def ec2_url(self):
|
||||
"""EC2 URL"""
|
||||
return self.get("ec2_url", "http://localhost:8773/services/Cloud")
|
||||
|
||||
@property
|
||||
def s3_url(self):
|
||||
"""S3 URL"""
|
||||
return self.get("s3_url", "http://localhost:8080")
|
||||
|
||||
@property
|
||||
def aws_secret(self):
|
||||
"""AWS Secret Key"""
|
||||
return self.get("aws_secret")
|
||||
|
||||
@property
|
||||
def aws_access(self):
|
||||
"""AWS Access Key"""
|
||||
return self.get("aws_access")
|
||||
|
||||
@property
|
||||
def aws_region(self):
|
||||
"""AWS Region"""
|
||||
return self.get("aws_region", "RegionOne")
|
||||
|
||||
@property
|
||||
def s3_materials_path(self):
|
||||
return self.get("s3_materials_path",
|
||||
"/opt/stack/devstack/files/images/"
|
||||
"s3-materials/cirros-0.3.0")
|
||||
|
||||
@property
|
||||
def ari_manifest(self):
|
||||
"""ARI Ramdisk Image manifest"""
|
||||
return self.get("ari_manifest",
|
||||
"cirros-0.3.0-x86_64-initrd.manifest.xml")
|
||||
|
||||
@property
|
||||
def ami_manifest(self):
|
||||
"""AMI Machine Image manifest"""
|
||||
return self.get("ami_manifest",
|
||||
"cirros-0.3.0-x86_64-blank.img.manifest.xml")
|
||||
|
||||
@property
|
||||
def aki_manifest(self):
|
||||
"""AKI Kernel Image manifest"""
|
||||
return self.get("aki_manifest",
|
||||
"cirros-0.3.0-x86_64-vmlinuz.manifest.xml")
|
||||
|
||||
@property
|
||||
def instance_type(self):
|
||||
"""Instance type"""
|
||||
return self.get("Instance type", "m1.tiny")
|
||||
|
||||
@property
|
||||
def http_socket_timeout(self):
|
||||
"""boto Http socket timeout"""
|
||||
return self.get("http_socket_timeout", "3")
|
||||
|
||||
@property
|
||||
def build_timeout(self):
|
||||
"""status change timeout"""
|
||||
return float(self.get("build_timeout", "60"))
|
||||
|
||||
@property
|
||||
def build_interval(self):
|
||||
"""status change test interval"""
|
||||
return float(self.get("build_interval", 1))
|
||||
|
||||
|
||||
# TODO(jaypipes): Move this to a common utils (not data_utils...)
|
||||
def singleton(cls):
|
||||
"""Simple wrapper for classes that should only have a single instance"""
|
||||
|
@ -463,6 +543,7 @@ class TempestConfig:
|
|||
self.network = NetworkConfig(self._conf)
|
||||
self.volume = VolumeConfig(self._conf)
|
||||
self.object_storage = ObjectStorageConfig(self._conf)
|
||||
self.boto = BotoConfig(self._conf)
|
||||
|
||||
def load_config(self, path):
|
||||
"""Read configuration from given path and return a config object."""
|
||||
|
|
|
@ -1,3 +1,21 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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.
|
||||
|
||||
|
||||
class TempestException(Exception):
|
||||
"""
|
||||
Base Tempest Exception
|
||||
|
@ -51,6 +69,11 @@ class AddImageException(TempestException):
|
|||
message = "Image %(image_id) failed to become ACTIVE in the allotted time"
|
||||
|
||||
|
||||
class EC2RegisterImageException(TempestException):
|
||||
message = ("Image %(image_id) failed to become 'available' "
|
||||
"in the allotted time")
|
||||
|
||||
|
||||
class VolumeBuildErrorException(TempestException):
|
||||
message = "Volume %(volume_id)s failed to build and is in ERROR status"
|
||||
|
||||
|
@ -106,3 +129,7 @@ class ServerUnreachable(TempestException):
|
|||
|
||||
class SQLException(TempestException):
|
||||
message = "SQL error: %(message)s"
|
||||
|
||||
|
||||
class TearDownException(TempestException):
|
||||
message = "%(num)d cleanUp operation failed"
|
||||
|
|
|
@ -57,6 +57,8 @@ from tempest.services.volume.xml.volumes_client import VolumesClientXML
|
|||
from tempest.services.object_storage.account_client import AccountClient
|
||||
from tempest.services.object_storage.container_client import ContainerClient
|
||||
from tempest.services.object_storage.object_client import ObjectClient
|
||||
from tempest.services.boto.clients import APIClientEC2
|
||||
from tempest.services.boto.clients import ObjectClientS3
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -186,6 +188,8 @@ class Manager(object):
|
|||
self.account_client = AccountClient(*client_args)
|
||||
self.container_client = ContainerClient(*client_args)
|
||||
self.object_client = ObjectClient(*client_args)
|
||||
self.ec2api_client = APIClientEC2(*client_args)
|
||||
self.s3_client = ObjectClientS3(*client_args)
|
||||
|
||||
|
||||
class AltManager(Manager):
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 boto
|
||||
|
||||
from ConfigParser import DuplicateSectionError
|
||||
|
||||
from tempest.exceptions import InvalidConfiguration
|
||||
from tempest.exceptions import NotFound
|
||||
|
||||
import re
|
||||
from types import MethodType
|
||||
from contextlib import closing
|
||||
|
||||
|
||||
class BotoClientBase(object):
|
||||
|
||||
ALLOWED_METHODS = set()
|
||||
|
||||
def __init__(self, config,
|
||||
username=None, password=None,
|
||||
auth_url=None, tenant_name=None,
|
||||
*args, **kwargs):
|
||||
|
||||
self.connection_timeout = config.boto.http_socket_timeout
|
||||
self.build_timeout = config.boto.build_timeout
|
||||
# We do not need the "path": "/token" part
|
||||
if auth_url:
|
||||
auth_url = re.sub("(.*)" + re.escape(config.identity.path) + "$",
|
||||
"\\1", auth_url)
|
||||
self.ks_cred = {"username": username,
|
||||
"password": password,
|
||||
"auth_url": auth_url,
|
||||
"tenant_name": tenant_name}
|
||||
|
||||
def _keystone_aws_get(self):
|
||||
import keystoneclient.v2_0.client
|
||||
|
||||
keystone = keystoneclient.v2_0.client.Client(**self.ks_cred)
|
||||
ec2_cred_list = keystone.ec2.list(keystone.auth_user_id)
|
||||
ec2_cred = None
|
||||
for cred in ec2_cred_list:
|
||||
if cred.tenant_id == keystone.auth_tenant_id:
|
||||
ec2_cred = cred
|
||||
break
|
||||
else:
|
||||
ec2_cred = keystone.ec2.create(keystone.auth_user_id,
|
||||
keystone.auth_tenant_id)
|
||||
if not all((ec2_cred, ec2_cred.access, ec2_cred.secret)):
|
||||
raise NotFound("Unable to get access and secret keys")
|
||||
return ec2_cred
|
||||
|
||||
def _config_boto_timeout(self, timeout):
|
||||
try:
|
||||
boto.config.add_section("Boto")
|
||||
except DuplicateSectionError:
|
||||
pass
|
||||
boto.config.set("Boto", "http_socket_timeout", timeout)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Automatically creates methods for the allowed methods set"""
|
||||
if name in self.ALLOWED_METHODS:
|
||||
def func(self, *args, **kwargs):
|
||||
with closing(self.get_connection()) as conn:
|
||||
return getattr(conn, name)(*args, **kwargs)
|
||||
|
||||
func.__name__ = name
|
||||
setattr(self, name, MethodType(func, self, self.__class__))
|
||||
setattr(self.__class__, name,
|
||||
MethodType(func, None, self.__class__))
|
||||
return getattr(self, name)
|
||||
else:
|
||||
raise AttributeError(name)
|
||||
|
||||
def get_connection(self):
|
||||
self._config_boto_timeout(self.connection_timeout)
|
||||
if not all((self.connection_data["aws_access_key_id"],
|
||||
self.connection_data["aws_secret_access_key"])):
|
||||
if all(self.ks_cred.itervalues()):
|
||||
ec2_cred = self._keystone_aws_get()
|
||||
self.connection_data["aws_access_key_id"] = \
|
||||
ec2_cred.access
|
||||
self.connection_data["aws_secret_access_key"] = \
|
||||
ec2_cred.secret
|
||||
else:
|
||||
raise InvalidConfiguration(
|
||||
"Unable to get access and secret keys")
|
||||
return self.connect_method(**self.connection_data)
|
|
@ -0,0 +1,139 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 boto
|
||||
from boto.s3.connection import OrdinaryCallingFormat
|
||||
from boto.ec2.regioninfo import RegionInfo
|
||||
from tempest.services.boto import BotoClientBase
|
||||
import urlparse
|
||||
|
||||
|
||||
class APIClientEC2(BotoClientBase):
|
||||
|
||||
def connect_method(self, *args, **kwargs):
|
||||
return boto.connect_ec2(*args, **kwargs)
|
||||
|
||||
def __init__(self, config, *args, **kwargs):
|
||||
super(APIClientEC2, self).__init__(config, *args, **kwargs)
|
||||
aws_access = config.boto.aws_access
|
||||
aws_secret = config.boto.aws_secret
|
||||
purl = urlparse.urlparse(config.boto.ec2_url)
|
||||
|
||||
region = RegionInfo(name=config.boto.aws_region,
|
||||
endpoint=purl.hostname)
|
||||
port = purl.port
|
||||
if port is None:
|
||||
if purl.scheme is not "https":
|
||||
port = 80
|
||||
else:
|
||||
port = 443
|
||||
else:
|
||||
port = int(port)
|
||||
self.connection_data = {"aws_access_key_id": aws_access,
|
||||
"aws_secret_access_key": aws_secret,
|
||||
"is_secure": purl.scheme == "https",
|
||||
"region": region,
|
||||
"host": purl.hostname,
|
||||
"port": port,
|
||||
"path": purl.path}
|
||||
|
||||
ALLOWED_METHODS = set(('create_key_pair', 'get_key_pair',
|
||||
'delete_key_pair', 'import_key_pair',
|
||||
'get_all_key_pairs',
|
||||
'create_image', 'get_image',
|
||||
'register_image', 'deregister_image',
|
||||
'get_all_images', 'get_image_attribute',
|
||||
'modify_image_attribute', 'reset_image_attribute',
|
||||
'get_all_kernels',
|
||||
'create_volume', 'delete_volume',
|
||||
'get_all_volume_status', 'get_all_volumes',
|
||||
'get_volume_attribute', 'modify_volume_attribute'
|
||||
'bundle_instance', 'cancel_spot_instance_requests',
|
||||
'confirm_product_instanc',
|
||||
'get_all_instance_status', 'get_all_instances',
|
||||
'get_all_reserved_instances',
|
||||
'get_all_spot_instance_requests',
|
||||
'get_instance_attribute', 'monitor_instance',
|
||||
'monitor_instances', 'unmonitor_instance',
|
||||
'unmonitor_instances',
|
||||
'purchase_reserved_instance_offering',
|
||||
'reboot_instances', 'request_spot_instances',
|
||||
'reset_instance_attribute', 'run_instances',
|
||||
'start_instances', 'stop_instances',
|
||||
'terminate_instances',
|
||||
'attach_network_interface', 'attach_volume',
|
||||
'detach_network_interface', 'detach_volume',
|
||||
'get_console_output',
|
||||
'delete_network_interface', 'create_subnet',
|
||||
'create_network_interface', 'delete_subnet',
|
||||
'get_all_network_interfaces',
|
||||
'allocate_address', 'associate_address',
|
||||
'disassociate_address', 'get_all_addresses',
|
||||
'release_address',
|
||||
'create_snapshot', 'delete_snapshot',
|
||||
'get_all_snapshots', 'get_snapshot_attribute',
|
||||
'modify_snapshot_attribute',
|
||||
'reset_snapshot_attribute', 'trim_snapshots',
|
||||
'get_all_regions', 'get_all_zones',
|
||||
'get_all_security_groups', 'create_security_group',
|
||||
'delete_security_group', 'authorize_security_group',
|
||||
'authorize_security_group_egress',
|
||||
'revoke_security_group',
|
||||
'revoke_security_group_egress'))
|
||||
|
||||
def get_good_zone(self):
|
||||
"""
|
||||
:rtype: BaseString
|
||||
:return: Returns with the first available zone name
|
||||
"""
|
||||
for zone in self.get_all_zones():
|
||||
#NOTE(afazekas): zone.region_name was None
|
||||
if (zone.state == "available" and
|
||||
zone.region.name == self.connection_data["region"].name):
|
||||
return zone.name
|
||||
else:
|
||||
raise IndexError("Don't have a good zone")
|
||||
|
||||
|
||||
class ObjectClientS3(BotoClientBase):
|
||||
|
||||
def connect_method(self, *args, **kwargs):
|
||||
return boto.connect_s3(*args, **kwargs)
|
||||
|
||||
def __init__(self, config, *args, **kwargs):
|
||||
super(ObjectClientS3, self).__init__(config, *args, **kwargs)
|
||||
aws_access = config.boto.aws_access
|
||||
aws_secret = config.boto.aws_secret
|
||||
purl = urlparse.urlparse(config.boto.s3_url)
|
||||
port = purl.port
|
||||
if port is None:
|
||||
if purl.scheme is not "https":
|
||||
port = 80
|
||||
else:
|
||||
port = 443
|
||||
else:
|
||||
port = int(port)
|
||||
self.connection_data = {"aws_access_key_id": aws_access,
|
||||
"aws_secret_access_key": aws_secret,
|
||||
"is_secure": purl.scheme == "https",
|
||||
"host": purl.hostname,
|
||||
"port": port,
|
||||
"calling_format": OrdinaryCallingFormat()}
|
||||
|
||||
ALLOWED_METHODS = set(('create_bucket', 'delete_bucket', 'generate_url',
|
||||
'get_all_buckets', 'get_bucket', 'delete_key',
|
||||
'lookup'))
|
|
@ -0,0 +1,535 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 unittest2 as unittest
|
||||
import nose
|
||||
import tempest.tests.boto
|
||||
from tempest.exceptions import TearDownException
|
||||
from tempest.tests.boto.utils.wait import state_wait, wait_no_exception
|
||||
from tempest.tests.boto.utils.wait import re_search_wait, wait_exception
|
||||
import boto
|
||||
from boto.s3.key import Key
|
||||
from boto.s3.bucket import Bucket
|
||||
from boto.exception import BotoServerError
|
||||
from contextlib import closing
|
||||
import re
|
||||
import logging
|
||||
import time
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BotoExceptionMatcher(object):
|
||||
STATUS_RE = r'[45]\d\d'
|
||||
CODE_RE = '.*' # regexp makes sense in group match
|
||||
|
||||
def match(self, exc):
|
||||
if not isinstance(exc, BotoServerError):
|
||||
return "%r not an BotoServerError instance" % exc
|
||||
LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
|
||||
if re.match(self.STATUS_RE, str(exc.status)) is None:
|
||||
return ("Status code (%s) does not match"
|
||||
"the expected re pattern \"%s\""
|
||||
% (exc.status, self.STATUS_RE))
|
||||
if re.match(self.CODE_RE, str(exc.error_code)) is None:
|
||||
return ("Error code (%s) does not match" +
|
||||
"the expected re pattern \"%s\"") %\
|
||||
(exc.error_code, self.CODE_RE)
|
||||
|
||||
|
||||
class ClientError(BotoExceptionMatcher):
|
||||
STATUS_RE = r'4\d\d'
|
||||
|
||||
|
||||
class ServerError(BotoExceptionMatcher):
|
||||
STATUS_RE = r'5\d\d'
|
||||
|
||||
|
||||
def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
|
||||
"""
|
||||
Usable for adding an ExceptionMatcher(s) into the exception tree.
|
||||
The not leaf elements does wildcard match
|
||||
"""
|
||||
# in error_code just literal and '.' characters expected
|
||||
if not isinstance(error_data, basestring):
|
||||
(error_code, status_code) = map(str, error_data)
|
||||
else:
|
||||
status_code = None
|
||||
error_code = error_data
|
||||
parts = error_code.split('.')
|
||||
basematch = ""
|
||||
num_parts = len(parts)
|
||||
max_index = num_parts - 1
|
||||
add_cls = error_cls
|
||||
for i_part in xrange(num_parts):
|
||||
part = parts[i_part]
|
||||
leaf = i_part == max_index
|
||||
if not leaf:
|
||||
match = basematch + part + "[.].*"
|
||||
else:
|
||||
match = basematch + part
|
||||
|
||||
basematch += part + "[.]"
|
||||
if not hasattr(add_cls, part):
|
||||
cls_dict = {"CODE_RE": match}
|
||||
if leaf and status_code is not None:
|
||||
cls_dict["STATUS_RE"] = status_code
|
||||
cls = type(part, (base, ), cls_dict)
|
||||
setattr(add_cls, part, cls())
|
||||
add_cls = cls
|
||||
elif leaf:
|
||||
raise LookupError("Tries to redefine an error code \"%s\"" % part)
|
||||
else:
|
||||
add_cls = getattr(add_cls, part)
|
||||
|
||||
|
||||
#TODO(afazekas): classmethod handling
|
||||
def friendly_function_name_simple(call_able):
|
||||
name = ""
|
||||
if hasattr(call_able, "im_class"):
|
||||
name += call_able.im_class.__name__ + "."
|
||||
name += call_able.__name__
|
||||
return name
|
||||
|
||||
|
||||
def friendly_function_call_str(call_able, *args, **kwargs):
|
||||
string = friendly_function_name_simple(call_able)
|
||||
string += "(" + ", ".join(map(str, args))
|
||||
if len(kwargs):
|
||||
if len(args):
|
||||
string += ", "
|
||||
string += ", ".join("=".join(map(str, (key, value)))
|
||||
for (key, value) in kwargs.items())
|
||||
return string + ")"
|
||||
|
||||
|
||||
class BotoTestCase(unittest.TestCase):
|
||||
"""Recommended to use as base class for boto related test"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# The trash contains cleanup functions and paramaters in tuples
|
||||
# (function, *args, **kwargs)
|
||||
cls._resource_trash_bin = {}
|
||||
cls._sequence = -1
|
||||
if (hasattr(cls, "EC2") and
|
||||
tempest.tests.boto.EC2_CAN_CONNECT_ERROR is not None):
|
||||
raise nose.SkipTest("EC2 " + cls.__name__ + ": " +
|
||||
tempest.tests.boto.EC2_CAN_CONNECT_ERROR)
|
||||
if (hasattr(cls, "S3") and
|
||||
tempest.tests.boto.S3_CAN_CONNECT_ERROR is not None):
|
||||
raise nose.SkipTest("S3 " + cls.__name__ + ": " +
|
||||
tempest.tests.boto.S3_CAN_CONNECT_ERROR)
|
||||
|
||||
@classmethod
|
||||
def addResourceCleanUp(cls, function, *args, **kwargs):
|
||||
"""Adds CleanUp callable, used by tearDownClass.
|
||||
Recommended to a use (deep)copy on the mutable args"""
|
||||
cls._sequence = cls._sequence + 1
|
||||
cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
|
||||
return cls._sequence
|
||||
|
||||
@classmethod
|
||||
def cancelResourceCleanUp(cls, key):
|
||||
"""Cancel Clean up request"""
|
||||
del cls._resource_trash_bin[key]
|
||||
|
||||
#TODO(afazekas): Add "with" context handling
|
||||
def assertBotoError(self, excMatcher, callableObj,
|
||||
*args, **kwargs):
|
||||
"""Example usage:
|
||||
self.assertBotoError(self.ec2_error_code.client.
|
||||
InvalidKeyPair.Duplicate,
|
||||
self.client.create_keypair,
|
||||
key_name)"""
|
||||
try:
|
||||
callableObj(*args, **kwargs)
|
||||
except BotoServerError as exc:
|
||||
error_msg = excMatcher.match(exc)
|
||||
if error_msg is not None:
|
||||
raise self.failureException, error_msg
|
||||
else:
|
||||
raise self.failureException, "BotoServerError not raised"
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
""" Calls the callables added by addResourceCleanUp,
|
||||
when you overwire this function dont't forget to call this too"""
|
||||
fail_count = 0
|
||||
trash_keys = sorted(cls._resource_trash_bin, reverse=True)
|
||||
for key in trash_keys:
|
||||
(function, pos_args, kw_args) = cls._resource_trash_bin[key]
|
||||
try:
|
||||
LOG.debug("Cleaning up: %s" %
|
||||
friendly_function_call_str(function, *pos_args,
|
||||
**kw_args))
|
||||
function(*pos_args, **kw_args)
|
||||
except BaseException as exc:
|
||||
fail_count += 1
|
||||
LOG.exception(exc)
|
||||
finally:
|
||||
del cls._resource_trash_bin[key]
|
||||
if fail_count:
|
||||
raise TearDownException(num=fail_count)
|
||||
|
||||
ec2_error_code = BotoExceptionMatcher()
|
||||
# InsufficientInstanceCapacity can be both server and client error
|
||||
ec2_error_code.server = ServerError()
|
||||
ec2_error_code.client = ClientError()
|
||||
s3_error_code = BotoExceptionMatcher()
|
||||
s3_error_code.server = ServerError()
|
||||
s3_error_code.client = ClientError()
|
||||
valid_image_state = set(('available', 'pending', 'failed'))
|
||||
valid_instance_state = set(('pending', 'running', 'shutting-down',
|
||||
'terminated', 'stopping', 'stopped'))
|
||||
valid_volume_status = set(('creating', 'available', 'in-use',
|
||||
'deleting', 'deleted', 'error'))
|
||||
valid_snapshot_status = set(('pending', 'completed', 'error'))
|
||||
|
||||
#TODO(afazekas): object base version for resurces supports update
|
||||
def waitImageState(self, lfunction, wait_for):
|
||||
state = state_wait(lfunction, wait_for, self.valid_image_state)
|
||||
self.assertIn(state, self.valid_image_state)
|
||||
return state
|
||||
|
||||
def waitInstanceState(self, lfunction, wait_for):
|
||||
state = state_wait(lfunction, wait_for, self.valid_instance_state)
|
||||
self.assertIn(state, self.valid_instance_state)
|
||||
return state
|
||||
|
||||
def waitVolumeStatus(self, lfunction, wait_for):
|
||||
state = state_wait(lfunction, wait_for, self.valid_volume_status)
|
||||
self.assertIn(state, self.valid_volume_status)
|
||||
return state
|
||||
|
||||
def waitSnapshotStatus(self, lfunction, wait_for):
|
||||
state = state_wait(lfunction, wait_for, self.valid_snapshot_status)
|
||||
self.assertIn(state, self.valid_snapshot_status)
|
||||
return state
|
||||
|
||||
def assertImageStateWait(self, lfunction, wait_for):
|
||||
state = self.waitImageState(lfunction, wait_for)
|
||||
self.assertIn(state, wait_for)
|
||||
|
||||
def assertInstanceStateWait(self, lfunction, wait_for):
|
||||
state = self.waitInstanceState(lfunction, wait_for)
|
||||
self.assertIn(state, wait_for)
|
||||
|
||||
def assertVolumeStatusWait(self, lfunction, wait_for):
|
||||
state = self.waitVolumeStatus(lfunction, wait_for)
|
||||
self.assertIn(state, wait_for)
|
||||
|
||||
def assertSnapshotStatusWait(self, lfunction, wait_for):
|
||||
state = self.waitSnapshotStatus(lfunction, wait_for)
|
||||
self.assertIn(state, wait_for)
|
||||
|
||||
def assertAddressDissasociatedWait(self, address):
|
||||
|
||||
def _disassociate():
|
||||
cli = self.ec2_client
|
||||
addresses = cli.get_all_addresses(addresses=(address.public_ip,))
|
||||
if len(addresses) != 1:
|
||||
return "INVALID"
|
||||
if addresses[0].instance_id:
|
||||
LOG.info("%s associated to %s",
|
||||
address.public_ip,
|
||||
addresses[0].instance_id)
|
||||
return "ASSOCIATED"
|
||||
return "DISASSOCIATED"
|
||||
|
||||
state = state_wait(_disassociate, "DISASSOCIATED",
|
||||
set(("ASSOCIATED", "DISASSOCIATED")))
|
||||
self.assertEqual(state, "DISASSOCIATED")
|
||||
|
||||
def assertAddressReleasedWait(self, address):
|
||||
|
||||
def _address_delete():
|
||||
#NOTE(afazekas): the filter gives back IP
|
||||
# even if it is not associated to my tenant
|
||||
if (address.public_ip not in map(lambda a: a.public_ip,
|
||||
self.ec2_client.get_all_addresses())):
|
||||
return "DELETED"
|
||||
return "NOTDELETED"
|
||||
|
||||
state = state_wait(_address_delete, "DELETED")
|
||||
self.assertEqual(state, "DELETED")
|
||||
|
||||
def assertReSearch(self, regexp, string):
|
||||
if re.search(regexp, string) is None:
|
||||
raise self.failureException("regexp: '%s' not found in '%s'" %
|
||||
(regexp, string))
|
||||
|
||||
def assertNotReSearch(self, regexp, string):
|
||||
if re.search(regexp, string) is not None:
|
||||
raise self.failureException("regexp: '%s' found in '%s'" %
|
||||
(regexp, string))
|
||||
|
||||
def assertReMatch(self, regexp, string):
|
||||
if re.match(regexp, string) is None:
|
||||
raise self.failureException("regexp: '%s' not matches on '%s'" %
|
||||
(regexp, string))
|
||||
|
||||
def assertNotReMatch(self, regexp, string):
|
||||
if re.match(regexp, string) is not None:
|
||||
raise self.failureException("regexp: '%s' matches on '%s'" %
|
||||
(regexp, string))
|
||||
|
||||
@classmethod
|
||||
def destroy_bucket(cls, connection_data, bucket):
|
||||
"""Destroys the bucket and its content, just for teardown"""
|
||||
exc_num = 0
|
||||
try:
|
||||
with closing(boto.connect_s3(**connection_data)) as conn:
|
||||
if isinstance(bucket, basestring):
|
||||
bucket = conn.lookup(bucket)
|
||||
assert isinstance(bucket, Bucket)
|
||||
for obj in bucket.list():
|
||||
try:
|
||||
bucket.delete_key(obj.key)
|
||||
obj.close()
|
||||
except BaseException as exc:
|
||||
LOG.exception(exc)
|
||||
exc_num += 1
|
||||
conn.delete_bucket(bucket)
|
||||
except BaseException as exc:
|
||||
LOG.exception(exc)
|
||||
exc_num += 1
|
||||
if exc_num:
|
||||
raise TearDownException(num=exc_num)
|
||||
|
||||
@classmethod
|
||||
def destroy_reservation(cls, reservation):
|
||||
"""Terminate instances in a reservation, just for teardown"""
|
||||
exc_num = 0
|
||||
|
||||
def _instance_state():
|
||||
try:
|
||||
instance.update(validate=True)
|
||||
except ValueError:
|
||||
return "terminated"
|
||||
return instance.state
|
||||
|
||||
for instance in reservation.instances:
|
||||
try:
|
||||
instance.terminate()
|
||||
re_search_wait(_instance_state, "terminated")
|
||||
except BaseException as exc:
|
||||
LOG.exception(exc)
|
||||
exc_num += 1
|
||||
if exc_num:
|
||||
raise TearDownException(num=exc_num)
|
||||
|
||||
#NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
|
||||
# to write better teardown
|
||||
|
||||
@classmethod
|
||||
def destroy_security_group_wait(cls, group):
|
||||
"""Delete group.
|
||||
Use just for teardown!
|
||||
"""
|
||||
#NOTE(afazekas): should wait/try until all related instance terminates
|
||||
#2. looks like it is locked even if the instance not listed
|
||||
time.sleep(1)
|
||||
group.delete()
|
||||
|
||||
@classmethod
|
||||
def destroy_volume_wait(cls, volume):
|
||||
"""Delete volume, tryies to detach first.
|
||||
Use just for teardown!
|
||||
"""
|
||||
exc_num = 0
|
||||
snaps = volume.snapshots()
|
||||
if len(snaps):
|
||||
LOG.critical("%s Volume has %s snapshot(s)", volume.id,
|
||||
map(snps.id, snaps))
|
||||
|
||||
#Note(afazekas): detaching/attching not valid EC2 status
|
||||
def _volume_state():
|
||||
volume.update(validate=True)
|
||||
try:
|
||||
if volume.status != "available":
|
||||
volume.detach(force=True)
|
||||
except BaseException as exc:
|
||||
LOG.exception(exc)
|
||||
#exc_num += 1 "nonlocal" not in python2
|
||||
return volume.status
|
||||
|
||||
try:
|
||||
re_search_wait(_volume_state, "available") # not validates status
|
||||
LOG.info(_volume_state())
|
||||
volume.delete()
|
||||
except BaseException as exc:
|
||||
LOG.exception(exc)
|
||||
exc_num += 1
|
||||
if exc_num:
|
||||
raise TearDownException(num=exc_num)
|
||||
|
||||
@classmethod
|
||||
def destroy_snapshot_wait(cls, snapshot):
|
||||
"""delete snaphot, wait until not exists"""
|
||||
snapshot.delete()
|
||||
|
||||
def _update():
|
||||
snapshot.update(validate=True)
|
||||
|
||||
wait_exception(_update)
|
||||
|
||||
|
||||
# you can specify tuples if you want to specify the status pattern
|
||||
for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
|
||||
'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
|
||||
'DiskImageSizeTooLarge', 'FilterLimitExceeded',
|
||||
'Gateway.NotAttached', 'IdempotentParameterMismatch',
|
||||
'IncorrectInstanceState', 'IncorrectState',
|
||||
'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
|
||||
'InsufficientReservedInstancesCapacity',
|
||||
'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
|
||||
'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
|
||||
'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
|
||||
'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
|
||||
'InvalidCustomerGateway.DuplicateIpAddress',
|
||||
'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
|
||||
'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
|
||||
'InvalidFilter', 'InvalidGatewayID.NotFound',
|
||||
'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
|
||||
'InvalidGroup.InUse', 'InvalidGroup.NotFound',
|
||||
'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
|
||||
'InvalidInstanceID.NotFound',
|
||||
'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
|
||||
'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
|
||||
'InvalidKeyPair.NotFound', 'InvalidManifest',
|
||||
'InvalidNetworkAclEntry.NotFound',
|
||||
'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
|
||||
'InvalidParameterValue', 'InvalidPermission.Duplicate',
|
||||
'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
|
||||
'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
|
||||
'InvalidRouteTableID.NotFound',
|
||||
'InvalidSecurity.RequestHasExpired',
|
||||
'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
|
||||
'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
|
||||
'InvalidReservedInstancesOfferingId',
|
||||
'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
|
||||
'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
|
||||
'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
|
||||
'InvalidVpnConnectionID.NotFound',
|
||||
'InvalidVpnGatewayID.NotFound',
|
||||
'InvalidZone.NotFound', 'LegacySecurityGroup',
|
||||
'MissingParameter', 'NetworkAclEntryAlreadyExists',
|
||||
'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
|
||||
'NonEBSInstance', 'PendingSnapshotLimitExceeded',
|
||||
'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
|
||||
'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
|
||||
'ResourceLimitExceeded', 'RouteAlreadyExists',
|
||||
'RouteLimitExceeded', 'RouteTableLimitExceeded',
|
||||
'RulesPerSecurityGroupLimitExceeded',
|
||||
'SecurityGroupLimitExceeded',
|
||||
'SecurityGroupsPerInstanceLimitExceeded',
|
||||
'SnapshotLimitExceeded', 'SubnetLimitExceeded',
|
||||
'UnknownParameter', 'UnsupportedOperation',
|
||||
'VolumeLimitExceeded', 'VpcLimitExceeded',
|
||||
'VpnConnectionLimitExceeded',
|
||||
'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
|
||||
_add_matcher_class(BotoTestCase.ec2_error_code.client,
|
||||
code, base=ClientError)
|
||||
|
||||
for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
|
||||
'InsufficientReservedInstanceCapacity', 'InternalError',
|
||||
'Unavailable'):
|
||||
_add_matcher_class(BotoTestCase.ec2_error_code.server,
|
||||
code, base=ServerError)
|
||||
|
||||
|
||||
for code in (('AccessDenied', 403),
|
||||
('AccountProblem', 403),
|
||||
('AmbiguousGrantByEmailAddress', 400),
|
||||
('BadDigest', 400),
|
||||
('BucketAlreadyExists', 409),
|
||||
('BucketAlreadyOwnedByYou', 409),
|
||||
('BucketNotEmpty', 409),
|
||||
('CredentialsNotSupported', 400),
|
||||
('CrossLocationLoggingProhibited', 403),
|
||||
('EntityTooSmall', 400),
|
||||
('EntityTooLarge', 400),
|
||||
('ExpiredToken', 400),
|
||||
('IllegalVersioningConfigurationException', 400),
|
||||
('IncompleteBody', 400),
|
||||
('IncorrectNumberOfFilesInPostRequest', 400),
|
||||
('InlineDataTooLarge', 400),
|
||||
('InvalidAccessKeyId', 403),
|
||||
'InvalidAddressingHeader',
|
||||
('InvalidArgument', 400),
|
||||
('InvalidBucketName', 400),
|
||||
('InvalidBucketState', 409),
|
||||
('InvalidDigest', 400),
|
||||
('InvalidLocationConstraint', 400),
|
||||
('InvalidPart', 400),
|
||||
('InvalidPartOrder', 400),
|
||||
('InvalidPayer', 403),
|
||||
('InvalidPolicyDocument', 400),
|
||||
('InvalidRange', 416),
|
||||
('InvalidRequest', 400),
|
||||
('InvalidSecurity', 403),
|
||||
('InvalidSOAPRequest', 400),
|
||||
('InvalidStorageClass', 400),
|
||||
('InvalidTargetBucketForLogging', 400),
|
||||
('InvalidToken', 400),
|
||||
('InvalidURI', 400),
|
||||
('KeyTooLong', 400),
|
||||
('MalformedACLError', 400),
|
||||
('MalformedPOSTRequest', 400),
|
||||
('MalformedXML', 400),
|
||||
('MaxMessageLengthExceeded', 400),
|
||||
('MaxPostPreDataLengthExceededError', 400),
|
||||
('MetadataTooLarge', 400),
|
||||
('MethodNotAllowed', 405),
|
||||
('MissingAttachment'),
|
||||
('MissingContentLength', 411),
|
||||
('MissingRequestBodyError', 400),
|
||||
('MissingSecurityElement', 400),
|
||||
('MissingSecurityHeader', 400),
|
||||
('NoLoggingStatusForKey', 400),
|
||||
('NoSuchBucket', 404),
|
||||
('NoSuchKey', 404),
|
||||
('NoSuchLifecycleConfiguration', 404),
|
||||
('NoSuchUpload', 404),
|
||||
('NoSuchVersion', 404),
|
||||
('NotSignedUp', 403),
|
||||
('NotSuchBucketPolicy', 404),
|
||||
('OperationAborted', 409),
|
||||
('PermanentRedirect', 301),
|
||||
('PreconditionFailed', 412),
|
||||
('Redirect', 307),
|
||||
('RequestIsNotMultiPartContent', 400),
|
||||
('RequestTimeout', 400),
|
||||
('RequestTimeTooSkewed', 403),
|
||||
('RequestTorrentOfBucketError', 400),
|
||||
('SignatureDoesNotMatch', 403),
|
||||
('TemporaryRedirect', 307),
|
||||
('TokenRefreshRequired', 400),
|
||||
('TooManyBuckets', 400),
|
||||
('UnexpectedContent', 400),
|
||||
('UnresolvableGrantByEmailAddress', 400),
|
||||
('UserKeyMustBeSpecified', 400)):
|
||||
_add_matcher_class(BotoTestCase.s3_error_code.client,
|
||||
code, base=ClientError)
|
||||
|
||||
|
||||
for code in (('InternalError', 500),
|
||||
('NotImplemented', 501),
|
||||
('ServiceUnavailable', 503),
|
||||
('SlowDown', 503)):
|
||||
_add_matcher_class(BotoTestCase.s3_error_code.server,
|
||||
code, base=ServerError)
|
|
@ -0,0 +1,98 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 tempest.config
|
||||
from tempest.common.utils.file_utils import have_effective_read_access
|
||||
import os
|
||||
import tempest.openstack
|
||||
import re
|
||||
import keystoneclient.exceptions
|
||||
import boto.exception
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
A_I_IMAGES_READY = False # ari,ami,aki
|
||||
S3_CAN_CONNECT_ERROR = "Unknown Error"
|
||||
EC2_CAN_CONNECT_ERROR = "Unknown Error"
|
||||
|
||||
|
||||
def setup_package():
|
||||
global A_I_IMAGES_READY
|
||||
global S3_CAN_CONNECT_ERROR
|
||||
global EC2_CAN_CONNECT_ERROR
|
||||
secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system
|
||||
id_matcher = re.compile("[A-Za-z0-9]{20,}")
|
||||
|
||||
def all_read(*args):
|
||||
return all(map(have_effective_read_access, args))
|
||||
|
||||
config = tempest.config.TempestConfig()
|
||||
materials_path = config.boto.s3_materials_path
|
||||
ami_path = materials_path + os.sep + config.boto.ami_manifest
|
||||
aki_path = materials_path + os.sep + config.boto.aki_manifest
|
||||
ari_path = materials_path + os.sep + config.boto.ari_manifest
|
||||
|
||||
A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
|
||||
boto_logger = logging.getLogger('boto')
|
||||
level = boto_logger.level
|
||||
boto_logger.setLevel(logging.CRITICAL) # suppress logging for these
|
||||
|
||||
def _cred_sub_check(connection_data):
|
||||
if not id_matcher.match(connection_data["aws_access_key_id"]):
|
||||
raise Exception("Invalid AWS access Key")
|
||||
if not secret_matcher.match(connection_data["aws_secret_access_key"]):
|
||||
raise Exception("Invalid AWS secret Key")
|
||||
raise Exception("Unknown (Authentication?) Error")
|
||||
openstack = tempest.openstack.Manager()
|
||||
try:
|
||||
if urlparse.urlparse(config.boto.ec2_url).hostname is None:
|
||||
raise Exception("Failed to get hostname from the ec2_url")
|
||||
ec2client = openstack.ec2api_client
|
||||
try:
|
||||
ec2client.get_all_regions()
|
||||
except boto.exception.BotoServerError as exc:
|
||||
if exc.error_code is None:
|
||||
raise Exception("EC2 target does not looks EC2 service")
|
||||
_cred_sub_check(ec2client.connection_data)
|
||||
|
||||
except keystoneclient.exceptions.Unauthorized:
|
||||
EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
|
||||
" faild to get them even by keystoneclient"
|
||||
except Exception as exc:
|
||||
logging.exception(exc)
|
||||
EC2_CAN_CONNECT_ERROR = str(exc)
|
||||
else:
|
||||
EC2_CAN_CONNECT_ERROR = None
|
||||
|
||||
try:
|
||||
if urlparse.urlparse(config.boto.s3_url).hostname is None:
|
||||
raise Exception("Failed to get hostname from the s3_url")
|
||||
s3client = openstack.s3_client
|
||||
try:
|
||||
s3client.get_bucket("^INVALID*#()@INVALID.")
|
||||
except boto.exception.BotoServerError as exc:
|
||||
if exc.status == 403:
|
||||
_cred_sub_check(s3client.connection_data)
|
||||
except Exception as exc:
|
||||
logging.exception(exc)
|
||||
S3_CAN_CONNECT_ERROR = str(exc)
|
||||
except keystoneclient.exceptions.Unauthorized:
|
||||
S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
|
||||
" faild to get them even by keystoneclient"
|
||||
else:
|
||||
S3_CAN_CONNECT_ERROR = None
|
||||
boto_logger.setLevel(level)
|
|
@ -0,0 +1,249 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose
|
||||
from nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest.tests.boto.utils.s3 import s3_upload_dir
|
||||
import tempest.tests.boto
|
||||
from tempest.common.utils.data_utils import rand_name
|
||||
from tempest.exceptions import EC2RegisterImageException
|
||||
from tempest.tests.boto.utils.wait import state_wait, re_search_wait
|
||||
from tempest import openstack
|
||||
from tempest.common.utils.linux.remote_client import RemoteClient
|
||||
from boto.s3.key import Key
|
||||
from contextlib import closing
|
||||
import logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@attr("S3", "EC2")
|
||||
class InstanceRunTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(InstanceRunTest, cls).setUpClass()
|
||||
if not tempest.tests.boto.A_I_IMAGES_READY:
|
||||
raise nose.SkipTest("".join(("EC2 ", cls.__name__,
|
||||
": requires ami/aki/ari manifest")))
|
||||
cls.os = openstack.Manager()
|
||||
cls.s3_client = cls.os.s3_client
|
||||
cls.ec2_client = cls.os.ec2api_client
|
||||
config = cls.os.config
|
||||
cls.zone = cls.ec2_client.get_good_zone()
|
||||
cls.materials_path = config.boto.s3_materials_path
|
||||
ami_manifest = config.boto.ami_manifest
|
||||
aki_manifest = config.boto.aki_manifest
|
||||
ari_manifest = config.boto.ari_manifest
|
||||
cls.instance_type = config.boto.instance_type
|
||||
cls.bucket_name = rand_name("s3bucket-")
|
||||
cls.keypair_name = rand_name("keypair-")
|
||||
cls.keypair = cls.ec2_client.create_key_pair(cls.keypair_name)
|
||||
cls.addResourceCleanUp(cls.ec2_client.delete_key_pair,
|
||||
cls.keypair_name)
|
||||
bucket = cls.s3_client.create_bucket(cls.bucket_name)
|
||||
cls.addResourceCleanUp(cls.destroy_bucket,
|
||||
cls.s3_client.connection_data,
|
||||
cls.bucket_name)
|
||||
s3_upload_dir(bucket, cls.materials_path)
|
||||
cls.images = {"ami":
|
||||
{"name": rand_name("ami-name-"),
|
||||
"location": cls.bucket_name + "/" + ami_manifest},
|
||||
"aki":
|
||||
{"name": rand_name("aki-name-"),
|
||||
"location": cls.bucket_name + "/" + aki_manifest},
|
||||
"ari":
|
||||
{"name": rand_name("ari-name-"),
|
||||
"location": cls.bucket_name + "/" + ari_manifest}}
|
||||
for image in cls.images.itervalues():
|
||||
image["image_id"] = cls.ec2_client.register_image(
|
||||
name=image["name"],
|
||||
image_location=image["location"])
|
||||
cls.addResourceCleanUp(cls.ec2_client.deregister_image,
|
||||
image["image_id"])
|
||||
|
||||
for image in cls.images.itervalues():
|
||||
def _state():
|
||||
retr = cls.ec2_client.get_image(image["image_id"])
|
||||
return retr.state
|
||||
state = state_wait(_state, "available")
|
||||
if state != "available":
|
||||
for _image in cls.images.itervalues():
|
||||
ec2_client.deregister_image(_image["image_id"])
|
||||
raise RegisterImageException(image_id=image["image_id"])
|
||||
|
||||
@attr(type='smoke')
|
||||
def test_run_stop_terminate_instance(self):
|
||||
"""EC2 run, stop and terminate instance"""
|
||||
image_ami = self.ec2_client.get_image(self.images["ami"]
|
||||
["image_id"])
|
||||
reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"],
|
||||
ramdisk_id=self.images["ari"]["image_id"],
|
||||
instance_type=self.instance_type)
|
||||
rcuk = self.addResourceCleanUp(self.destroy_reservation, reservation)
|
||||
|
||||
def _state():
|
||||
instance.update(validate=True)
|
||||
return instance.state
|
||||
|
||||
for instance in reservation.instances:
|
||||
LOG.info("state: %s", instance.state)
|
||||
if instance.state != "running":
|
||||
self.assertInstanceStateWait(_state, "running")
|
||||
|
||||
for instance in reservation.instances:
|
||||
instance.stop()
|
||||
LOG.info("state: %s", instance.state)
|
||||
if instance.state != "stopped":
|
||||
self.assertInstanceStateWait(_state, "stopped")
|
||||
|
||||
for instance in reservation.instances:
|
||||
instance.terminate()
|
||||
self.cancelResourceCleanUp(rcuk)
|
||||
|
||||
@attr(type='smoke')
|
||||
def test_run_terminate_instance(self):
|
||||
"""EC2 run, terminate immediately"""
|
||||
image_ami = self.ec2_client.get_image(self.images["ami"]
|
||||
["image_id"])
|
||||
reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"],
|
||||
ramdisk_id=self.images["ari"]["image_id"],
|
||||
instance_type=self.instance_type)
|
||||
|
||||
for instance in reservation.instances:
|
||||
instance.terminate()
|
||||
|
||||
instance.update(validate=True)
|
||||
self.assertNotEqual(instance.state, "running")
|
||||
|
||||
#NOTE(afazekas): doctored test case,
|
||||
# with normal validation it would fail
|
||||
@attr("slow", type='smoke')
|
||||
def test_integration_1(self):
|
||||
"""EC2 1. integration test (not strict)"""
|
||||
image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"])
|
||||
sec_group_name = rand_name("securitygroup-")
|
||||
group_desc = sec_group_name + " security group description "
|
||||
security_group = self.ec2_client.create_security_group(sec_group_name,
|
||||
group_desc)
|
||||
self.addResourceCleanUp(self.destroy_security_group_wait,
|
||||
security_group)
|
||||
self.ec2_client.authorize_security_group(sec_group_name,
|
||||
ip_protocol="icmp",
|
||||
cidr_ip="0.0.0.0/0",
|
||||
from_port=-1,
|
||||
to_port=-1)
|
||||
self.ec2_client.authorize_security_group(sec_group_name,
|
||||
ip_protocol="tcp",
|
||||
cidr_ip="0.0.0.0/0",
|
||||
from_port=22,
|
||||
to_port=22)
|
||||
reservation = image_ami.run(kernel_id=self.images["aki"]["image_id"],
|
||||
ramdisk_id=self.images["ari"]["image_id"],
|
||||
instance_type=self.instance_type,
|
||||
key_name=self.keypair_name,
|
||||
security_groups=(sec_group_name,))
|
||||
self.addResourceCleanUp(self.destroy_reservation,
|
||||
reservation)
|
||||
volume = self.ec2_client.create_volume(1, self.zone)
|
||||
self.addResourceCleanUp(self.destroy_volume_wait, volume)
|
||||
instance = reservation.instances[0]
|
||||
|
||||
def _instance_state():
|
||||
instance.update(validate=True)
|
||||
return instance.state
|
||||
|
||||
def _volume_state():
|
||||
volume.update(validate=True)
|
||||
return volume.status
|
||||
|
||||
LOG.info("state: %s", instance.state)
|
||||
if instance.state != "running":
|
||||
self.assertInstanceStateWait(_instance_state, "running")
|
||||
|
||||
address = self.ec2_client.allocate_address()
|
||||
rcuk_a = self.addResourceCleanUp(address.delete)
|
||||
address.associate(instance.id)
|
||||
|
||||
rcuk_da = self.addResourceCleanUp(address.disassociate)
|
||||
#TODO(afazekas): ping test. dependecy/permission ?
|
||||
|
||||
self.assertVolumeStatusWait(_volume_state, "available")
|
||||
#NOTE(afazekas): it may be reports availble before it is available
|
||||
|
||||
ssh = RemoteClient(address.public_ip,
|
||||
self.os.config.compute.ssh_user,
|
||||
pkey=self.keypair.material)
|
||||
text = rand_name("Pattern text for console output -")
|
||||
resp = ssh.write_to_console(text)
|
||||
self.assertFalse(resp)
|
||||
|
||||
def _output():
|
||||
output = instance.get_console_output()
|
||||
return output.output
|
||||
|
||||
re_search_wait(_output, text)
|
||||
part_lines = ssh.get_partitions().split('\n')
|
||||
# "attaching" invalid EC2 state ! #1074901
|
||||
volume.attach(instance.id, "/dev/vdh")
|
||||
|
||||
#self.assertVolumeStatusWait(_volume_state, "in-use") # #1074901
|
||||
re_search_wait(_volume_state, "in-use")
|
||||
|
||||
#NOTE(afazekas): Different Hypervisor backends names
|
||||
# differently the devices,
|
||||
# now we just test is the partition number increased/decrised
|
||||
|
||||
def _part_state():
|
||||
current = ssh.get_partitions().split('\n')
|
||||
if current > part_lines:
|
||||
return 'INCREASE'
|
||||
if current < part_lines:
|
||||
return 'DECREASE'
|
||||
return 'EQUAL'
|
||||
|
||||
state_wait(_part_state, 'INCREASE')
|
||||
part_lines = ssh.get_partitions().split('\n')
|
||||
|
||||
#TODO(afazekas): Resource compare to the flavor settings
|
||||
|
||||
volume.detach() # "detaching" invalid EC2 status #1074901
|
||||
|
||||
#self.assertVolumeStatusWait(_volume_state, "available")
|
||||
re_search_wait(_volume_state, "available")
|
||||
LOG.info("Volume %s state: %s", volume.id, volume.status)
|
||||
|
||||
state_wait(_part_state, 'DECREASE')
|
||||
|
||||
instance.stop()
|
||||
address.disassociate()
|
||||
self.assertAddressDissasociatedWait(address)
|
||||
self.cancelResourceCleanUp(rcuk_da)
|
||||
address.release()
|
||||
self.assertAddressReleasedWait(address)
|
||||
self.cancelResourceCleanUp(rcuk_a)
|
||||
|
||||
LOG.info("state: %s", instance.state)
|
||||
if instance.state != "stopped":
|
||||
self.assertInstanceStateWait(_instance_state, "stopped")
|
||||
#TODO(afazekas): move steps from teardown to the test case
|
||||
|
||||
|
||||
#TODO(afazekas): Snapshot/volume read/write test case
|
|
@ -0,0 +1,79 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest.common.utils.data_utils import rand_name
|
||||
from tempest import openstack
|
||||
|
||||
|
||||
def compare_key_pairs(a, b):
|
||||
return (a.name == b.name and
|
||||
a.fingerprint == b.fingerprint)
|
||||
|
||||
|
||||
@attr("EC2")
|
||||
class EC2KeysTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EC2KeysTest, cls).setUpClass()
|
||||
cls.os = openstack.Manager()
|
||||
cls.client = cls.os.ec2api_client
|
||||
|
||||
@attr(type='smoke')
|
||||
def test_create_ec2_keypair(self):
|
||||
"""EC2 create KeyPair"""
|
||||
key_name = rand_name("keypair-")
|
||||
self.addResourceCleanUp(self.client.delete_key_pair, key_name)
|
||||
keypair = self.client.create_key_pair(key_name)
|
||||
self.assertTrue(compare_key_pairs(keypair,
|
||||
self.client.get_key_pair(key_name)))
|
||||
|
||||
@attr(type='smoke')
|
||||
@unittest.skip("Skipped until the Bug #1072318 is resolved")
|
||||
def test_delete_ec2_keypair(self):
|
||||
"""EC2 delete KeyPair"""
|
||||
key_name = rand_name("keypair-")
|
||||
self.client.create_key_pair(key_name)
|
||||
self.client.delete_key_pair(key_name)
|
||||
self.assertEqual(None, self.client.get_key_pair(key_name))
|
||||
|
||||
@attr(type='smoke')
|
||||
def test_get_ec2_keypair(self):
|
||||
"""EC2 get KeyPair"""
|
||||
key_name = rand_name("keypair-")
|
||||
self.addResourceCleanUp(self.client.delete_key_pair, key_name)
|
||||
keypair = self.client.create_key_pair(key_name)
|
||||
self.assertTrue(compare_key_pairs(keypair,
|
||||
self.client.get_key_pair(key_name)))
|
||||
|
||||
@attr(type='smoke')
|
||||
@unittest.skip("Skipped until the Bug #1072762 is resolved")
|
||||
def test_duplicate_ec2_keypair(self):
|
||||
"""EC2 duplicate KeyPair"""
|
||||
key_name = rand_name("keypair-")
|
||||
self.addResourceCleanUp(self.client.delete_key_pair, key_name)
|
||||
keypair = self.client.create_key_pair(key_name)
|
||||
self.assertEC2ResponseError(self.error_code.client.
|
||||
InvalidKeyPair.Duplicate,
|
||||
self.client.create_key_pair,
|
||||
key_name)
|
||||
self.assertTrue(compare_key_pairs(keypair,
|
||||
self.client.get_key_pair(key_name)))
|
|
@ -0,0 +1,49 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest import openstack
|
||||
|
||||
|
||||
@attr("EC2")
|
||||
class EC2NetworkTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EC2NetworkTest, cls).setUpClass()
|
||||
cls.os = openstack.Manager()
|
||||
cls.client = cls.os.ec2api_client
|
||||
|
||||
#Note(afazekas): these tests for things duable without an instance
|
||||
@unittest.skip("Skipped until the Bug #1080406 is resolved")
|
||||
@attr(type='smoke')
|
||||
def test_disassociate_not_associated_floating_ip(self):
|
||||
"""EC2 disassociate not associated floating ip"""
|
||||
ec2_codes = self.ec2_error_code
|
||||
address = self.client.allocate_address()
|
||||
public_ip = address.public_ip
|
||||
rcuk = self.addResourceCleanUp(self.client.release_address, public_ip)
|
||||
addresses_get = self.client.get_all_addresses(addresses=(public_ip,))
|
||||
self.assertEqual(len(addresses_get), 1)
|
||||
self.assertEqual(addresses_get[0].public_ip, public_ip)
|
||||
self.assertBotoError(ec2_codes.client.InvalidAssociationID.NotFound,
|
||||
address.disassociate)
|
||||
self.client.release_address(public_ip)
|
||||
self.cancelResourceCleanUp(rcuk)
|
||||
assertAddressReleasedWait(address)
|
|
@ -0,0 +1,78 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest.common.utils.data_utils import rand_name
|
||||
from tempest import openstack
|
||||
|
||||
|
||||
@attr("EC2")
|
||||
class EC2SecurityGroupTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EC2SecurityGroupTest, cls).setUpClass()
|
||||
cls.os = openstack.Manager()
|
||||
cls.client = cls.os.ec2api_client
|
||||
|
||||
@attr(type='smoke')
|
||||
def test_create_authorize_security_group(self):
|
||||
"""EC2 Create, authorize/revoke security group"""
|
||||
group_name = rand_name("securty_group-")
|
||||
group_description = group_name + " security group description "
|
||||
group = self.client.create_security_group(group_name,
|
||||
group_description)
|
||||
self.addResourceCleanUp(self.client.delete_security_group, group_name)
|
||||
groups_get = self.client.get_all_security_groups(groupnames=
|
||||
(group_name,))
|
||||
self.assertEqual(len(groups_get), 1)
|
||||
group_get = groups_get[0]
|
||||
self.assertEqual(group.name, group_get.name)
|
||||
self.assertEqual(group.name, group_get.name)
|
||||
#ping (icmp_echo) and other icmp allowed from everywhere
|
||||
# from_port and to_port act as icmp type
|
||||
success = self.client.authorize_security_group(group_name,
|
||||
ip_protocol="icmp",
|
||||
cidr_ip="0.0.0.0/0",
|
||||
from_port=-1,
|
||||
to_port=-1)
|
||||
self.assertTrue(success)
|
||||
#allow standard ssh port from anywhere
|
||||
success = self.client.authorize_security_group(group_name,
|
||||
ip_protocol="tcp",
|
||||
cidr_ip="0.0.0.0/0",
|
||||
from_port=22,
|
||||
to_port=22)
|
||||
self.assertTrue(success)
|
||||
#TODO(afazekas): Duplicate tests
|
||||
group_get = self.client.get_all_security_groups(groupnames=
|
||||
(group_name,))[0]
|
||||
#remove listed rules
|
||||
for ip_permission in group_get.rules:
|
||||
for cidr in ip_permission.grants:
|
||||
self.assertTrue(self.client.revoke_security_group(group_name,
|
||||
ip_protocol=ip_permission.ip_protocol,
|
||||
cidr_ip=cidr,
|
||||
from_port=ip_permission.from_port,
|
||||
to_port=ip_permission.to_port))
|
||||
|
||||
group_get = self.client.get_all_security_groups(groupnames=
|
||||
(group_name,))[0]
|
||||
#all rules shuld be removed now
|
||||
self.assertEqual(0, len(group_get.rules))
|
|
@ -0,0 +1,93 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest import openstack
|
||||
import unittest2 as unittest
|
||||
import logging
|
||||
import time
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def compare_volumes(a, b):
|
||||
return (a.id == b.id and
|
||||
a.size == b.size)
|
||||
|
||||
|
||||
@attr("EC2")
|
||||
class EC2VolumesTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(EC2VolumesTest, cls).setUpClass()
|
||||
cls.os = openstack.Manager()
|
||||
cls.client = cls.os.ec2api_client
|
||||
cls.zone = cls.client.get_good_zone()
|
||||
|
||||
#NOTE(afazekas): as admin it can trigger the Bug #1074901
|
||||
@attr(type='smoke')
|
||||
def test_create_get_delete(self):
|
||||
"""EC2 Create, get, delete Volume"""
|
||||
volume = self.client.create_volume(1, self.zone)
|
||||
cuk = self.addResourceCleanUp(self.client.delete_volume, volume.id)
|
||||
self.assertIn(volume.status, self.valid_volume_status)
|
||||
retrieved = self.client.get_all_volumes((volume.id,))
|
||||
self.assertEqual(1, len(retrieved))
|
||||
self.assertTrue(compare_volumes(volume, retrieved[0]))
|
||||
|
||||
def _status():
|
||||
volume.update(validate=True)
|
||||
return volume.status
|
||||
|
||||
self.assertVolumeStatusWait(_status, "available")
|
||||
self.client.delete_volume(volume.id)
|
||||
self.cancelResourceCleanUp(cuk)
|
||||
|
||||
@unittest.skip("Skipped until the Bug #1080284 is resolved")
|
||||
def test_create_volme_from_snapshot(self):
|
||||
"""EC2 Create volume from snapshot"""
|
||||
volume = self.client.create_volume(1, self.zone)
|
||||
self.addResourceCleanUp(self.client.delete_volume, volume.id)
|
||||
|
||||
def _status():
|
||||
volume.update(validate=True)
|
||||
return volume.status
|
||||
|
||||
self.assertVolumeStatusWait(_status, "available")
|
||||
snap = self.client.create_snapshot(volume.id)
|
||||
self.addResourceCleanUp(self.destroy_snapshot_wait, snap)
|
||||
|
||||
def _snap_status():
|
||||
snap.update(validate=True)
|
||||
return snap.status
|
||||
|
||||
#self.assertVolumeStatusWait(_snap_status, "available") # not a volume
|
||||
self.assertSnapshotStatusWait(_snap_status, "completed")
|
||||
|
||||
svol = self.client.create_volume(1, self.zone, snapshot=snap)
|
||||
cuk = self.addResourceCleanUp(svol.delete)
|
||||
|
||||
def _snap_vol_status():
|
||||
svol.update(validate=True)
|
||||
return svol.status
|
||||
|
||||
self.assertVolumeStatusWait(_snap_vol_status, "available")
|
||||
svol.delete()
|
||||
self.cancelResourceCleanUp(cuk)
|
|
@ -0,0 +1,49 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest.common.utils.data_utils import rand_name
|
||||
from tempest import openstack
|
||||
|
||||
|
||||
@attr("S3")
|
||||
class S3BucketsTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(S3BucketsTest, cls).setUpClass()
|
||||
cls.os = openstack.Manager()
|
||||
cls.client = cls.os.s3_client
|
||||
cls.config = cls.os.config
|
||||
|
||||
@unittest.skip("Skipped until the Bug #1076965 is resolved")
|
||||
@attr(type='smoke')
|
||||
def test_create_and_get_delete_bucket(self):
|
||||
"""S3 Create, get and delete bucket"""
|
||||
bucket_name = rand_name("s3bucket-")
|
||||
cleanup_key = self.addResourceCleanUp(self.client.delete_bucket,
|
||||
bucket_name)
|
||||
bucket = self.client.create_bucket(bucket_name)
|
||||
self.assertTrue(bucket.name == bucket_name)
|
||||
bucket = self.client.get_bucket(bucket_name)
|
||||
self.assertTrue(bucket.name == bucket_name)
|
||||
self.client.delete_bucket(bucket_name)
|
||||
self.assertBotoError(self.s3_error_code.client.NoSuchBucket,
|
||||
self.client.get_bucket, bucket_name)
|
||||
self.cancelResourceCleanUp(cleanup_key)
|
|
@ -0,0 +1,144 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest import openstack
|
||||
from tempest.testboto import BotoTestCase
|
||||
import tempest.tests.boto
|
||||
from tempest.tests.boto.utils.wait import state_wait
|
||||
from tempest.tests.boto.utils.s3 import s3_upload_dir
|
||||
from tempest.common.utils.data_utils import rand_name
|
||||
from contextlib import closing
|
||||
from boto.s3.key import Key
|
||||
import logging
|
||||
import nose
|
||||
import os
|
||||
|
||||
|
||||
@attr("S3", "EC2")
|
||||
class S3ImagesTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(S3ImagesTest, cls).setUpClass()
|
||||
if not tempest.tests.boto.A_I_IMAGES_READY:
|
||||
raise nose.SkipTest("".join(("EC2 ", cls.__name__,
|
||||
": requires ami/aki/ari manifest")))
|
||||
cls.os = openstack.Manager()
|
||||
cls.s3_client = cls.os.s3_client
|
||||
cls.images_client = cls.os.ec2api_client
|
||||
config = cls.os.config
|
||||
cls.materials_path = config.boto.s3_materials_path
|
||||
cls.ami_manifest = config.boto.ami_manifest
|
||||
cls.aki_manifest = config.boto.aki_manifest
|
||||
cls.ari_manifest = config.boto.ari_manifest
|
||||
cls.ami_path = cls.materials_path + os.sep + cls.ami_manifest
|
||||
cls.aki_path = cls.materials_path + os.sep + cls.aki_manifest
|
||||
cls.ari_path = cls.materials_path + os.sep + cls.ari_manifest
|
||||
cls.bucket_name = rand_name("bucket-")
|
||||
bucket = cls.s3_client.create_bucket(cls.bucket_name)
|
||||
cls.addResourceCleanUp(cls.destroy_bucket,
|
||||
cls.s3_client.connection_data,
|
||||
cls.bucket_name)
|
||||
s3_upload_dir(bucket, cls.materials_path)
|
||||
|
||||
#Note(afazekas): Without the normal status change test!
|
||||
# otherwise I would skip it too
|
||||
@attr(type='smoke')
|
||||
def test_register_get_deregister_ami_image(self):
|
||||
"""Register and deregister ami image"""
|
||||
image = {"name": rand_name("ami-name-"),
|
||||
"location": self.bucket_name + "/" + self.ami_manifest,
|
||||
"type": "ami"}
|
||||
image["image_id"] = self.images_client.register_image(
|
||||
name=image["name"],
|
||||
image_location=image["location"])
|
||||
#Note(afazekas): delete_snapshot=True might trigger boto lib? bug
|
||||
image["cleanUp"] = self.addResourceCleanUp(
|
||||
self.images_client.deregister_image,
|
||||
image["image_id"])
|
||||
self.assertEqual(image["image_id"][0:3], image["type"])
|
||||
retrieved_image = self.images_client.get_image(image["image_id"])
|
||||
self.assertTrue(retrieved_image.name == image["name"])
|
||||
self.assertTrue(retrieved_image.id == image["image_id"])
|
||||
state = retrieved_image.state
|
||||
if state != "available":
|
||||
def _state():
|
||||
retr = self.images_client.get_image(image["image_id"])
|
||||
return retr.state
|
||||
state = state_wait(_state, "available")
|
||||
self.assertEqual("available", state)
|
||||
self.images_client.deregister_image(image["image_id"])
|
||||
#TODO(afazekas): double deregister ?
|
||||
self.cancelResourceCleanUp(image["cleanUp"])
|
||||
|
||||
@unittest.skip("Skipped until the Bug #1074904 is resolved")
|
||||
def test_register_get_deregister_aki_image(self):
|
||||
"""Register and deregister aki image"""
|
||||
image = {"name": rand_name("aki-name-"),
|
||||
"location": self.bucket_name + "/" + self.ari_manifest,
|
||||
"type": "aki"}
|
||||
image["image_id"] = self.images_client.register_image(
|
||||
name=image["name"],
|
||||
image_location=image["location"])
|
||||
image["cleanUp"] = self.addResourceCleanUp(
|
||||
self.images_client.deregister_image,
|
||||
image["image_id"])
|
||||
self.assertEqual(image["image_id"][0:3], image["type"])
|
||||
retrieved_image = self.images_client.get_image(image["image_id"])
|
||||
self.assertTrue(retrieved_image.name == image["name"])
|
||||
self.assertTrue(retrieved_image.id == image["image_id"])
|
||||
self.assertIn(retrieved_image.state, self.valid_image_state)
|
||||
if retrieved_image.state != "available":
|
||||
def _state():
|
||||
retr = self.images_client.get_image(image["image_id"])
|
||||
return retr.state
|
||||
self.assertImageStateWait(_state, "available")
|
||||
self.images_client.deregister_image(image["image_id"])
|
||||
#TODO(afazekas): verify deregister in a better way
|
||||
retrieved_image = self.images_client.get_image(image["image_id"])
|
||||
self.assertIn(retrieved_image.state, self.valid_image_state)
|
||||
self.cancelResourceCleanUp(image["cleanUp"])
|
||||
|
||||
@unittest.skip("Skipped until the Bug #1074908 and #1074904 is resolved")
|
||||
def test_register_get_deregister_ari_image(self):
|
||||
"""Register and deregister ari image"""
|
||||
image = {"name": rand_name("ari-name-"),
|
||||
"location": "/" + self.bucket_name + "/" + self.ari_manifest,
|
||||
"type": "ari"}
|
||||
image["image_id"] = self.images_client.register_image(
|
||||
name=image["name"],
|
||||
image_location=image["location"])
|
||||
image["cleanUp"] = self.addResourceCleanUp(
|
||||
self.images_client.deregister_image,
|
||||
image["image_id"])
|
||||
self.assertEqual(image["image_id"][0:3], image["type"])
|
||||
retrieved_image = self.images_client.get_image(image["image_id"])
|
||||
self.assertIn(retrieved_image.state, self.valid_image_state)
|
||||
if retrieved_image.state != "available":
|
||||
def _state():
|
||||
retr = self.images_client.get_image(image["image_id"])
|
||||
return retr.state
|
||||
self.assertImageStateWait(_state, "available")
|
||||
self.assertIn(retrieved_image.state, self.valid_image_state)
|
||||
self.assertTrue(retrieved_image.name == image["name"])
|
||||
self.assertTrue(retrieved_image.id == image["image_id"])
|
||||
self.images_client.deregister_image(image["image_id"])
|
||||
self.cancelResourceCleanUp(image["cleanUp"])
|
||||
|
||||
#TODO(afazekas): less copy-paste style
|
|
@ -0,0 +1,58 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 nose.plugins.attrib import attr
|
||||
import unittest2 as unittest
|
||||
from tempest.testboto import BotoTestCase
|
||||
from tempest.common.utils.data_utils import rand_name
|
||||
from tempest import openstack
|
||||
from tempest.tests import boto
|
||||
from boto.s3.key import Key
|
||||
from contextlib import closing
|
||||
|
||||
|
||||
@attr("S3")
|
||||
class S3BucketsTest(BotoTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(S3BucketsTest, cls).setUpClass()
|
||||
cls.os = openstack.Manager()
|
||||
cls.client = cls.os.s3_client
|
||||
cls.config = cls.os.config
|
||||
|
||||
@unittest.skip("Skipped until the Bug #1076534 is resolved")
|
||||
@attr(type='smoke')
|
||||
def test_create_get_delete_object(self):
|
||||
"""S3 Create, get and delete object"""
|
||||
bucket_name = rand_name("s3bucket-")
|
||||
object_name = rand_name("s3object-")
|
||||
content = 'x' * 42
|
||||
bucket = self.client.create_bucket(bucket_name)
|
||||
self.addResourceCleanUp(self.destroy_bucket,
|
||||
self.client.connection_data,
|
||||
bucket_name)
|
||||
|
||||
self.assertTrue(bucket.name == bucket_name)
|
||||
with closing(Key(bucket)) as key:
|
||||
key.key = object_name
|
||||
key.set_contents_from_string(content)
|
||||
readback = key.get_contents_as_string()
|
||||
self.assertTrue(readback == content)
|
||||
bucket.delete_key(key)
|
||||
self.assertBotoError(self.s3_error_code.client.NoSuchKey,
|
||||
key.get_contents_as_string)
|
|
@ -0,0 +1,42 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 boto
|
||||
from boto.s3.key import Key
|
||||
from contextlib import closing
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def s3_upload_dir(bucket, path, prefix="", connection_data=None):
|
||||
if isinstance(bucket, basestring):
|
||||
with closing(boto.connect_s3(**connection_data)) as conn:
|
||||
bucket = conn.lookup(bucket)
|
||||
for root, dirs, files in os.walk(path):
|
||||
for fil in files:
|
||||
with closing(Key(bucket)) as key:
|
||||
source = root + os.sep + fil
|
||||
target = re.sub("^" + re.escape(path) + "?/", prefix, source)
|
||||
if os.sep != '/':
|
||||
target = re.sub(re.escape(os.sep), '/', target)
|
||||
key.key = target
|
||||
LOG.info("Uploading %s to %s/%s", source, bucket.name, target)
|
||||
key.set_contents_from_filename(source)
|
|
@ -0,0 +1,130 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 tempest.config
|
||||
import time
|
||||
from unittest2 import TestCase
|
||||
import logging
|
||||
import re
|
||||
from boto.exception import BotoServerError
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
_boto_config = tempest.config.TempestConfig().boto
|
||||
|
||||
default_timeout = _boto_config.build_timeout
|
||||
|
||||
default_check_interval = _boto_config.build_interval
|
||||
|
||||
|
||||
def state_wait(lfunction, final_set=set(), valid_set=None):
|
||||
#TODO(afazekas): evaluate using ABC here
|
||||
if not isinstance(final_set, set):
|
||||
final_set = set((final_set,))
|
||||
if not isinstance(valid_set, set) and valid_set is not None:
|
||||
valid_set = set((valid_set,))
|
||||
start_time = time.time()
|
||||
old_status = status = lfunction()
|
||||
while True:
|
||||
if status != old_status:
|
||||
LOG.info('State transition "%s" ==> "%s" %d second', old_status,
|
||||
status, time.time() - start_time)
|
||||
if status in final_set:
|
||||
return status
|
||||
if valid_set is not None and status not in valid_set:
|
||||
return status
|
||||
dtime = time.time() - start_time
|
||||
if dtime > default_timeout:
|
||||
raise TestCase.failureException("State change timeout exceeded!"
|
||||
'(%ds) While waiting'
|
||||
'for %s at "%s"' %
|
||||
(dtime,
|
||||
final_set, status))
|
||||
time.sleep(default_check_interval)
|
||||
old_status = status
|
||||
status = lfunction()
|
||||
|
||||
|
||||
def re_search_wait(lfunction, regexp):
|
||||
"""Stops waiting on success"""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
text = lfunction()
|
||||
result = re.search(regexp, text)
|
||||
if result is not None:
|
||||
LOG.info('Pattern "%s" found in %d second in "%s"',
|
||||
regexp,
|
||||
time.time() - start_time,
|
||||
text)
|
||||
return result
|
||||
dtime = time.time() - start_time
|
||||
if dtime > default_timeout:
|
||||
raise TestCase.failureException('Pattern find timeout exceeded!'
|
||||
'(%ds) While waiting for'
|
||||
'"%s" pattern in "%s"' %
|
||||
(dtime,
|
||||
regexp, text))
|
||||
time.sleep(default_check_interval)
|
||||
|
||||
|
||||
def wait_no_exception(lfunction, exc_class=None, exc_matcher=None):
|
||||
"""Stops waiting on success"""
|
||||
start_time = time.time()
|
||||
if exc_matcher is not None:
|
||||
exc_class = BotoServerError
|
||||
|
||||
if exc_class is None:
|
||||
exc_class = BaseException
|
||||
while True:
|
||||
result = None
|
||||
try:
|
||||
result = lfunction()
|
||||
LOG.info('No Exception in %d second',
|
||||
time.time() - start_time)
|
||||
return result
|
||||
except exc_class as exc:
|
||||
if exc_matcher is not None:
|
||||
res = exc_matcher.match(exc)
|
||||
if res is not None:
|
||||
LOG.info(res)
|
||||
raise exc
|
||||
# Let the other exceptions propagate
|
||||
dtime = time.time() - start_time
|
||||
if dtime > default_timeout:
|
||||
raise TestCase.failureException("Wait timeout exceeded! (%ds)" %
|
||||
dtime)
|
||||
time.sleep(default_check_interval)
|
||||
|
||||
|
||||
#NOTE(afazekas): EC2/boto normally raise exception instead of empty list
|
||||
def wait_exception(lfunction):
|
||||
"""Returns with the exception or raises one"""
|
||||
start_time = time.time()
|
||||
while True:
|
||||
try:
|
||||
lfunction()
|
||||
except BaseException as exc:
|
||||
LOG.info('Exception in %d second',
|
||||
time.time() - start_time)
|
||||
return exc
|
||||
dtime = time.time() - start_time
|
||||
if dtime > default_timeout:
|
||||
raise TestCase.failureException("Wait timeout exceeded! (%ds)" %
|
||||
dtime)
|
||||
time.sleep(default_check_interval)
|
||||
|
||||
#TODO(afazekas): consider strategy design pattern..
|
|
@ -4,3 +4,4 @@ httplib2>=0.7.0
|
|||
pika
|
||||
unittest2
|
||||
lxml
|
||||
boto>=2.2.1
|
||||
|
|
Loading…
Reference in New Issue