blob: 8812a105de63ac9dca5fcc623429823420331d2a [file] [log] [blame]
Attila Fazekasa23f5002012-10-23 19:32:45 +02001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
Monty Taylorb2ca5ca2013-04-28 18:00:21 -070018import contextlib
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090019import logging as orig_logging
Attila Fazekas234d3e82013-02-22 16:39:49 +010020import os
Matthew Treinisha83a16e2012-12-07 13:44:02 -050021import re
Attila Fazekas234d3e82013-02-22 16:39:49 +010022import urlparse
Attila Fazekasa23f5002012-10-23 19:32:45 +020023
Matthew Treinisha83a16e2012-12-07 13:44:02 -050024import boto
Attila Fazekas40aa3612013-01-19 22:16:38 +010025from boto import ec2
26from boto import exception
27from boto import s3
Attila Fazekas234d3e82013-02-22 16:39:49 +010028import keystoneclient.exceptions
Matthew Treinisha83a16e2012-12-07 13:44:02 -050029
Attila Fazekas234d3e82013-02-22 16:39:49 +010030import tempest.clients
31from tempest.common.utils.file_utils import have_effective_read_access
32import tempest.config
Attila Fazekas40aa3612013-01-19 22:16:38 +010033from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040034from tempest.openstack.common import log as logging
Attila Fazekasdc216422013-01-29 15:12:14 +010035import tempest.test
Sean Dague09761f62013-05-13 15:20:40 -040036from tempest.thirdparty.boto.utils.wait import re_search_wait
37from tempest.thirdparty.boto.utils.wait import state_wait
38from tempest.thirdparty.boto.utils.wait import wait_exception
Matthew Treinisha83a16e2012-12-07 13:44:02 -050039
Attila Fazekasa23f5002012-10-23 19:32:45 +020040LOG = logging.getLogger(__name__)
41
42
Attila Fazekas234d3e82013-02-22 16:39:49 +010043def decision_maker():
44 A_I_IMAGES_READY = True # ari,ami,aki
45 S3_CAN_CONNECT_ERROR = None
46 EC2_CAN_CONNECT_ERROR = None
47 secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system
48 id_matcher = re.compile("[A-Za-z0-9]{20,}")
49
50 def all_read(*args):
51 return all(map(have_effective_read_access, args))
52
53 config = tempest.config.TempestConfig()
54 materials_path = config.boto.s3_materials_path
55 ami_path = materials_path + os.sep + config.boto.ami_manifest
56 aki_path = materials_path + os.sep + config.boto.aki_manifest
57 ari_path = materials_path + os.sep + config.boto.ari_manifest
58
59 A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
60 boto_logger = logging.getLogger('boto')
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040061 level = boto_logger.logger.level
62 boto_logger.logger.setLevel(orig_logging.CRITICAL) # suppress logging
63 # for these
Attila Fazekas234d3e82013-02-22 16:39:49 +010064
65 def _cred_sub_check(connection_data):
66 if not id_matcher.match(connection_data["aws_access_key_id"]):
67 raise Exception("Invalid AWS access Key")
68 if not secret_matcher.match(connection_data["aws_secret_access_key"]):
69 raise Exception("Invalid AWS secret Key")
70 raise Exception("Unknown (Authentication?) Error")
71 openstack = tempest.clients.Manager()
72 try:
73 if urlparse.urlparse(config.boto.ec2_url).hostname is None:
74 raise Exception("Failed to get hostname from the ec2_url")
75 ec2client = openstack.ec2api_client
76 try:
77 ec2client.get_all_regions()
78 except exception.BotoServerError as exc:
79 if exc.error_code is None:
80 raise Exception("EC2 target does not looks EC2 service")
81 _cred_sub_check(ec2client.connection_data)
82
83 except keystoneclient.exceptions.Unauthorized:
84 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
85 " faild to get them even by keystoneclient"
86 except Exception as exc:
87 EC2_CAN_CONNECT_ERROR = str(exc)
88
89 try:
90 if urlparse.urlparse(config.boto.s3_url).hostname is None:
91 raise Exception("Failed to get hostname from the s3_url")
92 s3client = openstack.s3_client
93 try:
94 s3client.get_bucket("^INVALID*#()@INVALID.")
95 except exception.BotoServerError as exc:
96 if exc.status == 403:
97 _cred_sub_check(s3client.connection_data)
98 except Exception as exc:
99 S3_CAN_CONNECT_ERROR = str(exc)
100 except keystoneclient.exceptions.Unauthorized:
101 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
102 " faild to get them even by keystoneclient"
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -0400103 boto_logger.logger.setLevel(level)
Attila Fazekas234d3e82013-02-22 16:39:49 +0100104 return {'A_I_IMAGES_READY': A_I_IMAGES_READY,
105 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR,
106 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR}
107
108
Attila Fazekasa23f5002012-10-23 19:32:45 +0200109class BotoExceptionMatcher(object):
110 STATUS_RE = r'[45]\d\d'
111 CODE_RE = '.*' # regexp makes sense in group match
112
113 def match(self, exc):
Attila Fazekas40aa3612013-01-19 22:16:38 +0100114 if not isinstance(exc, exception.BotoServerError):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200115 return "%r not an BotoServerError instance" % exc
116 LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
117 if re.match(self.STATUS_RE, str(exc.status)) is None:
118 return ("Status code (%s) does not match"
119 "the expected re pattern \"%s\""
120 % (exc.status, self.STATUS_RE))
121 if re.match(self.CODE_RE, str(exc.error_code)) is None:
122 return ("Error code (%s) does not match" +
123 "the expected re pattern \"%s\"") %\
124 (exc.error_code, self.CODE_RE)
125
126
127class ClientError(BotoExceptionMatcher):
128 STATUS_RE = r'4\d\d'
129
130
131class ServerError(BotoExceptionMatcher):
132 STATUS_RE = r'5\d\d'
133
134
135def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
136 """
137 Usable for adding an ExceptionMatcher(s) into the exception tree.
138 The not leaf elements does wildcard match
139 """
140 # in error_code just literal and '.' characters expected
141 if not isinstance(error_data, basestring):
142 (error_code, status_code) = map(str, error_data)
143 else:
144 status_code = None
145 error_code = error_data
146 parts = error_code.split('.')
147 basematch = ""
148 num_parts = len(parts)
149 max_index = num_parts - 1
150 add_cls = error_cls
151 for i_part in xrange(num_parts):
152 part = parts[i_part]
153 leaf = i_part == max_index
154 if not leaf:
155 match = basematch + part + "[.].*"
156 else:
157 match = basematch + part
158
159 basematch += part + "[.]"
160 if not hasattr(add_cls, part):
161 cls_dict = {"CODE_RE": match}
162 if leaf and status_code is not None:
163 cls_dict["STATUS_RE"] = status_code
164 cls = type(part, (base, ), cls_dict)
165 setattr(add_cls, part, cls())
166 add_cls = cls
167 elif leaf:
168 raise LookupError("Tries to redefine an error code \"%s\"" % part)
169 else:
170 add_cls = getattr(add_cls, part)
171
172
Attila Fazekas3e381f72013-08-01 16:52:23 +0200173# TODO(afazekas): classmethod handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200174def friendly_function_name_simple(call_able):
175 name = ""
176 if hasattr(call_able, "im_class"):
177 name += call_able.im_class.__name__ + "."
178 name += call_able.__name__
179 return name
180
181
182def friendly_function_call_str(call_able, *args, **kwargs):
183 string = friendly_function_name_simple(call_able)
184 string += "(" + ", ".join(map(str, args))
185 if len(kwargs):
186 if len(args):
187 string += ", "
188 string += ", ".join("=".join(map(str, (key, value)))
189 for (key, value) in kwargs.items())
190 return string + ")"
191
192
Attila Fazekasdc216422013-01-29 15:12:14 +0100193class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500194 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030195
Attila Fazekas234d3e82013-02-22 16:39:49 +0100196 conclusion = decision_maker()
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030197
Attila Fazekasa23f5002012-10-23 19:32:45 +0200198 @classmethod
199 def setUpClass(cls):
200 # The trash contains cleanup functions and paramaters in tuples
201 # (function, *args, **kwargs)
202 cls._resource_trash_bin = {}
203 cls._sequence = -1
204 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100205 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800206 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100207 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200208 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100209 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800210 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100211 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200212
213 @classmethod
214 def addResourceCleanUp(cls, function, *args, **kwargs):
215 """Adds CleanUp callable, used by tearDownClass.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100216 Recommended to a use (deep)copy on the mutable args.
217 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200218 cls._sequence = cls._sequence + 1
219 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
220 return cls._sequence
221
222 @classmethod
223 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500224 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200225 del cls._resource_trash_bin[key]
226
Attila Fazekas3e381f72013-08-01 16:52:23 +0200227 # TODO(afazekas): Add "with" context handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200228 def assertBotoError(self, excMatcher, callableObj,
229 *args, **kwargs):
230 """Example usage:
231 self.assertBotoError(self.ec2_error_code.client.
232 InvalidKeyPair.Duplicate,
233 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100234 key_name)
235 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200236 try:
237 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100238 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200239 error_msg = excMatcher.match(exc)
240 if error_msg is not None:
241 raise self.failureException, error_msg
242 else:
243 raise self.failureException, "BotoServerError not raised"
244
245 @classmethod
246 def tearDownClass(cls):
Attila Fazekasb2902af2013-02-16 16:22:44 +0100247 """Calls the callables added by addResourceCleanUp,
248 when you overwire this function dont't forget to call this too.
249 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200250 fail_count = 0
251 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
252 for key in trash_keys:
253 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
254 try:
255 LOG.debug("Cleaning up: %s" %
256 friendly_function_call_str(function, *pos_args,
257 **kw_args))
258 function(*pos_args, **kw_args)
259 except BaseException as exc:
260 fail_count += 1
261 LOG.exception(exc)
262 finally:
263 del cls._resource_trash_bin[key]
264 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100265 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200266
267 ec2_error_code = BotoExceptionMatcher()
268 # InsufficientInstanceCapacity can be both server and client error
269 ec2_error_code.server = ServerError()
270 ec2_error_code.client = ClientError()
271 s3_error_code = BotoExceptionMatcher()
272 s3_error_code.server = ServerError()
273 s3_error_code.client = ClientError()
274 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekas3e381f72013-08-01 16:52:23 +0200275 # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
Attila Fazekasc66ee652013-01-31 06:56:13 +0100276 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200277 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100278 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200279 valid_volume_status = set(('creating', 'available', 'in-use',
280 'deleting', 'deleted', 'error'))
281 valid_snapshot_status = set(('pending', 'completed', 'error'))
282
Attila Fazekas37f83042013-01-12 16:13:03 +0100283 gone_set = set(('_GONE',))
284
Attila Fazekas40aa3612013-01-19 22:16:38 +0100285 @classmethod
286 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400287 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100288 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400289 is the function.
290 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100291 ec = cls.ec2_error_code
292 if isinstance(obj, ec2.instance.Instance):
293 colusure_matcher = ec.client.InvalidInstanceID.NotFound
294 status_attr = "state"
295 elif isinstance(obj, ec2.image.Image):
296 colusure_matcher = ec.client.InvalidAMIID.NotFound
297 status_attr = "state"
298 elif isinstance(obj, ec2.snapshot.Snapshot):
299 colusure_matcher = ec.client.InvalidSnapshot.NotFound
300 status_attr = "status"
301 elif isinstance(obj, ec2.volume.Volume):
302 colusure_matcher = ec.client.InvalidVolume.NotFound
303 status_attr = "status"
304 else:
305 return obj
306
307 def _status():
308 try:
309 obj.update(validate=True)
310 except ValueError:
311 return "_GONE"
312 except exception.EC2ResponseError as exc:
313 if colusure_matcher.match(exc):
314 return "_GONE"
315 else:
316 raise
317 return getattr(obj, status_attr)
318
319 return _status
320
Attila Fazekas37f83042013-01-12 16:13:03 +0100321 def state_wait_gone(self, lfunction, final_set, valid_set):
322 if not isinstance(final_set, set):
323 final_set = set((final_set,))
324 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100325 lfunction = self.get_lfunction_gone(lfunction)
Attila Fazekas37f83042013-01-12 16:13:03 +0100326 state = state_wait(lfunction, final_set, valid_set)
327 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200328 return state
329
Attila Fazekas37f83042013-01-12 16:13:03 +0100330 def waitImageState(self, lfunction, wait_for):
331 return self.state_wait_gone(lfunction, wait_for,
332 self.valid_image_state)
333
Attila Fazekasa23f5002012-10-23 19:32:45 +0200334 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100335 return self.state_wait_gone(lfunction, wait_for,
336 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200337
Attila Fazekasa23f5002012-10-23 19:32:45 +0200338 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100339 return self.state_wait_gone(lfunction, wait_for,
340 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200341
Attila Fazekas40aa3612013-01-19 22:16:38 +0100342 def waitVolumeStatus(self, lfunction, wait_for):
343 return self.state_wait_gone(lfunction, wait_for,
344 self.valid_volume_status)
345
Attila Fazekasa23f5002012-10-23 19:32:45 +0200346 def assertImageStateWait(self, lfunction, wait_for):
347 state = self.waitImageState(lfunction, wait_for)
348 self.assertIn(state, wait_for)
349
350 def assertInstanceStateWait(self, lfunction, wait_for):
351 state = self.waitInstanceState(lfunction, wait_for)
352 self.assertIn(state, wait_for)
353
354 def assertVolumeStatusWait(self, lfunction, wait_for):
355 state = self.waitVolumeStatus(lfunction, wait_for)
356 self.assertIn(state, wait_for)
357
358 def assertSnapshotStatusWait(self, lfunction, wait_for):
359 state = self.waitSnapshotStatus(lfunction, wait_for)
360 self.assertIn(state, wait_for)
361
362 def assertAddressDissasociatedWait(self, address):
363
364 def _disassociate():
365 cli = self.ec2_client
366 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
367 if len(addresses) != 1:
368 return "INVALID"
369 if addresses[0].instance_id:
370 LOG.info("%s associated to %s",
371 address.public_ip,
372 addresses[0].instance_id)
373 return "ASSOCIATED"
374 return "DISASSOCIATED"
375
376 state = state_wait(_disassociate, "DISASSOCIATED",
377 set(("ASSOCIATED", "DISASSOCIATED")))
378 self.assertEqual(state, "DISASSOCIATED")
379
380 def assertAddressReleasedWait(self, address):
381
382 def _address_delete():
Attila Fazekas3e381f72013-08-01 16:52:23 +0200383 # NOTE(afazekas): the filter gives back IP
Attila Fazekasa23f5002012-10-23 19:32:45 +0200384 # even if it is not associated to my tenant
385 if (address.public_ip not in map(lambda a: a.public_ip,
386 self.ec2_client.get_all_addresses())):
387 return "DELETED"
388 return "NOTDELETED"
389
390 state = state_wait(_address_delete, "DELETED")
391 self.assertEqual(state, "DELETED")
392
393 def assertReSearch(self, regexp, string):
394 if re.search(regexp, string) is None:
395 raise self.failureException("regexp: '%s' not found in '%s'" %
396 (regexp, string))
397
398 def assertNotReSearch(self, regexp, string):
399 if re.search(regexp, string) is not None:
400 raise self.failureException("regexp: '%s' found in '%s'" %
401 (regexp, string))
402
403 def assertReMatch(self, regexp, string):
404 if re.match(regexp, string) is None:
405 raise self.failureException("regexp: '%s' not matches on '%s'" %
406 (regexp, string))
407
408 def assertNotReMatch(self, regexp, string):
409 if re.match(regexp, string) is not None:
410 raise self.failureException("regexp: '%s' matches on '%s'" %
411 (regexp, string))
412
413 @classmethod
414 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500415 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200416 exc_num = 0
417 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700418 with contextlib.closing(
419 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200420 if isinstance(bucket, basestring):
421 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100422 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200423 for obj in bucket.list():
424 try:
425 bucket.delete_key(obj.key)
426 obj.close()
427 except BaseException as exc:
428 LOG.exception(exc)
429 exc_num += 1
430 conn.delete_bucket(bucket)
431 except BaseException as exc:
432 LOG.exception(exc)
433 exc_num += 1
434 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100435 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200436
437 @classmethod
438 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500439 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200440 exc_num = 0
441
442 def _instance_state():
443 try:
444 instance.update(validate=True)
445 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100446 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100447 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100448 if cls.ec2_error_code.\
Sean Dague14c68182013-04-14 15:34:30 -0400449 client.InvalidInstanceID.NotFound.match(exc):
Attila Fazekas37f83042013-01-12 16:13:03 +0100450 return "_GONE"
Attila Fazekas3e381f72013-08-01 16:52:23 +0200451 # NOTE(afazekas): incorrect code,
Attila Fazekas37f83042013-01-12 16:13:03 +0100452 # but the resource must be destoreyd
453 if exc.error_code == "InstanceNotFound":
454 return "_GONE"
455
Attila Fazekasa23f5002012-10-23 19:32:45 +0200456 return instance.state
457
458 for instance in reservation.instances:
459 try:
460 instance.terminate()
Attila Fazekas37f83042013-01-12 16:13:03 +0100461 re_search_wait(_instance_state, "_GONE")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200462 except BaseException as exc:
463 LOG.exception(exc)
464 exc_num += 1
465 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100466 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200467
Attila Fazekas3e381f72013-08-01 16:52:23 +0200468 # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
Attila Fazekasa23f5002012-10-23 19:32:45 +0200469 # to write better teardown
470
471 @classmethod
472 def destroy_security_group_wait(cls, group):
473 """Delete group.
474 Use just for teardown!
475 """
Attila Fazekas3e381f72013-08-01 16:52:23 +0200476 # NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200477 group.delete()
478
479 @classmethod
480 def destroy_volume_wait(cls, volume):
481 """Delete volume, tryies to detach first.
482 Use just for teardown!
483 """
484 exc_num = 0
485 snaps = volume.snapshots()
486 if len(snaps):
487 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100488 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200489
Attila Fazekas3e381f72013-08-01 16:52:23 +0200490 # NOTE(afazekas): detaching/attching not valid EC2 status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200491 def _volume_state():
492 volume.update(validate=True)
493 try:
494 if volume.status != "available":
495 volume.detach(force=True)
496 except BaseException as exc:
497 LOG.exception(exc)
Attila Fazekas3e381f72013-08-01 16:52:23 +0200498 # exc_num += 1 "nonlocal" not in python2
Attila Fazekasa23f5002012-10-23 19:32:45 +0200499 return volume.status
500
501 try:
502 re_search_wait(_volume_state, "available") # not validates status
503 LOG.info(_volume_state())
504 volume.delete()
505 except BaseException as exc:
506 LOG.exception(exc)
507 exc_num += 1
508 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100509 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200510
511 @classmethod
512 def destroy_snapshot_wait(cls, snapshot):
Sean Daguef237ccb2013-01-04 15:19:14 -0500513 """delete snaphot, wait until not exists."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200514 snapshot.delete()
515
516 def _update():
517 snapshot.update(validate=True)
518
519 wait_exception(_update)
520
521
522# you can specify tuples if you want to specify the status pattern
523for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
524 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
525 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
526 'Gateway.NotAttached', 'IdempotentParameterMismatch',
527 'IncorrectInstanceState', 'IncorrectState',
528 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
529 'InsufficientReservedInstancesCapacity',
530 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
531 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
532 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
533 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
534 'InvalidCustomerGateway.DuplicateIpAddress',
535 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
536 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
537 'InvalidFilter', 'InvalidGatewayID.NotFound',
538 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
539 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
540 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
541 'InvalidInstanceID.NotFound',
542 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
543 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
544 'InvalidKeyPair.NotFound', 'InvalidManifest',
545 'InvalidNetworkAclEntry.NotFound',
546 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
547 'InvalidParameterValue', 'InvalidPermission.Duplicate',
548 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
549 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
550 'InvalidRouteTableID.NotFound',
551 'InvalidSecurity.RequestHasExpired',
552 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
553 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
554 'InvalidReservedInstancesOfferingId',
555 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
556 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
557 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
558 'InvalidVpnConnectionID.NotFound',
559 'InvalidVpnGatewayID.NotFound',
560 'InvalidZone.NotFound', 'LegacySecurityGroup',
561 'MissingParameter', 'NetworkAclEntryAlreadyExists',
562 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
563 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
564 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
565 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
566 'ResourceLimitExceeded', 'RouteAlreadyExists',
567 'RouteLimitExceeded', 'RouteTableLimitExceeded',
568 'RulesPerSecurityGroupLimitExceeded',
569 'SecurityGroupLimitExceeded',
570 'SecurityGroupsPerInstanceLimitExceeded',
571 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
572 'UnknownParameter', 'UnsupportedOperation',
573 'VolumeLimitExceeded', 'VpcLimitExceeded',
574 'VpnConnectionLimitExceeded',
575 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
576 _add_matcher_class(BotoTestCase.ec2_error_code.client,
577 code, base=ClientError)
578
579for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
580 'InsufficientReservedInstanceCapacity', 'InternalError',
581 'Unavailable'):
582 _add_matcher_class(BotoTestCase.ec2_error_code.server,
583 code, base=ServerError)
584
585
586for code in (('AccessDenied', 403),
587 ('AccountProblem', 403),
588 ('AmbiguousGrantByEmailAddress', 400),
589 ('BadDigest', 400),
590 ('BucketAlreadyExists', 409),
591 ('BucketAlreadyOwnedByYou', 409),
592 ('BucketNotEmpty', 409),
593 ('CredentialsNotSupported', 400),
594 ('CrossLocationLoggingProhibited', 403),
595 ('EntityTooSmall', 400),
596 ('EntityTooLarge', 400),
597 ('ExpiredToken', 400),
598 ('IllegalVersioningConfigurationException', 400),
599 ('IncompleteBody', 400),
600 ('IncorrectNumberOfFilesInPostRequest', 400),
601 ('InlineDataTooLarge', 400),
602 ('InvalidAccessKeyId', 403),
603 'InvalidAddressingHeader',
604 ('InvalidArgument', 400),
605 ('InvalidBucketName', 400),
606 ('InvalidBucketState', 409),
607 ('InvalidDigest', 400),
608 ('InvalidLocationConstraint', 400),
609 ('InvalidPart', 400),
610 ('InvalidPartOrder', 400),
611 ('InvalidPayer', 403),
612 ('InvalidPolicyDocument', 400),
613 ('InvalidRange', 416),
614 ('InvalidRequest', 400),
615 ('InvalidSecurity', 403),
616 ('InvalidSOAPRequest', 400),
617 ('InvalidStorageClass', 400),
618 ('InvalidTargetBucketForLogging', 400),
619 ('InvalidToken', 400),
620 ('InvalidURI', 400),
621 ('KeyTooLong', 400),
622 ('MalformedACLError', 400),
623 ('MalformedPOSTRequest', 400),
624 ('MalformedXML', 400),
625 ('MaxMessageLengthExceeded', 400),
626 ('MaxPostPreDataLengthExceededError', 400),
627 ('MetadataTooLarge', 400),
628 ('MethodNotAllowed', 405),
629 ('MissingAttachment'),
630 ('MissingContentLength', 411),
631 ('MissingRequestBodyError', 400),
632 ('MissingSecurityElement', 400),
633 ('MissingSecurityHeader', 400),
634 ('NoLoggingStatusForKey', 400),
635 ('NoSuchBucket', 404),
636 ('NoSuchKey', 404),
637 ('NoSuchLifecycleConfiguration', 404),
638 ('NoSuchUpload', 404),
639 ('NoSuchVersion', 404),
640 ('NotSignedUp', 403),
641 ('NotSuchBucketPolicy', 404),
642 ('OperationAborted', 409),
643 ('PermanentRedirect', 301),
644 ('PreconditionFailed', 412),
645 ('Redirect', 307),
646 ('RequestIsNotMultiPartContent', 400),
647 ('RequestTimeout', 400),
648 ('RequestTimeTooSkewed', 403),
649 ('RequestTorrentOfBucketError', 400),
650 ('SignatureDoesNotMatch', 403),
651 ('TemporaryRedirect', 307),
652 ('TokenRefreshRequired', 400),
653 ('TooManyBuckets', 400),
654 ('UnexpectedContent', 400),
655 ('UnresolvableGrantByEmailAddress', 400),
656 ('UserKeyMustBeSpecified', 400)):
657 _add_matcher_class(BotoTestCase.s3_error_code.client,
658 code, base=ClientError)
659
660
661for code in (('InternalError', 500),
662 ('NotImplemented', 501),
663 ('ServiceUnavailable', 503),
664 ('SlowDown', 503)):
665 _add_matcher_class(BotoTestCase.s3_error_code.server,
666 code, base=ServerError)