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: I0dff9b05f215b56456272f22aa1c014cd53b4f4bchanges/89/14689/18
parent
c8521f2c18
commit
a23f500725
@ -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
|
@ -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)
|
||||
< |