blob: 9e652cb843f64dba8b1c6c4e77040ac2cd2c226d [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
Attila Fazekasa23f5002012-10-23 19:32:45 +020019import 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
Attila Fazekasdc216422013-01-29 15:12:14 +010034import tempest.test
Matthew Treinisha83a16e2012-12-07 13:44:02 -050035from tempest.tests.boto.utils.wait import re_search_wait
36from tempest.tests.boto.utils.wait import state_wait
37from tempest.tests.boto.utils.wait import wait_exception
Matthew Treinisha83a16e2012-12-07 13:44:02 -050038
Attila Fazekasa23f5002012-10-23 19:32:45 +020039LOG = logging.getLogger(__name__)
40
41
Attila Fazekas234d3e82013-02-22 16:39:49 +010042def decision_maker():
43 A_I_IMAGES_READY = True # ari,ami,aki
44 S3_CAN_CONNECT_ERROR = None
45 EC2_CAN_CONNECT_ERROR = None
46 secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system
47 id_matcher = re.compile("[A-Za-z0-9]{20,}")
48
49 def all_read(*args):
50 return all(map(have_effective_read_access, args))
51
52 config = tempest.config.TempestConfig()
53 materials_path = config.boto.s3_materials_path
54 ami_path = materials_path + os.sep + config.boto.ami_manifest
55 aki_path = materials_path + os.sep + config.boto.aki_manifest
56 ari_path = materials_path + os.sep + config.boto.ari_manifest
57
58 A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
59 boto_logger = logging.getLogger('boto')
60 level = boto_logger.level
61 boto_logger.setLevel(logging.CRITICAL) # suppress logging for these
62
63 def _cred_sub_check(connection_data):
64 if not id_matcher.match(connection_data["aws_access_key_id"]):
65 raise Exception("Invalid AWS access Key")
66 if not secret_matcher.match(connection_data["aws_secret_access_key"]):
67 raise Exception("Invalid AWS secret Key")
68 raise Exception("Unknown (Authentication?) Error")
69 openstack = tempest.clients.Manager()
70 try:
71 if urlparse.urlparse(config.boto.ec2_url).hostname is None:
72 raise Exception("Failed to get hostname from the ec2_url")
73 ec2client = openstack.ec2api_client
74 try:
75 ec2client.get_all_regions()
76 except exception.BotoServerError as exc:
77 if exc.error_code is None:
78 raise Exception("EC2 target does not looks EC2 service")
79 _cred_sub_check(ec2client.connection_data)
80
81 except keystoneclient.exceptions.Unauthorized:
82 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
83 " faild to get them even by keystoneclient"
84 except Exception as exc:
85 EC2_CAN_CONNECT_ERROR = str(exc)
86
87 try:
88 if urlparse.urlparse(config.boto.s3_url).hostname is None:
89 raise Exception("Failed to get hostname from the s3_url")
90 s3client = openstack.s3_client
91 try:
92 s3client.get_bucket("^INVALID*#()@INVALID.")
93 except exception.BotoServerError as exc:
94 if exc.status == 403:
95 _cred_sub_check(s3client.connection_data)
96 except Exception as exc:
97 S3_CAN_CONNECT_ERROR = str(exc)
98 except keystoneclient.exceptions.Unauthorized:
99 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
100 " faild to get them even by keystoneclient"
101 boto_logger.setLevel(level)
102 return {'A_I_IMAGES_READY': A_I_IMAGES_READY,
103 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR,
104 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR}
105
106
Attila Fazekasa23f5002012-10-23 19:32:45 +0200107class BotoExceptionMatcher(object):
108 STATUS_RE = r'[45]\d\d'
109 CODE_RE = '.*' # regexp makes sense in group match
110
111 def match(self, exc):
Attila Fazekas40aa3612013-01-19 22:16:38 +0100112 if not isinstance(exc, exception.BotoServerError):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200113 return "%r not an BotoServerError instance" % exc
114 LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
115 if re.match(self.STATUS_RE, str(exc.status)) is None:
116 return ("Status code (%s) does not match"
117 "the expected re pattern \"%s\""
118 % (exc.status, self.STATUS_RE))
119 if re.match(self.CODE_RE, str(exc.error_code)) is None:
120 return ("Error code (%s) does not match" +
121 "the expected re pattern \"%s\"") %\
122 (exc.error_code, self.CODE_RE)
123
124
125class ClientError(BotoExceptionMatcher):
126 STATUS_RE = r'4\d\d'
127
128
129class ServerError(BotoExceptionMatcher):
130 STATUS_RE = r'5\d\d'
131
132
133def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
134 """
135 Usable for adding an ExceptionMatcher(s) into the exception tree.
136 The not leaf elements does wildcard match
137 """
138 # in error_code just literal and '.' characters expected
139 if not isinstance(error_data, basestring):
140 (error_code, status_code) = map(str, error_data)
141 else:
142 status_code = None
143 error_code = error_data
144 parts = error_code.split('.')
145 basematch = ""
146 num_parts = len(parts)
147 max_index = num_parts - 1
148 add_cls = error_cls
149 for i_part in xrange(num_parts):
150 part = parts[i_part]
151 leaf = i_part == max_index
152 if not leaf:
153 match = basematch + part + "[.].*"
154 else:
155 match = basematch + part
156
157 basematch += part + "[.]"
158 if not hasattr(add_cls, part):
159 cls_dict = {"CODE_RE": match}
160 if leaf and status_code is not None:
161 cls_dict["STATUS_RE"] = status_code
162 cls = type(part, (base, ), cls_dict)
163 setattr(add_cls, part, cls())
164 add_cls = cls
165 elif leaf:
166 raise LookupError("Tries to redefine an error code \"%s\"" % part)
167 else:
168 add_cls = getattr(add_cls, part)
169
170
171#TODO(afazekas): classmethod handling
172def friendly_function_name_simple(call_able):
173 name = ""
174 if hasattr(call_able, "im_class"):
175 name += call_able.im_class.__name__ + "."
176 name += call_able.__name__
177 return name
178
179
180def friendly_function_call_str(call_able, *args, **kwargs):
181 string = friendly_function_name_simple(call_able)
182 string += "(" + ", ".join(map(str, args))
183 if len(kwargs):
184 if len(args):
185 string += ", "
186 string += ", ".join("=".join(map(str, (key, value)))
187 for (key, value) in kwargs.items())
188 return string + ")"
189
190
Attila Fazekasdc216422013-01-29 15:12:14 +0100191class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500192 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030193
Attila Fazekas234d3e82013-02-22 16:39:49 +0100194 conclusion = decision_maker()
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030195
Attila Fazekasa23f5002012-10-23 19:32:45 +0200196 @classmethod
197 def setUpClass(cls):
198 # The trash contains cleanup functions and paramaters in tuples
199 # (function, *args, **kwargs)
200 cls._resource_trash_bin = {}
201 cls._sequence = -1
202 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100203 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800204 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100205 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200206 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100207 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800208 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100209 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200210
211 @classmethod
212 def addResourceCleanUp(cls, function, *args, **kwargs):
213 """Adds CleanUp callable, used by tearDownClass.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100214 Recommended to a use (deep)copy on the mutable args.
215 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200216 cls._sequence = cls._sequence + 1
217 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
218 return cls._sequence
219
220 @classmethod
221 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500222 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200223 del cls._resource_trash_bin[key]
224
225 #TODO(afazekas): Add "with" context handling
226 def assertBotoError(self, excMatcher, callableObj,
227 *args, **kwargs):
228 """Example usage:
229 self.assertBotoError(self.ec2_error_code.client.
230 InvalidKeyPair.Duplicate,
231 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100232 key_name)
233 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200234 try:
235 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100236 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200237 error_msg = excMatcher.match(exc)
238 if error_msg is not None:
239 raise self.failureException, error_msg
240 else:
241 raise self.failureException, "BotoServerError not raised"
242
243 @classmethod
244 def tearDownClass(cls):
Attila Fazekasb2902af2013-02-16 16:22:44 +0100245 """Calls the callables added by addResourceCleanUp,
246 when you overwire this function dont't forget to call this too.
247 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200248 fail_count = 0
249 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
250 for key in trash_keys:
251 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
252 try:
253 LOG.debug("Cleaning up: %s" %
254 friendly_function_call_str(function, *pos_args,
255 **kw_args))
256 function(*pos_args, **kw_args)
257 except BaseException as exc:
258 fail_count += 1
259 LOG.exception(exc)
260 finally:
261 del cls._resource_trash_bin[key]
262 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100263 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200264
265 ec2_error_code = BotoExceptionMatcher()
266 # InsufficientInstanceCapacity can be both server and client error
267 ec2_error_code.server = ServerError()
268 ec2_error_code.client = ClientError()
269 s3_error_code = BotoExceptionMatcher()
270 s3_error_code.server = ServerError()
271 s3_error_code.client = ClientError()
272 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekasc66ee652013-01-31 06:56:13 +0100273 #NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
274 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200275 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100276 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200277 valid_volume_status = set(('creating', 'available', 'in-use',
278 'deleting', 'deleted', 'error'))
279 valid_snapshot_status = set(('pending', 'completed', 'error'))
280
Attila Fazekas37f83042013-01-12 16:13:03 +0100281 gone_set = set(('_GONE',))
282
Attila Fazekas40aa3612013-01-19 22:16:38 +0100283 @classmethod
284 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400285 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100286 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400287 is the function.
288 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100289 ec = cls.ec2_error_code
290 if isinstance(obj, ec2.instance.Instance):
291 colusure_matcher = ec.client.InvalidInstanceID.NotFound
292 status_attr = "state"
293 elif isinstance(obj, ec2.image.Image):
294 colusure_matcher = ec.client.InvalidAMIID.NotFound
295 status_attr = "state"
296 elif isinstance(obj, ec2.snapshot.Snapshot):
297 colusure_matcher = ec.client.InvalidSnapshot.NotFound
298 status_attr = "status"
299 elif isinstance(obj, ec2.volume.Volume):
300 colusure_matcher = ec.client.InvalidVolume.NotFound
301 status_attr = "status"
302 else:
303 return obj
304
305 def _status():
306 try:
307 obj.update(validate=True)
308 except ValueError:
309 return "_GONE"
310 except exception.EC2ResponseError as exc:
311 if colusure_matcher.match(exc):
312 return "_GONE"
313 else:
314 raise
315 return getattr(obj, status_attr)
316
317 return _status
318
Attila Fazekas37f83042013-01-12 16:13:03 +0100319 def state_wait_gone(self, lfunction, final_set, valid_set):
320 if not isinstance(final_set, set):
321 final_set = set((final_set,))
322 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100323 lfunction = self.get_lfunction_gone(lfunction)
Attila Fazekas37f83042013-01-12 16:13:03 +0100324 state = state_wait(lfunction, final_set, valid_set)
325 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200326 return state
327
Attila Fazekas37f83042013-01-12 16:13:03 +0100328 def waitImageState(self, lfunction, wait_for):
329 return self.state_wait_gone(lfunction, wait_for,
330 self.valid_image_state)
331
Attila Fazekasa23f5002012-10-23 19:32:45 +0200332 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100333 return self.state_wait_gone(lfunction, wait_for,
334 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200335
Attila Fazekasa23f5002012-10-23 19:32:45 +0200336 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100337 return self.state_wait_gone(lfunction, wait_for,
338 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200339
Attila Fazekas40aa3612013-01-19 22:16:38 +0100340 def waitVolumeStatus(self, lfunction, wait_for):
341 return self.state_wait_gone(lfunction, wait_for,
342 self.valid_volume_status)
343
Attila Fazekasa23f5002012-10-23 19:32:45 +0200344 def assertImageStateWait(self, lfunction, wait_for):
345 state = self.waitImageState(lfunction, wait_for)
346 self.assertIn(state, wait_for)
347
348 def assertInstanceStateWait(self, lfunction, wait_for):
349 state = self.waitInstanceState(lfunction, wait_for)
350 self.assertIn(state, wait_for)
351
352 def assertVolumeStatusWait(self, lfunction, wait_for):
353 state = self.waitVolumeStatus(lfunction, wait_for)
354 self.assertIn(state, wait_for)
355
356 def assertSnapshotStatusWait(self, lfunction, wait_for):
357 state = self.waitSnapshotStatus(lfunction, wait_for)
358 self.assertIn(state, wait_for)
359
360 def assertAddressDissasociatedWait(self, address):
361
362 def _disassociate():
363 cli = self.ec2_client
364 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
365 if len(addresses) != 1:
366 return "INVALID"
367 if addresses[0].instance_id:
368 LOG.info("%s associated to %s",
369 address.public_ip,
370 addresses[0].instance_id)
371 return "ASSOCIATED"
372 return "DISASSOCIATED"
373
374 state = state_wait(_disassociate, "DISASSOCIATED",
375 set(("ASSOCIATED", "DISASSOCIATED")))
376 self.assertEqual(state, "DISASSOCIATED")
377
378 def assertAddressReleasedWait(self, address):
379
380 def _address_delete():
381 #NOTE(afazekas): the filter gives back IP
382 # even if it is not associated to my tenant
383 if (address.public_ip not in map(lambda a: a.public_ip,
384 self.ec2_client.get_all_addresses())):
385 return "DELETED"
386 return "NOTDELETED"
387
388 state = state_wait(_address_delete, "DELETED")
389 self.assertEqual(state, "DELETED")
390
391 def assertReSearch(self, regexp, string):
392 if re.search(regexp, string) is None:
393 raise self.failureException("regexp: '%s' not found in '%s'" %
394 (regexp, string))
395
396 def assertNotReSearch(self, regexp, string):
397 if re.search(regexp, string) is not None:
398 raise self.failureException("regexp: '%s' found in '%s'" %
399 (regexp, string))
400
401 def assertReMatch(self, regexp, string):
402 if re.match(regexp, string) is None:
403 raise self.failureException("regexp: '%s' not matches on '%s'" %
404 (regexp, string))
405
406 def assertNotReMatch(self, regexp, string):
407 if re.match(regexp, string) is not None:
408 raise self.failureException("regexp: '%s' matches on '%s'" %
409 (regexp, string))
410
411 @classmethod
412 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500413 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200414 exc_num = 0
415 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700416 with contextlib.closing(
417 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200418 if isinstance(bucket, basestring):
419 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100420 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200421 for obj in bucket.list():
422 try:
423 bucket.delete_key(obj.key)
424 obj.close()
425 except BaseException as exc:
426 LOG.exception(exc)
427 exc_num += 1
428 conn.delete_bucket(bucket)
429 except BaseException as exc:
430 LOG.exception(exc)
431 exc_num += 1
432 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100433 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200434
435 @classmethod
436 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500437 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200438 exc_num = 0
439
440 def _instance_state():
441 try:
442 instance.update(validate=True)
443 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100444 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100445 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100446 if cls.ec2_error_code.\
Sean Dague14c68182013-04-14 15:34:30 -0400447 client.InvalidInstanceID.NotFound.match(exc):
Attila Fazekas37f83042013-01-12 16:13:03 +0100448 return "_GONE"
449 #NOTE(afazekas): incorrect code,
450 # but the resource must be destoreyd
451 if exc.error_code == "InstanceNotFound":
452 return "_GONE"
453
Attila Fazekasa23f5002012-10-23 19:32:45 +0200454 return instance.state
455
456 for instance in reservation.instances:
457 try:
458 instance.terminate()
Attila Fazekas37f83042013-01-12 16:13:03 +0100459 re_search_wait(_instance_state, "_GONE")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200460 except BaseException as exc:
461 LOG.exception(exc)
462 exc_num += 1
463 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100464 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200465
466 #NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
467 # to write better teardown
468
469 @classmethod
470 def destroy_security_group_wait(cls, group):
471 """Delete group.
472 Use just for teardown!
473 """
474 #NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200475 group.delete()
476
477 @classmethod
478 def destroy_volume_wait(cls, volume):
479 """Delete volume, tryies to detach first.
480 Use just for teardown!
481 """
482 exc_num = 0
483 snaps = volume.snapshots()
484 if len(snaps):
485 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100486 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200487
488 #Note(afazekas): detaching/attching not valid EC2 status
489 def _volume_state():
490 volume.update(validate=True)
491 try:
492 if volume.status != "available":
493 volume.detach(force=True)
494 except BaseException as exc:
495 LOG.exception(exc)
496 #exc_num += 1 "nonlocal" not in python2
497 return volume.status
498
499 try:
500 re_search_wait(_volume_state, "available") # not validates status
501 LOG.info(_volume_state())
502 volume.delete()
503 except BaseException as exc:
504 LOG.exception(exc)
505 exc_num += 1
506 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100507 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200508
509 @classmethod
510 def destroy_snapshot_wait(cls, snapshot):
Sean Daguef237ccb2013-01-04 15:19:14 -0500511 """delete snaphot, wait until not exists."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200512 snapshot.delete()
513
514 def _update():
515 snapshot.update(validate=True)
516
517 wait_exception(_update)
518
519
520# you can specify tuples if you want to specify the status pattern
521for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
522 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
523 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
524 'Gateway.NotAttached', 'IdempotentParameterMismatch',
525 'IncorrectInstanceState', 'IncorrectState',
526 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
527 'InsufficientReservedInstancesCapacity',
528 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
529 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
530 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
531 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
532 'InvalidCustomerGateway.DuplicateIpAddress',
533 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
534 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
535 'InvalidFilter', 'InvalidGatewayID.NotFound',
536 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
537 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
538 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
539 'InvalidInstanceID.NotFound',
540 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
541 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
542 'InvalidKeyPair.NotFound', 'InvalidManifest',
543 'InvalidNetworkAclEntry.NotFound',
544 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
545 'InvalidParameterValue', 'InvalidPermission.Duplicate',
546 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
547 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
548 'InvalidRouteTableID.NotFound',
549 'InvalidSecurity.RequestHasExpired',
550 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
551 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
552 'InvalidReservedInstancesOfferingId',
553 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
554 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
555 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
556 'InvalidVpnConnectionID.NotFound',
557 'InvalidVpnGatewayID.NotFound',
558 'InvalidZone.NotFound', 'LegacySecurityGroup',
559 'MissingParameter', 'NetworkAclEntryAlreadyExists',
560 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
561 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
562 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
563 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
564 'ResourceLimitExceeded', 'RouteAlreadyExists',
565 'RouteLimitExceeded', 'RouteTableLimitExceeded',
566 'RulesPerSecurityGroupLimitExceeded',
567 'SecurityGroupLimitExceeded',
568 'SecurityGroupsPerInstanceLimitExceeded',
569 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
570 'UnknownParameter', 'UnsupportedOperation',
571 'VolumeLimitExceeded', 'VpcLimitExceeded',
572 'VpnConnectionLimitExceeded',
573 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
574 _add_matcher_class(BotoTestCase.ec2_error_code.client,
575 code, base=ClientError)
576
577for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
578 'InsufficientReservedInstanceCapacity', 'InternalError',
579 'Unavailable'):
580 _add_matcher_class(BotoTestCase.ec2_error_code.server,
581 code, base=ServerError)
582
583
584for code in (('AccessDenied', 403),
585 ('AccountProblem', 403),
586 ('AmbiguousGrantByEmailAddress', 400),
587 ('BadDigest', 400),
588 ('BucketAlreadyExists', 409),
589 ('BucketAlreadyOwnedByYou', 409),
590 ('BucketNotEmpty', 409),
591 ('CredentialsNotSupported', 400),
592 ('CrossLocationLoggingProhibited', 403),
593 ('EntityTooSmall', 400),
594 ('EntityTooLarge', 400),
595 ('ExpiredToken', 400),
596 ('IllegalVersioningConfigurationException', 400),
597 ('IncompleteBody', 400),
598 ('IncorrectNumberOfFilesInPostRequest', 400),
599 ('InlineDataTooLarge', 400),
600 ('InvalidAccessKeyId', 403),
601 'InvalidAddressingHeader',
602 ('InvalidArgument', 400),
603 ('InvalidBucketName', 400),
604 ('InvalidBucketState', 409),
605 ('InvalidDigest', 400),
606 ('InvalidLocationConstraint', 400),
607 ('InvalidPart', 400),
608 ('InvalidPartOrder', 400),
609 ('InvalidPayer', 403),
610 ('InvalidPolicyDocument', 400),
611 ('InvalidRange', 416),
612 ('InvalidRequest', 400),
613 ('InvalidSecurity', 403),
614 ('InvalidSOAPRequest', 400),
615 ('InvalidStorageClass', 400),
616 ('InvalidTargetBucketForLogging', 400),
617 ('InvalidToken', 400),
618 ('InvalidURI', 400),
619 ('KeyTooLong', 400),
620 ('MalformedACLError', 400),
621 ('MalformedPOSTRequest', 400),
622 ('MalformedXML', 400),
623 ('MaxMessageLengthExceeded', 400),
624 ('MaxPostPreDataLengthExceededError', 400),
625 ('MetadataTooLarge', 400),
626 ('MethodNotAllowed', 405),
627 ('MissingAttachment'),
628 ('MissingContentLength', 411),
629 ('MissingRequestBodyError', 400),
630 ('MissingSecurityElement', 400),
631 ('MissingSecurityHeader', 400),
632 ('NoLoggingStatusForKey', 400),
633 ('NoSuchBucket', 404),
634 ('NoSuchKey', 404),
635 ('NoSuchLifecycleConfiguration', 404),
636 ('NoSuchUpload', 404),
637 ('NoSuchVersion', 404),
638 ('NotSignedUp', 403),
639 ('NotSuchBucketPolicy', 404),
640 ('OperationAborted', 409),
641 ('PermanentRedirect', 301),
642 ('PreconditionFailed', 412),
643 ('Redirect', 307),
644 ('RequestIsNotMultiPartContent', 400),
645 ('RequestTimeout', 400),
646 ('RequestTimeTooSkewed', 403),
647 ('RequestTorrentOfBucketError', 400),
648 ('SignatureDoesNotMatch', 403),
649 ('TemporaryRedirect', 307),
650 ('TokenRefreshRequired', 400),
651 ('TooManyBuckets', 400),
652 ('UnexpectedContent', 400),
653 ('UnresolvableGrantByEmailAddress', 400),
654 ('UserKeyMustBeSpecified', 400)):
655 _add_matcher_class(BotoTestCase.s3_error_code.client,
656 code, base=ClientError)
657
658
659for code in (('InternalError', 500),
660 ('NotImplemented', 501),
661 ('ServiceUnavailable', 503),
662 ('SlowDown', 503)):
663 _add_matcher_class(BotoTestCase.s3_error_code.server,
664 code, base=ServerError)