blob: cfd3747678f0a1333c2f5978dc6f680e046c49d8 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Attila Fazekasa23f5002012-10-23 19:32:45 +02002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Monty Taylorb2ca5ca2013-04-28 18:00:21 -070016import contextlib
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090017import logging as orig_logging
Attila Fazekas234d3e82013-02-22 16:39:49 +010018import os
Matthew Treinisha83a16e2012-12-07 13:44:02 -050019import re
Attila Fazekasa23f5002012-10-23 19:32:45 +020020
Matthew Treinisha83a16e2012-12-07 13:44:02 -050021import boto
Attila Fazekas40aa3612013-01-19 22:16:38 +010022from boto import ec2
23from boto import exception
24from boto import s3
Doug Hellmann583ce2c2015-03-11 14:55:46 +000025from oslo_log import log as logging
Matthew Treinish96e9e882014-06-09 18:37:19 -040026import six
Matthew Treinishf077dd22015-04-23 09:37:41 -040027from six.moves.urllib import parse as urlparse
Andrea Frittoli32d74992015-03-06 17:01:07 +000028from tempest_lib import exceptions as lib_exc
29
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010030from tempest.common import credentials_factory as credentials
Masayuki Igawa224a8272014-02-17 15:07:43 +090031from tempest.common.utils import file_utils
Sean Dague86bd8422013-12-20 09:56:44 -050032from tempest import config
Attila Fazekas40aa3612013-01-19 22:16:38 +010033from tempest import exceptions
Attila Fazekasdc216422013-01-29 15:12:14 +010034import tempest.test
Masayuki Igawa224a8272014-02-17 15:07:43 +090035from tempest.thirdparty.boto.utils import wait
Matthew Treinisha83a16e2012-12-07 13:44:02 -050036
Sean Dague86bd8422013-12-20 09:56:44 -050037CONF = config.CONF
Attila Fazekasa23f5002012-10-23 19:32:45 +020038LOG = logging.getLogger(__name__)
39
40
Attila Fazekas234d3e82013-02-22 16:39:49 +010041def decision_maker():
42 A_I_IMAGES_READY = True # ari,ami,aki
43 S3_CAN_CONNECT_ERROR = None
44 EC2_CAN_CONNECT_ERROR = None
45 secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system
46 id_matcher = re.compile("[A-Za-z0-9]{20,}")
47
48 def all_read(*args):
Masayuki Igawa224a8272014-02-17 15:07:43 +090049 return all(map(file_utils.have_effective_read_access, args))
Attila Fazekas234d3e82013-02-22 16:39:49 +010050
Sean Dague86bd8422013-12-20 09:56:44 -050051 materials_path = CONF.boto.s3_materials_path
52 ami_path = materials_path + os.sep + CONF.boto.ami_manifest
53 aki_path = materials_path + os.sep + CONF.boto.aki_manifest
54 ari_path = materials_path + os.sep + CONF.boto.ari_manifest
Attila Fazekas234d3e82013-02-22 16:39:49 +010055
56 A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
57 boto_logger = logging.getLogger('boto')
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040058 level = boto_logger.logger.level
Matthew Treinishc795b9e2014-06-09 17:01:10 -040059 # suppress logging for boto
60 boto_logger.logger.setLevel(orig_logging.CRITICAL)
Attila Fazekas234d3e82013-02-22 16:39:49 +010061
62 def _cred_sub_check(connection_data):
63 if not id_matcher.match(connection_data["aws_access_key_id"]):
64 raise Exception("Invalid AWS access Key")
65 if not secret_matcher.match(connection_data["aws_secret_access_key"]):
66 raise Exception("Invalid AWS secret Key")
67 raise Exception("Unknown (Authentication?) Error")
Andrea Frittoli32d74992015-03-06 17:01:07 +000068 # NOTE(andreaf) Setting up an extra manager here is redundant,
69 # and should be removed.
Andrea Frittoli (andreaf)290b3e12015-10-08 10:25:02 +010070 openstack = credentials.ConfiguredUserManager()
Attila Fazekas234d3e82013-02-22 16:39:49 +010071 try:
Sean Dague86bd8422013-12-20 09:56:44 -050072 if urlparse.urlparse(CONF.boto.ec2_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010073 raise Exception("Failed to get hostname from the ec2_url")
74 ec2client = openstack.ec2api_client
75 try:
76 ec2client.get_all_regions()
77 except exception.BotoServerError as exc:
78 if exc.error_code is None:
79 raise Exception("EC2 target does not looks EC2 service")
80 _cred_sub_check(ec2client.connection_data)
81
Andrea Frittoli32d74992015-03-06 17:01:07 +000082 except lib_exc.Unauthorized:
Attila Fazekas234d3e82013-02-22 16:39:49 +010083 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
Attila Fazekas62aebc42015-03-12 07:38:19 +010084 " also failed to get it from keystone"
Attila Fazekas234d3e82013-02-22 16:39:49 +010085 except Exception as exc:
86 EC2_CAN_CONNECT_ERROR = str(exc)
87
88 try:
Sean Dague86bd8422013-12-20 09:56:44 -050089 if urlparse.urlparse(CONF.boto.s3_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010090 raise Exception("Failed to get hostname from the s3_url")
91 s3client = openstack.s3_client
92 try:
93 s3client.get_bucket("^INVALID*#()@INVALID.")
94 except exception.BotoServerError as exc:
95 if exc.status == 403:
96 _cred_sub_check(s3client.connection_data)
97 except Exception as exc:
98 S3_CAN_CONNECT_ERROR = str(exc)
Attila Fazekas62aebc42015-03-12 07:38:19 +010099 except lib_exc.Unauthorized:
Attila Fazekas234d3e82013-02-22 16:39:49 +0100100 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
Matt Riedemann78b0f802015-02-09 14:06:07 -0800101 " failed to get them even by keystoneclient"
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -0400102 boto_logger.logger.setLevel(level)
Attila Fazekas234d3e82013-02-22 16:39:49 +0100103 return {'A_I_IMAGES_READY': A_I_IMAGES_READY,
104 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR,
105 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR}
106
107
Attila Fazekasa23f5002012-10-23 19:32:45 +0200108class BotoExceptionMatcher(object):
109 STATUS_RE = r'[45]\d\d'
110 CODE_RE = '.*' # regexp makes sense in group match
111
112 def match(self, exc):
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000113 """Check boto exception
114
115 :returns: Returns with an error string if it does not match,
116 returns with None when it matches.
Attila Fazekas55c597d2014-02-21 13:10:41 +0100117 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100118 if not isinstance(exc, exception.BotoServerError):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200119 return "%r not an BotoServerError instance" % exc
120 LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
121 if re.match(self.STATUS_RE, str(exc.status)) is None:
122 return ("Status code (%s) does not match"
123 "the expected re pattern \"%s\""
124 % (exc.status, self.STATUS_RE))
125 if re.match(self.CODE_RE, str(exc.error_code)) is None:
126 return ("Error code (%s) does not match" +
127 "the expected re pattern \"%s\"") %\
128 (exc.error_code, self.CODE_RE)
Attila Fazekas55c597d2014-02-21 13:10:41 +0100129 return None
Attila Fazekasa23f5002012-10-23 19:32:45 +0200130
131
132class ClientError(BotoExceptionMatcher):
133 STATUS_RE = r'4\d\d'
134
135
136class ServerError(BotoExceptionMatcher):
137 STATUS_RE = r'5\d\d'
138
139
140def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000141 """Usable for adding an ExceptionMatcher(s) into the exception tree.
142
143 The not leaf elements does wildcard match
Attila Fazekasa23f5002012-10-23 19:32:45 +0200144 """
145 # in error_code just literal and '.' characters expected
llg821243b20502014-02-22 10:32:49 +0800146 if not isinstance(error_data, six.string_types):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200147 (error_code, status_code) = map(str, error_data)
148 else:
149 status_code = None
150 error_code = error_data
151 parts = error_code.split('.')
152 basematch = ""
153 num_parts = len(parts)
154 max_index = num_parts - 1
155 add_cls = error_cls
llg821243b20502014-02-22 10:32:49 +0800156 for i_part in six.moves.xrange(num_parts):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200157 part = parts[i_part]
158 leaf = i_part == max_index
159 if not leaf:
160 match = basematch + part + "[.].*"
161 else:
162 match = basematch + part
163
164 basematch += part + "[.]"
165 if not hasattr(add_cls, part):
166 cls_dict = {"CODE_RE": match}
167 if leaf and status_code is not None:
168 cls_dict["STATUS_RE"] = status_code
169 cls = type(part, (base, ), cls_dict)
170 setattr(add_cls, part, cls())
171 add_cls = cls
172 elif leaf:
173 raise LookupError("Tries to redefine an error code \"%s\"" % part)
174 else:
175 add_cls = getattr(add_cls, part)
176
177
Attila Fazekas3e381f72013-08-01 16:52:23 +0200178# TODO(afazekas): classmethod handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200179def friendly_function_name_simple(call_able):
180 name = ""
181 if hasattr(call_able, "im_class"):
182 name += call_able.im_class.__name__ + "."
183 name += call_able.__name__
184 return name
185
186
187def friendly_function_call_str(call_able, *args, **kwargs):
188 string = friendly_function_name_simple(call_able)
189 string += "(" + ", ".join(map(str, args))
190 if len(kwargs):
191 if len(args):
192 string += ", "
193 string += ", ".join("=".join(map(str, (key, value)))
Matthew Treinish1d14c542014-06-17 20:25:40 -0400194 for (key, value) in kwargs.items())
Attila Fazekasa23f5002012-10-23 19:32:45 +0200195 return string + ")"
196
197
Attila Fazekasdc216422013-01-29 15:12:14 +0100198class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500199 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030200
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000201 credentials = ['primary']
202
Attila Fazekasa23f5002012-10-23 19:32:45 +0200203 @classmethod
Matthew Treinishdfd7ac02015-02-09 17:47:31 -0500204 def skip_checks(cls):
205 super(BotoTestCase, cls).skip_checks()
206 if not CONF.compute_feature_enabled.ec2_api:
207 raise cls.skipException("The EC2 API is not available")
Andrea Frittoli046953c2015-03-12 11:20:04 +0000208 if not CONF.identity_feature_enabled.api_v2 or \
209 not CONF.identity.auth_version == 'v2':
210 raise cls.skipException("Identity v2 is not available")
Matthew Treinishdfd7ac02015-02-09 17:47:31 -0500211
212 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100213 def resource_setup(cls):
214 super(BotoTestCase, cls).resource_setup()
Matthew Treinishf054a9c2013-10-28 21:34:47 -0400215 cls.conclusion = decision_maker()
Takashi NATSUME6d5a2b42015-09-08 11:27:49 +0900216 # The trash contains cleanup functions and parameters in tuples
Attila Fazekasa23f5002012-10-23 19:32:45 +0200217 # (function, *args, **kwargs)
218 cls._resource_trash_bin = {}
219 cls._sequence = -1
220 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100221 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800222 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100223 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200224 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100225 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800226 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100227 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200228
229 @classmethod
230 def addResourceCleanUp(cls, function, *args, **kwargs):
231 """Adds CleanUp callable, used by tearDownClass.
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000232
Attila Fazekasb2902af2013-02-16 16:22:44 +0100233 Recommended to a use (deep)copy on the mutable args.
234 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200235 cls._sequence = cls._sequence + 1
236 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
237 return cls._sequence
238
239 @classmethod
240 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500241 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200242 del cls._resource_trash_bin[key]
243
Attila Fazekas3e381f72013-08-01 16:52:23 +0200244 # TODO(afazekas): Add "with" context handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200245 def assertBotoError(self, excMatcher, callableObj,
246 *args, **kwargs):
247 """Example usage:
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000248
Attila Fazekasa23f5002012-10-23 19:32:45 +0200249 self.assertBotoError(self.ec2_error_code.client.
250 InvalidKeyPair.Duplicate,
251 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100252 key_name)
253 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200254 try:
255 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100256 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200257 error_msg = excMatcher.match(exc)
258 if error_msg is not None:
Matthew Treinish843227d2015-04-23 10:17:17 -0400259 raise self.failureException(error_msg)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200260 else:
Matthew Treinish843227d2015-04-23 10:17:17 -0400261 raise self.failureException("BotoServerError not raised")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200262
263 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100264 def resource_cleanup(cls):
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000265 """Calls the callables added by addResourceCleanUp
266
Yair Frieda039f872014-01-02 12:11:10 +0200267 when you overwrite this function don't forget to call this too.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100268 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200269 fail_count = 0
270 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
271 for key in trash_keys:
272 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
273 try:
Yair Frieda039f872014-01-02 12:11:10 +0200274 func_name = friendly_function_call_str(function, *pos_args,
275 **kw_args)
276 LOG.debug("Cleaning up: %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200277 function(*pos_args, **kw_args)
Yair Frieda039f872014-01-02 12:11:10 +0200278 except BaseException:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200279 fail_count += 1
Yair Frieda039f872014-01-02 12:11:10 +0200280 LOG.exception("Cleanup failed %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200281 finally:
282 del cls._resource_trash_bin[key]
Andrea Frittoli29fea352014-09-15 13:31:14 +0100283 super(BotoTestCase, cls).resource_cleanup()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200284 # NOTE(afazekas): let the super called even on exceptions
285 # The real exceptions already logged, if the super throws another,
286 # does not causes hidden issues
Attila Fazekasa23f5002012-10-23 19:32:45 +0200287 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100288 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200289
290 ec2_error_code = BotoExceptionMatcher()
291 # InsufficientInstanceCapacity can be both server and client error
292 ec2_error_code.server = ServerError()
293 ec2_error_code.client = ClientError()
294 s3_error_code = BotoExceptionMatcher()
295 s3_error_code.server = ServerError()
296 s3_error_code.client = ClientError()
297 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekas3e381f72013-08-01 16:52:23 +0200298 # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
Attila Fazekasc66ee652013-01-31 06:56:13 +0100299 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200300 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100301 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200302 valid_volume_status = set(('creating', 'available', 'in-use',
303 'deleting', 'deleted', 'error'))
304 valid_snapshot_status = set(('pending', 'completed', 'error'))
305
Attila Fazekas37f83042013-01-12 16:13:03 +0100306 gone_set = set(('_GONE',))
307
Attila Fazekas40aa3612013-01-19 22:16:38 +0100308 @classmethod
309 def get_lfunction_gone(cls, obj):
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000310 # NOTE: If the object is instance of a well know type returns back with
311 # with the corresponding function otherwise it assumes the obj itself
312 # is the function.
Attila Fazekas40aa3612013-01-19 22:16:38 +0100313 ec = cls.ec2_error_code
314 if isinstance(obj, ec2.instance.Instance):
315 colusure_matcher = ec.client.InvalidInstanceID.NotFound
316 status_attr = "state"
317 elif isinstance(obj, ec2.image.Image):
318 colusure_matcher = ec.client.InvalidAMIID.NotFound
319 status_attr = "state"
320 elif isinstance(obj, ec2.snapshot.Snapshot):
321 colusure_matcher = ec.client.InvalidSnapshot.NotFound
322 status_attr = "status"
323 elif isinstance(obj, ec2.volume.Volume):
324 colusure_matcher = ec.client.InvalidVolume.NotFound
325 status_attr = "status"
326 else:
327 return obj
328
329 def _status():
330 try:
331 obj.update(validate=True)
332 except ValueError:
333 return "_GONE"
334 except exception.EC2ResponseError as exc:
Attila Fazekas55c597d2014-02-21 13:10:41 +0100335 if colusure_matcher.match(exc) is None:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100336 return "_GONE"
337 else:
338 raise
339 return getattr(obj, status_attr)
340
341 return _status
342
Attila Fazekas37f83042013-01-12 16:13:03 +0100343 def state_wait_gone(self, lfunction, final_set, valid_set):
344 if not isinstance(final_set, set):
345 final_set = set((final_set,))
346 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100347 lfunction = self.get_lfunction_gone(lfunction)
Masayuki Igawa224a8272014-02-17 15:07:43 +0900348 state = wait.state_wait(lfunction, final_set, valid_set)
Attila Fazekas37f83042013-01-12 16:13:03 +0100349 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200350 return state
351
Attila Fazekas37f83042013-01-12 16:13:03 +0100352 def waitImageState(self, lfunction, wait_for):
353 return self.state_wait_gone(lfunction, wait_for,
354 self.valid_image_state)
355
Attila Fazekasa23f5002012-10-23 19:32:45 +0200356 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100357 return self.state_wait_gone(lfunction, wait_for,
358 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200359
Attila Fazekasa23f5002012-10-23 19:32:45 +0200360 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100361 return self.state_wait_gone(lfunction, wait_for,
362 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200363
Attila Fazekas40aa3612013-01-19 22:16:38 +0100364 def waitVolumeStatus(self, lfunction, wait_for):
365 return self.state_wait_gone(lfunction, wait_for,
366 self.valid_volume_status)
367
Attila Fazekasa23f5002012-10-23 19:32:45 +0200368 def assertImageStateWait(self, lfunction, wait_for):
369 state = self.waitImageState(lfunction, wait_for)
370 self.assertIn(state, wait_for)
371
372 def assertInstanceStateWait(self, lfunction, wait_for):
373 state = self.waitInstanceState(lfunction, wait_for)
374 self.assertIn(state, wait_for)
375
376 def assertVolumeStatusWait(self, lfunction, wait_for):
377 state = self.waitVolumeStatus(lfunction, wait_for)
378 self.assertIn(state, wait_for)
379
380 def assertSnapshotStatusWait(self, lfunction, wait_for):
381 state = self.waitSnapshotStatus(lfunction, wait_for)
382 self.assertIn(state, wait_for)
383
PranaliDeore6e1ad562015-08-25 03:26:55 -0700384 def assertAddressDisassociatedWait(self, address):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200385
386 def _disassociate():
387 cli = self.ec2_client
388 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
389 if len(addresses) != 1:
390 return "INVALID"
391 if addresses[0].instance_id:
392 LOG.info("%s associated to %s",
393 address.public_ip,
394 addresses[0].instance_id)
395 return "ASSOCIATED"
396 return "DISASSOCIATED"
397
Masayuki Igawa224a8272014-02-17 15:07:43 +0900398 state = wait.state_wait(_disassociate, "DISASSOCIATED",
399 set(("ASSOCIATED", "DISASSOCIATED")))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200400 self.assertEqual(state, "DISASSOCIATED")
401
402 def assertAddressReleasedWait(self, address):
403
404 def _address_delete():
Attila Fazekas3e381f72013-08-01 16:52:23 +0200405 # NOTE(afazekas): the filter gives back IP
Attila Fazekasa23f5002012-10-23 19:32:45 +0200406 # even if it is not associated to my tenant
407 if (address.public_ip not in map(lambda a: a.public_ip,
408 self.ec2_client.get_all_addresses())):
409 return "DELETED"
410 return "NOTDELETED"
411
Masayuki Igawa224a8272014-02-17 15:07:43 +0900412 state = wait.state_wait(_address_delete, "DELETED")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200413 self.assertEqual(state, "DELETED")
414
415 def assertReSearch(self, regexp, string):
416 if re.search(regexp, string) is None:
417 raise self.failureException("regexp: '%s' not found in '%s'" %
418 (regexp, string))
419
420 def assertNotReSearch(self, regexp, string):
421 if re.search(regexp, string) is not None:
422 raise self.failureException("regexp: '%s' found in '%s'" %
423 (regexp, string))
424
425 def assertReMatch(self, regexp, string):
426 if re.match(regexp, string) is None:
427 raise self.failureException("regexp: '%s' not matches on '%s'" %
428 (regexp, string))
429
430 def assertNotReMatch(self, regexp, string):
431 if re.match(regexp, string) is not None:
432 raise self.failureException("regexp: '%s' matches on '%s'" %
433 (regexp, string))
434
435 @classmethod
436 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500437 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200438 exc_num = 0
439 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700440 with contextlib.closing(
441 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200442 if isinstance(bucket, basestring):
443 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100444 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200445 for obj in bucket.list():
446 try:
447 bucket.delete_key(obj.key)
448 obj.close()
Yair Frieda039f872014-01-02 12:11:10 +0200449 except BaseException:
450 LOG.exception("Failed to delete key %s " % obj.key)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200451 exc_num += 1
452 conn.delete_bucket(bucket)
Yair Frieda039f872014-01-02 12:11:10 +0200453 except BaseException:
454 LOG.exception("Failed to destroy bucket %s " % bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200455 exc_num += 1
456 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100457 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200458
459 @classmethod
460 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500461 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200462 exc_num = 0
463
464 def _instance_state():
465 try:
466 instance.update(validate=True)
467 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100468 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100469 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100470 if cls.ec2_error_code.\
Attila Fazekas55c597d2014-02-21 13:10:41 +0100471 client.InvalidInstanceID.NotFound.match(exc) is None:
Attila Fazekas37f83042013-01-12 16:13:03 +0100472 return "_GONE"
Attila Fazekas3e381f72013-08-01 16:52:23 +0200473 # NOTE(afazekas): incorrect code,
nayna-patelfdc87a52015-08-03 10:46:33 +0000474 # but the resource must be destroyed
Attila Fazekas37f83042013-01-12 16:13:03 +0100475 if exc.error_code == "InstanceNotFound":
476 return "_GONE"
477
Attila Fazekasa23f5002012-10-23 19:32:45 +0200478 return instance.state
479
480 for instance in reservation.instances:
481 try:
482 instance.terminate()
Masayuki Igawa224a8272014-02-17 15:07:43 +0900483 wait.re_search_wait(_instance_state, "_GONE")
Yair Frieda039f872014-01-02 12:11:10 +0200484 except BaseException:
485 LOG.exception("Failed to terminate instance %s " % instance)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200486 exc_num += 1
487 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100488 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200489
Attila Fazekas3e381f72013-08-01 16:52:23 +0200490 # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
Attila Fazekasa23f5002012-10-23 19:32:45 +0200491 # to write better teardown
492
493 @classmethod
494 def destroy_security_group_wait(cls, group):
495 """Delete group.
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000496
Attila Fazekasa23f5002012-10-23 19:32:45 +0200497 Use just for teardown!
498 """
Attila Fazekas3e381f72013-08-01 16:52:23 +0200499 # NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200500 group.delete()
501
502 @classmethod
503 def destroy_volume_wait(cls, volume):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700504 """Delete volume, tries to detach first.
Ken'ichi Ohmichi0afa8812015-11-19 08:58:54 +0000505
Attila Fazekasa23f5002012-10-23 19:32:45 +0200506 Use just for teardown!
507 """
508 exc_num = 0
509 snaps = volume.snapshots()
510 if len(snaps):
511 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100512 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200513
nayna-patelfdc87a52015-08-03 10:46:33 +0000514 # NOTE(afazekas): detaching/attaching not valid EC2 status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200515 def _volume_state():
516 volume.update(validate=True)
517 try:
ghanshyam308640c2014-10-14 17:23:31 +0900518 # NOTE(gmann): Make sure volume is attached.
519 # Checking status as 'not "available"' is not enough to make
520 # sure volume is attached as it can be in "error" state
521 if volume.status == "in-use":
Attila Fazekasa23f5002012-10-23 19:32:45 +0200522 volume.detach(force=True)
Yair Frieda039f872014-01-02 12:11:10 +0200523 except BaseException:
524 LOG.exception("Failed to detach volume %s" % volume)
Attila Fazekas3e381f72013-08-01 16:52:23 +0200525 # exc_num += 1 "nonlocal" not in python2
Attila Fazekasa23f5002012-10-23 19:32:45 +0200526 return volume.status
527
528 try:
Masayuki Igawa224a8272014-02-17 15:07:43 +0900529 wait.re_search_wait(_volume_state, "available")
530 # not validates status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200531 LOG.info(_volume_state())
532 volume.delete()
Yair Frieda039f872014-01-02 12:11:10 +0200533 except BaseException:
534 LOG.exception("Failed to delete volume %s" % volume)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200535 exc_num += 1
536 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100537 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200538
539 @classmethod
540 def destroy_snapshot_wait(cls, snapshot):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700541 """delete snapshot, wait until it ceases to exist."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200542 snapshot.delete()
543
544 def _update():
545 snapshot.update(validate=True)
546
Masayuki Igawa224a8272014-02-17 15:07:43 +0900547 wait.wait_exception(_update)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200548
549
550# you can specify tuples if you want to specify the status pattern
551for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
552 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
553 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
554 'Gateway.NotAttached', 'IdempotentParameterMismatch',
555 'IncorrectInstanceState', 'IncorrectState',
556 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
557 'InsufficientReservedInstancesCapacity',
558 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
559 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
560 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
561 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
562 'InvalidCustomerGateway.DuplicateIpAddress',
563 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
564 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
565 'InvalidFilter', 'InvalidGatewayID.NotFound',
566 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
567 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
568 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
569 'InvalidInstanceID.NotFound',
570 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
571 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
572 'InvalidKeyPair.NotFound', 'InvalidManifest',
573 'InvalidNetworkAclEntry.NotFound',
574 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
575 'InvalidParameterValue', 'InvalidPermission.Duplicate',
576 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
577 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
578 'InvalidRouteTableID.NotFound',
579 'InvalidSecurity.RequestHasExpired',
580 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
581 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
582 'InvalidReservedInstancesOfferingId',
583 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
584 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
585 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
586 'InvalidVpnConnectionID.NotFound',
587 'InvalidVpnGatewayID.NotFound',
588 'InvalidZone.NotFound', 'LegacySecurityGroup',
589 'MissingParameter', 'NetworkAclEntryAlreadyExists',
590 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
591 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
592 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
593 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
594 'ResourceLimitExceeded', 'RouteAlreadyExists',
595 'RouteLimitExceeded', 'RouteTableLimitExceeded',
596 'RulesPerSecurityGroupLimitExceeded',
597 'SecurityGroupLimitExceeded',
598 'SecurityGroupsPerInstanceLimitExceeded',
599 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
600 'UnknownParameter', 'UnsupportedOperation',
601 'VolumeLimitExceeded', 'VpcLimitExceeded',
602 'VpnConnectionLimitExceeded',
603 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
604 _add_matcher_class(BotoTestCase.ec2_error_code.client,
605 code, base=ClientError)
606
607for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
608 'InsufficientReservedInstanceCapacity', 'InternalError',
609 'Unavailable'):
610 _add_matcher_class(BotoTestCase.ec2_error_code.server,
611 code, base=ServerError)
612
613
614for code in (('AccessDenied', 403),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400615 ('AccountProblem', 403),
616 ('AmbiguousGrantByEmailAddress', 400),
617 ('BadDigest', 400),
618 ('BucketAlreadyExists', 409),
619 ('BucketAlreadyOwnedByYou', 409),
620 ('BucketNotEmpty', 409),
621 ('CredentialsNotSupported', 400),
622 ('CrossLocationLoggingProhibited', 403),
623 ('EntityTooSmall', 400),
624 ('EntityTooLarge', 400),
625 ('ExpiredToken', 400),
626 ('IllegalVersioningConfigurationException', 400),
627 ('IncompleteBody', 400),
628 ('IncorrectNumberOfFilesInPostRequest', 400),
629 ('InlineDataTooLarge', 400),
630 ('InvalidAccessKeyId', 403),
Attila Fazekasa23f5002012-10-23 19:32:45 +0200631 'InvalidAddressingHeader',
Matthew Treinish1d14c542014-06-17 20:25:40 -0400632 ('InvalidArgument', 400),
633 ('InvalidBucketName', 400),
634 ('InvalidBucketState', 409),
635 ('InvalidDigest', 400),
636 ('InvalidLocationConstraint', 400),
637 ('InvalidPart', 400),
638 ('InvalidPartOrder', 400),
639 ('InvalidPayer', 403),
640 ('InvalidPolicyDocument', 400),
641 ('InvalidRange', 416),
642 ('InvalidRequest', 400),
643 ('InvalidSecurity', 403),
644 ('InvalidSOAPRequest', 400),
645 ('InvalidStorageClass', 400),
646 ('InvalidTargetBucketForLogging', 400),
647 ('InvalidToken', 400),
648 ('InvalidURI', 400),
649 ('KeyTooLong', 400),
650 ('MalformedACLError', 400),
651 ('MalformedPOSTRequest', 400),
652 ('MalformedXML', 400),
653 ('MaxMessageLengthExceeded', 400),
654 ('MaxPostPreDataLengthExceededError', 400),
655 ('MetadataTooLarge', 400),
656 ('MethodNotAllowed', 405),
657 ('MissingAttachment'),
658 ('MissingContentLength', 411),
659 ('MissingRequestBodyError', 400),
660 ('MissingSecurityElement', 400),
661 ('MissingSecurityHeader', 400),
662 ('NoLoggingStatusForKey', 400),
663 ('NoSuchBucket', 404),
664 ('NoSuchKey', 404),
665 ('NoSuchLifecycleConfiguration', 404),
666 ('NoSuchUpload', 404),
667 ('NoSuchVersion', 404),
668 ('NotSignedUp', 403),
669 ('NotSuchBucketPolicy', 404),
670 ('OperationAborted', 409),
671 ('PermanentRedirect', 301),
672 ('PreconditionFailed', 412),
673 ('Redirect', 307),
674 ('RequestIsNotMultiPartContent', 400),
675 ('RequestTimeout', 400),
676 ('RequestTimeTooSkewed', 403),
677 ('RequestTorrentOfBucketError', 400),
678 ('SignatureDoesNotMatch', 403),
679 ('TemporaryRedirect', 307),
680 ('TokenRefreshRequired', 400),
681 ('TooManyBuckets', 400),
682 ('UnexpectedContent', 400),
683 ('UnresolvableGrantByEmailAddress', 400),
684 ('UserKeyMustBeSpecified', 400)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200685 _add_matcher_class(BotoTestCase.s3_error_code.client,
686 code, base=ClientError)
687
688
689for code in (('InternalError', 500),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400690 ('NotImplemented', 501),
691 ('ServiceUnavailable', 503),
692 ('SlowDown', 503)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200693 _add_matcher_class(BotoTestCase.s3_error_code.server,
694 code, base=ServerError)