blob: 9ff628cb805a3550e3471b7407789126f84c62fc [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
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090031from tempest.common import log as logging
Attila Fazekas234d3e82013-02-22 16:39:49 +010032from tempest.common.utils.file_utils import have_effective_read_access
33import tempest.config
Attila Fazekas40aa3612013-01-19 22:16:38 +010034from tempest import exceptions
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')
61 level = boto_logger.level
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090062 boto_logger.setLevel(orig_logging.CRITICAL) # suppress logging for these
Attila Fazekas234d3e82013-02-22 16:39:49 +010063
64 def _cred_sub_check(connection_data):
65 if not id_matcher.match(connection_data["aws_access_key_id"]):
66 raise Exception("Invalid AWS access Key")
67 if not secret_matcher.match(connection_data["aws_secret_access_key"]):
68 raise Exception("Invalid AWS secret Key")
69 raise Exception("Unknown (Authentication?) Error")
70 openstack = tempest.clients.Manager()
71 try:
72 if urlparse.urlparse(config.boto.ec2_url).hostname is None:
73 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
82 except keystoneclient.exceptions.Unauthorized:
83 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
84 " faild to get them even by keystoneclient"
85 except Exception as exc:
86 EC2_CAN_CONNECT_ERROR = str(exc)
87
88 try:
89 if urlparse.urlparse(config.boto.s3_url).hostname is None:
90 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)
99 except keystoneclient.exceptions.Unauthorized:
100 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
101 " faild to get them even by keystoneclient"
102 boto_logger.setLevel(level)
103 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):
Attila Fazekas40aa3612013-01-19 22:16:38 +0100113 if not isinstance(exc, exception.BotoServerError):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200114 return "%r not an BotoServerError instance" % exc
115 LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
116 if re.match(self.STATUS_RE, str(exc.status)) is None:
117 return ("Status code (%s) does not match"
118 "the expected re pattern \"%s\""
119 % (exc.status, self.STATUS_RE))
120 if re.match(self.CODE_RE, str(exc.error_code)) is None:
121 return ("Error code (%s) does not match" +
122 "the expected re pattern \"%s\"") %\
123 (exc.error_code, self.CODE_RE)
124
125
126class ClientError(BotoExceptionMatcher):
127 STATUS_RE = r'4\d\d'
128
129
130class ServerError(BotoExceptionMatcher):
131 STATUS_RE = r'5\d\d'
132
133
134def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
135 """
136 Usable for adding an ExceptionMatcher(s) into the exception tree.
137 The not leaf elements does wildcard match
138 """
139 # in error_code just literal and '.' characters expected
140 if not isinstance(error_data, basestring):
141 (error_code, status_code) = map(str, error_data)
142 else:
143 status_code = None
144 error_code = error_data
145 parts = error_code.split('.')
146 basematch = ""
147 num_parts = len(parts)
148 max_index = num_parts - 1
149 add_cls = error_cls
150 for i_part in xrange(num_parts):
151 part = parts[i_part]
152 leaf = i_part == max_index
153 if not leaf:
154 match = basematch + part + "[.].*"
155 else:
156 match = basematch + part
157
158 basematch += part + "[.]"
159 if not hasattr(add_cls, part):
160 cls_dict = {"CODE_RE": match}
161 if leaf and status_code is not None:
162 cls_dict["STATUS_RE"] = status_code
163 cls = type(part, (base, ), cls_dict)
164 setattr(add_cls, part, cls())
165 add_cls = cls
166 elif leaf:
167 raise LookupError("Tries to redefine an error code \"%s\"" % part)
168 else:
169 add_cls = getattr(add_cls, part)
170
171
172#TODO(afazekas): classmethod handling
173def friendly_function_name_simple(call_able):
174 name = ""
175 if hasattr(call_able, "im_class"):
176 name += call_able.im_class.__name__ + "."
177 name += call_able.__name__
178 return name
179
180
181def friendly_function_call_str(call_able, *args, **kwargs):
182 string = friendly_function_name_simple(call_able)
183 string += "(" + ", ".join(map(str, args))
184 if len(kwargs):
185 if len(args):
186 string += ", "
187 string += ", ".join("=".join(map(str, (key, value)))
188 for (key, value) in kwargs.items())
189 return string + ")"
190
191
Attila Fazekasdc216422013-01-29 15:12:14 +0100192class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500193 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030194
Attila Fazekas234d3e82013-02-22 16:39:49 +0100195 conclusion = decision_maker()
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030196
Attila Fazekasa23f5002012-10-23 19:32:45 +0200197 @classmethod
198 def setUpClass(cls):
199 # The trash contains cleanup functions and paramaters in tuples
200 # (function, *args, **kwargs)
201 cls._resource_trash_bin = {}
202 cls._sequence = -1
203 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100204 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800205 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100206 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200207 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100208 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800209 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100210 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200211
212 @classmethod
213 def addResourceCleanUp(cls, function, *args, **kwargs):
214 """Adds CleanUp callable, used by tearDownClass.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100215 Recommended to a use (deep)copy on the mutable args.
216 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200217 cls._sequence = cls._sequence + 1
218 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
219 return cls._sequence
220
221 @classmethod
222 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500223 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200224 del cls._resource_trash_bin[key]
225
226 #TODO(afazekas): Add "with" context handling
227 def assertBotoError(self, excMatcher, callableObj,
228 *args, **kwargs):
229 """Example usage:
230 self.assertBotoError(self.ec2_error_code.client.
231 InvalidKeyPair.Duplicate,
232 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100233 key_name)
234 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200235 try:
236 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100237 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200238 error_msg = excMatcher.match(exc)
239 if error_msg is not None:
240 raise self.failureException, error_msg
241 else:
242 raise self.failureException, "BotoServerError not raised"
243
244 @classmethod
245 def tearDownClass(cls):
Attila Fazekasb2902af2013-02-16 16:22:44 +0100246 """Calls the callables added by addResourceCleanUp,
247 when you overwire this function dont't forget to call this too.
248 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200249 fail_count = 0
250 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
251 for key in trash_keys:
252 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
253 try:
254 LOG.debug("Cleaning up: %s" %
255 friendly_function_call_str(function, *pos_args,
256 **kw_args))
257 function(*pos_args, **kw_args)
258 except BaseException as exc:
259 fail_count += 1
260 LOG.exception(exc)
261 finally:
262 del cls._resource_trash_bin[key]
263 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100264 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200265
266 ec2_error_code = BotoExceptionMatcher()
267 # InsufficientInstanceCapacity can be both server and client error
268 ec2_error_code.server = ServerError()
269 ec2_error_code.client = ClientError()
270 s3_error_code = BotoExceptionMatcher()
271 s3_error_code.server = ServerError()
272 s3_error_code.client = ClientError()
273 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekasc66ee652013-01-31 06:56:13 +0100274 #NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
275 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200276 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100277 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200278 valid_volume_status = set(('creating', 'available', 'in-use',
279 'deleting', 'deleted', 'error'))
280 valid_snapshot_status = set(('pending', 'completed', 'error'))
281
Attila Fazekas37f83042013-01-12 16:13:03 +0100282 gone_set = set(('_GONE',))
283
Attila Fazekas40aa3612013-01-19 22:16:38 +0100284 @classmethod
285 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400286 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100287 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400288 is the function.
289 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100290 ec = cls.ec2_error_code
291 if isinstance(obj, ec2.instance.Instance):
292 colusure_matcher = ec.client.InvalidInstanceID.NotFound
293 status_attr = "state"
294 elif isinstance(obj, ec2.image.Image):
295 colusure_matcher = ec.client.InvalidAMIID.NotFound
296 status_attr = "state"
297 elif isinstance(obj, ec2.snapshot.Snapshot):
298 colusure_matcher = ec.client.InvalidSnapshot.NotFound
299 status_attr = "status"
300 elif isinstance(obj, ec2.volume.Volume):
301 colusure_matcher = ec.client.InvalidVolume.NotFound
302 status_attr = "status"
303 else:
304 return obj
305
306 def _status():
307 try:
308 obj.update(validate=True)
309 except ValueError:
310 return "_GONE"
311 except exception.EC2ResponseError as exc:
312 if colusure_matcher.match(exc):
313 return "_GONE"
314 else:
315 raise
316 return getattr(obj, status_attr)
317
318 return _status
319
Attila Fazekas37f83042013-01-12 16:13:03 +0100320 def state_wait_gone(self, lfunction, final_set, valid_set):
321 if not isinstance(final_set, set):
322 final_set = set((final_set,))
323 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100324 lfunction = self.get_lfunction_gone(lfunction)
Attila Fazekas37f83042013-01-12 16:13:03 +0100325 state = state_wait(lfunction, final_set, valid_set)
326 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200327 return state
328
Attila Fazekas37f83042013-01-12 16:13:03 +0100329 def waitImageState(self, lfunction, wait_for):
330 return self.state_wait_gone(lfunction, wait_for,
331 self.valid_image_state)
332
Attila Fazekasa23f5002012-10-23 19:32:45 +0200333 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100334 return self.state_wait_gone(lfunction, wait_for,
335 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200336
Attila Fazekasa23f5002012-10-23 19:32:45 +0200337 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100338 return self.state_wait_gone(lfunction, wait_for,
339 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200340
Attila Fazekas40aa3612013-01-19 22:16:38 +0100341 def waitVolumeStatus(self, lfunction, wait_for):
342 return self.state_wait_gone(lfunction, wait_for,
343 self.valid_volume_status)
344
Attila Fazekasa23f5002012-10-23 19:32:45 +0200345 def assertImageStateWait(self, lfunction, wait_for):
346 state = self.waitImageState(lfunction, wait_for)
347 self.assertIn(state, wait_for)
348
349 def assertInstanceStateWait(self, lfunction, wait_for):
350 state = self.waitInstanceState(lfunction, wait_for)
351 self.assertIn(state, wait_for)
352
353 def assertVolumeStatusWait(self, lfunction, wait_for):
354 state = self.waitVolumeStatus(lfunction, wait_for)
355 self.assertIn(state, wait_for)
356
357 def assertSnapshotStatusWait(self, lfunction, wait_for):
358 state = self.waitSnapshotStatus(lfunction, wait_for)
359 self.assertIn(state, wait_for)
360
361 def assertAddressDissasociatedWait(self, address):
362
363 def _disassociate():
364 cli = self.ec2_client
365 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
366 if len(addresses) != 1:
367 return "INVALID"
368 if addresses[0].instance_id:
369 LOG.info("%s associated to %s",
370 address.public_ip,
371 addresses[0].instance_id)
372 return "ASSOCIATED"
373 return "DISASSOCIATED"
374
375 state = state_wait(_disassociate, "DISASSOCIATED",
376 set(("ASSOCIATED", "DISASSOCIATED")))
377 self.assertEqual(state, "DISASSOCIATED")
378
379 def assertAddressReleasedWait(self, address):
380
381 def _address_delete():
382 #NOTE(afazekas): the filter gives back IP
383 # even if it is not associated to my tenant
384 if (address.public_ip not in map(lambda a: a.public_ip,
385 self.ec2_client.get_all_addresses())):
386 return "DELETED"
387 return "NOTDELETED"
388
389 state = state_wait(_address_delete, "DELETED")
390 self.assertEqual(state, "DELETED")
391
392 def assertReSearch(self, regexp, string):
393 if re.search(regexp, string) is None:
394 raise self.failureException("regexp: '%s' not found in '%s'" %
395 (regexp, string))
396
397 def assertNotReSearch(self, regexp, string):
398 if re.search(regexp, string) is not None:
399 raise self.failureException("regexp: '%s' found in '%s'" %
400 (regexp, string))
401
402 def assertReMatch(self, regexp, string):
403 if re.match(regexp, string) is None:
404 raise self.failureException("regexp: '%s' not matches on '%s'" %
405 (regexp, string))
406
407 def assertNotReMatch(self, regexp, string):
408 if re.match(regexp, string) is not None:
409 raise self.failureException("regexp: '%s' matches on '%s'" %
410 (regexp, string))
411
412 @classmethod
413 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500414 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200415 exc_num = 0
416 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700417 with contextlib.closing(
418 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200419 if isinstance(bucket, basestring):
420 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100421 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200422 for obj in bucket.list():
423 try:
424 bucket.delete_key(obj.key)
425 obj.close()
426 except BaseException as exc:
427 LOG.exception(exc)
428 exc_num += 1
429 conn.delete_bucket(bucket)
430 except BaseException as exc:
431 LOG.exception(exc)
432 exc_num += 1
433 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100434 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200435
436 @classmethod
437 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500438 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200439 exc_num = 0
440
441 def _instance_state():
442 try:
443 instance.update(validate=True)
444 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100445 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100446 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100447 if cls.ec2_error_code.\
Sean Dague14c68182013-04-14 15:34:30 -0400448 client.InvalidInstanceID.NotFound.match(exc):
Attila Fazekas37f83042013-01-12 16:13:03 +0100449 return "_GONE"
450 #NOTE(afazekas): incorrect code,
451 # but the resource must be destoreyd
452 if exc.error_code == "InstanceNotFound":
453 return "_GONE"
454
Attila Fazekasa23f5002012-10-23 19:32:45 +0200455 return instance.state
456
457 for instance in reservation.instances:
458 try:
459 instance.terminate()
Attila Fazekas37f83042013-01-12 16:13:03 +0100460 re_search_wait(_instance_state, "_GONE")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200461 except BaseException as exc:
462 LOG.exception(exc)
463 exc_num += 1
464 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100465 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200466
467 #NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
468 # to write better teardown
469
470 @classmethod
471 def destroy_security_group_wait(cls, group):
472 """Delete group.
473 Use just for teardown!
474 """
475 #NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200476 group.delete()
477
478 @classmethod
479 def destroy_volume_wait(cls, volume):
480 """Delete volume, tryies to detach first.
481 Use just for teardown!
482 """
483 exc_num = 0
484 snaps = volume.snapshots()
485 if len(snaps):
486 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100487 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200488
489 #Note(afazekas): detaching/attching not valid EC2 status
490 def _volume_state():
491 volume.update(validate=True)
492 try:
493 if volume.status != "available":
494 volume.detach(force=True)
495 except BaseException as exc:
496 LOG.exception(exc)
497 #exc_num += 1 "nonlocal" not in python2
498 return volume.status
499
500 try:
501 re_search_wait(_volume_state, "available") # not validates status
502 LOG.info(_volume_state())
503 volume.delete()
504 except BaseException as exc:
505 LOG.exception(exc)
506 exc_num += 1
507 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100508 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200509
510 @classmethod
511 def destroy_snapshot_wait(cls, snapshot):
Sean Daguef237ccb2013-01-04 15:19:14 -0500512 """delete snaphot, wait until not exists."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200513 snapshot.delete()
514
515 def _update():
516 snapshot.update(validate=True)
517
518 wait_exception(_update)
519
520
521# you can specify tuples if you want to specify the status pattern
522for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
523 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
524 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
525 'Gateway.NotAttached', 'IdempotentParameterMismatch',
526 'IncorrectInstanceState', 'IncorrectState',
527 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
528 'InsufficientReservedInstancesCapacity',
529 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
530 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
531 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
532 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
533 'InvalidCustomerGateway.DuplicateIpAddress',
534 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
535 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
536 'InvalidFilter', 'InvalidGatewayID.NotFound',
537 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
538 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
539 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
540 'InvalidInstanceID.NotFound',
541 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
542 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
543 'InvalidKeyPair.NotFound', 'InvalidManifest',
544 'InvalidNetworkAclEntry.NotFound',
545 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
546 'InvalidParameterValue', 'InvalidPermission.Duplicate',
547 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
548 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
549 'InvalidRouteTableID.NotFound',
550 'InvalidSecurity.RequestHasExpired',
551 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
552 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
553 'InvalidReservedInstancesOfferingId',
554 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
555 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
556 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
557 'InvalidVpnConnectionID.NotFound',
558 'InvalidVpnGatewayID.NotFound',
559 'InvalidZone.NotFound', 'LegacySecurityGroup',
560 'MissingParameter', 'NetworkAclEntryAlreadyExists',
561 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
562 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
563 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
564 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
565 'ResourceLimitExceeded', 'RouteAlreadyExists',
566 'RouteLimitExceeded', 'RouteTableLimitExceeded',
567 'RulesPerSecurityGroupLimitExceeded',
568 'SecurityGroupLimitExceeded',
569 'SecurityGroupsPerInstanceLimitExceeded',
570 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
571 'UnknownParameter', 'UnsupportedOperation',
572 'VolumeLimitExceeded', 'VpcLimitExceeded',
573 'VpnConnectionLimitExceeded',
574 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
575 _add_matcher_class(BotoTestCase.ec2_error_code.client,
576 code, base=ClientError)
577
578for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
579 'InsufficientReservedInstanceCapacity', 'InternalError',
580 'Unavailable'):
581 _add_matcher_class(BotoTestCase.ec2_error_code.server,
582 code, base=ServerError)
583
584
585for code in (('AccessDenied', 403),
586 ('AccountProblem', 403),
587 ('AmbiguousGrantByEmailAddress', 400),
588 ('BadDigest', 400),
589 ('BucketAlreadyExists', 409),
590 ('BucketAlreadyOwnedByYou', 409),
591 ('BucketNotEmpty', 409),
592 ('CredentialsNotSupported', 400),
593 ('CrossLocationLoggingProhibited', 403),
594 ('EntityTooSmall', 400),
595 ('EntityTooLarge', 400),
596 ('ExpiredToken', 400),
597 ('IllegalVersioningConfigurationException', 400),
598 ('IncompleteBody', 400),
599 ('IncorrectNumberOfFilesInPostRequest', 400),
600 ('InlineDataTooLarge', 400),
601 ('InvalidAccessKeyId', 403),
602 'InvalidAddressingHeader',
603 ('InvalidArgument', 400),
604 ('InvalidBucketName', 400),
605 ('InvalidBucketState', 409),
606 ('InvalidDigest', 400),
607 ('InvalidLocationConstraint', 400),
608 ('InvalidPart', 400),
609 ('InvalidPartOrder', 400),
610 ('InvalidPayer', 403),
611 ('InvalidPolicyDocument', 400),
612 ('InvalidRange', 416),
613 ('InvalidRequest', 400),
614 ('InvalidSecurity', 403),
615 ('InvalidSOAPRequest', 400),
616 ('InvalidStorageClass', 400),
617 ('InvalidTargetBucketForLogging', 400),
618 ('InvalidToken', 400),
619 ('InvalidURI', 400),
620 ('KeyTooLong', 400),
621 ('MalformedACLError', 400),
622 ('MalformedPOSTRequest', 400),
623 ('MalformedXML', 400),
624 ('MaxMessageLengthExceeded', 400),
625 ('MaxPostPreDataLengthExceededError', 400),
626 ('MetadataTooLarge', 400),
627 ('MethodNotAllowed', 405),
628 ('MissingAttachment'),
629 ('MissingContentLength', 411),
630 ('MissingRequestBodyError', 400),
631 ('MissingSecurityElement', 400),
632 ('MissingSecurityHeader', 400),
633 ('NoLoggingStatusForKey', 400),
634 ('NoSuchBucket', 404),
635 ('NoSuchKey', 404),
636 ('NoSuchLifecycleConfiguration', 404),
637 ('NoSuchUpload', 404),
638 ('NoSuchVersion', 404),
639 ('NotSignedUp', 403),
640 ('NotSuchBucketPolicy', 404),
641 ('OperationAborted', 409),
642 ('PermanentRedirect', 301),
643 ('PreconditionFailed', 412),
644 ('Redirect', 307),
645 ('RequestIsNotMultiPartContent', 400),
646 ('RequestTimeout', 400),
647 ('RequestTimeTooSkewed', 403),
648 ('RequestTorrentOfBucketError', 400),
649 ('SignatureDoesNotMatch', 403),
650 ('TemporaryRedirect', 307),
651 ('TokenRefreshRequired', 400),
652 ('TooManyBuckets', 400),
653 ('UnexpectedContent', 400),
654 ('UnresolvableGrantByEmailAddress', 400),
655 ('UserKeyMustBeSpecified', 400)):
656 _add_matcher_class(BotoTestCase.s3_error_code.client,
657 code, base=ClientError)
658
659
660for code in (('InternalError', 500),
661 ('NotImplemented', 501),
662 ('ServiceUnavailable', 503),
663 ('SlowDown', 503)):
664 _add_matcher_class(BotoTestCase.s3_error_code.server,
665 code, base=ServerError)