blob: 2ce5cce620c373e07d10ab2274a753aef54e7e85 [file] [log] [blame]
Attila Fazekasa23f5002012-10-23 19:32:45 +02001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
ZhiQiang Fan39f97222013-09-20 04:49:44 +08003# Copyright 2012 OpenStack Foundation
Attila Fazekasa23f5002012-10-23 19:32:45 +02004# 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
Sean Dague86bd8422013-12-20 09:56:44 -050032from tempest import 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
Sean Dague86bd8422013-12-20 09:56:44 -050040CONF = config.CONF
Attila Fazekasa23f5002012-10-23 19:32:45 +020041LOG = logging.getLogger(__name__)
42
43
Attila Fazekas234d3e82013-02-22 16:39:49 +010044def decision_maker():
45 A_I_IMAGES_READY = True # ari,ami,aki
46 S3_CAN_CONNECT_ERROR = None
47 EC2_CAN_CONNECT_ERROR = None
48 secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system
49 id_matcher = re.compile("[A-Za-z0-9]{20,}")
50
51 def all_read(*args):
52 return all(map(have_effective_read_access, args))
53
Sean Dague86bd8422013-12-20 09:56:44 -050054 materials_path = CONF.boto.s3_materials_path
55 ami_path = materials_path + os.sep + CONF.boto.ami_manifest
56 aki_path = materials_path + os.sep + CONF.boto.aki_manifest
57 ari_path = materials_path + os.sep + CONF.boto.ari_manifest
Attila Fazekas234d3e82013-02-22 16:39:49 +010058
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:
Sean Dague86bd8422013-12-20 09:56:44 -050073 if urlparse.urlparse(CONF.boto.ec2_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010074 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:
Sean Dague86bd8422013-12-20 09:56:44 -050090 if urlparse.urlparse(CONF.boto.s3_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010091 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 Fazekasa23f5002012-10-23 19:32:45 +0200196 @classmethod
197 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200198 super(BotoTestCase, cls).setUpClass()
Matthew Treinishf054a9c2013-10-28 21:34:47 -0400199 cls.conclusion = decision_maker()
Attila Fazekasa23f5002012-10-23 19:32:45 +0200200 # 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,
Yair Frieda039f872014-01-02 12:11:10 +0200248 when you overwrite this function don't forget to call this too.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100249 """
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:
Yair Frieda039f872014-01-02 12:11:10 +0200255 func_name = friendly_function_call_str(function, *pos_args,
256 **kw_args)
257 LOG.debug("Cleaning up: %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200258 function(*pos_args, **kw_args)
Yair Frieda039f872014-01-02 12:11:10 +0200259 except BaseException:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200260 fail_count += 1
Yair Frieda039f872014-01-02 12:11:10 +0200261 LOG.exception("Cleanup failed %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200262 finally:
263 del cls._resource_trash_bin[key]
Attila Fazekasf86fa312013-07-30 19:56:39 +0200264 super(BotoTestCase, cls).tearDownClass()
265 # NOTE(afazekas): let the super called even on exceptions
266 # The real exceptions already logged, if the super throws another,
267 # does not causes hidden issues
Attila Fazekasa23f5002012-10-23 19:32:45 +0200268 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100269 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200270
271 ec2_error_code = BotoExceptionMatcher()
272 # InsufficientInstanceCapacity can be both server and client error
273 ec2_error_code.server = ServerError()
274 ec2_error_code.client = ClientError()
275 s3_error_code = BotoExceptionMatcher()
276 s3_error_code.server = ServerError()
277 s3_error_code.client = ClientError()
278 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekas3e381f72013-08-01 16:52:23 +0200279 # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
Attila Fazekasc66ee652013-01-31 06:56:13 +0100280 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200281 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100282 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200283 valid_volume_status = set(('creating', 'available', 'in-use',
284 'deleting', 'deleted', 'error'))
285 valid_snapshot_status = set(('pending', 'completed', 'error'))
286
Attila Fazekas37f83042013-01-12 16:13:03 +0100287 gone_set = set(('_GONE',))
288
Attila Fazekas40aa3612013-01-19 22:16:38 +0100289 @classmethod
290 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400291 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100292 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400293 is the function.
294 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100295 ec = cls.ec2_error_code
296 if isinstance(obj, ec2.instance.Instance):
297 colusure_matcher = ec.client.InvalidInstanceID.NotFound
298 status_attr = "state"
299 elif isinstance(obj, ec2.image.Image):
300 colusure_matcher = ec.client.InvalidAMIID.NotFound
301 status_attr = "state"
302 elif isinstance(obj, ec2.snapshot.Snapshot):
303 colusure_matcher = ec.client.InvalidSnapshot.NotFound
304 status_attr = "status"
305 elif isinstance(obj, ec2.volume.Volume):
306 colusure_matcher = ec.client.InvalidVolume.NotFound
307 status_attr = "status"
308 else:
309 return obj
310
311 def _status():
312 try:
313 obj.update(validate=True)
314 except ValueError:
315 return "_GONE"
316 except exception.EC2ResponseError as exc:
317 if colusure_matcher.match(exc):
318 return "_GONE"
319 else:
320 raise
321 return getattr(obj, status_attr)
322
323 return _status
324
Attila Fazekas37f83042013-01-12 16:13:03 +0100325 def state_wait_gone(self, lfunction, final_set, valid_set):
326 if not isinstance(final_set, set):
327 final_set = set((final_set,))
328 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100329 lfunction = self.get_lfunction_gone(lfunction)
Attila Fazekas37f83042013-01-12 16:13:03 +0100330 state = state_wait(lfunction, final_set, valid_set)
331 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200332 return state
333
Attila Fazekas37f83042013-01-12 16:13:03 +0100334 def waitImageState(self, lfunction, wait_for):
335 return self.state_wait_gone(lfunction, wait_for,
336 self.valid_image_state)
337
Attila Fazekasa23f5002012-10-23 19:32:45 +0200338 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100339 return self.state_wait_gone(lfunction, wait_for,
340 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200341
Attila Fazekasa23f5002012-10-23 19:32:45 +0200342 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100343 return self.state_wait_gone(lfunction, wait_for,
344 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200345
Attila Fazekas40aa3612013-01-19 22:16:38 +0100346 def waitVolumeStatus(self, lfunction, wait_for):
347 return self.state_wait_gone(lfunction, wait_for,
348 self.valid_volume_status)
349
Attila Fazekasa23f5002012-10-23 19:32:45 +0200350 def assertImageStateWait(self, lfunction, wait_for):
351 state = self.waitImageState(lfunction, wait_for)
352 self.assertIn(state, wait_for)
353
354 def assertInstanceStateWait(self, lfunction, wait_for):
355 state = self.waitInstanceState(lfunction, wait_for)
356 self.assertIn(state, wait_for)
357
358 def assertVolumeStatusWait(self, lfunction, wait_for):
359 state = self.waitVolumeStatus(lfunction, wait_for)
360 self.assertIn(state, wait_for)
361
362 def assertSnapshotStatusWait(self, lfunction, wait_for):
363 state = self.waitSnapshotStatus(lfunction, wait_for)
364 self.assertIn(state, wait_for)
365
366 def assertAddressDissasociatedWait(self, address):
367
368 def _disassociate():
369 cli = self.ec2_client
370 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
371 if len(addresses) != 1:
372 return "INVALID"
373 if addresses[0].instance_id:
374 LOG.info("%s associated to %s",
375 address.public_ip,
376 addresses[0].instance_id)
377 return "ASSOCIATED"
378 return "DISASSOCIATED"
379
380 state = state_wait(_disassociate, "DISASSOCIATED",
381 set(("ASSOCIATED", "DISASSOCIATED")))
382 self.assertEqual(state, "DISASSOCIATED")
383
384 def assertAddressReleasedWait(self, address):
385
386 def _address_delete():
Attila Fazekas3e381f72013-08-01 16:52:23 +0200387 # NOTE(afazekas): the filter gives back IP
Attila Fazekasa23f5002012-10-23 19:32:45 +0200388 # even if it is not associated to my tenant
389 if (address.public_ip not in map(lambda a: a.public_ip,
390 self.ec2_client.get_all_addresses())):
391 return "DELETED"
392 return "NOTDELETED"
393
394 state = state_wait(_address_delete, "DELETED")
395 self.assertEqual(state, "DELETED")
396
397 def assertReSearch(self, regexp, string):
398 if re.search(regexp, string) is None:
399 raise self.failureException("regexp: '%s' not found in '%s'" %
400 (regexp, string))
401
402 def assertNotReSearch(self, regexp, string):
403 if re.search(regexp, string) is not None:
404 raise self.failureException("regexp: '%s' found in '%s'" %
405 (regexp, string))
406
407 def assertReMatch(self, regexp, string):
408 if re.match(regexp, string) is None:
409 raise self.failureException("regexp: '%s' not matches on '%s'" %
410 (regexp, string))
411
412 def assertNotReMatch(self, regexp, string):
413 if re.match(regexp, string) is not None:
414 raise self.failureException("regexp: '%s' matches on '%s'" %
415 (regexp, string))
416
417 @classmethod
418 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500419 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200420 exc_num = 0
421 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700422 with contextlib.closing(
423 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200424 if isinstance(bucket, basestring):
425 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100426 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200427 for obj in bucket.list():
428 try:
429 bucket.delete_key(obj.key)
430 obj.close()
Yair Frieda039f872014-01-02 12:11:10 +0200431 except BaseException:
432 LOG.exception("Failed to delete key %s " % obj.key)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200433 exc_num += 1
434 conn.delete_bucket(bucket)
Yair Frieda039f872014-01-02 12:11:10 +0200435 except BaseException:
436 LOG.exception("Failed to destroy bucket %s " % bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200437 exc_num += 1
438 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100439 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200440
441 @classmethod
442 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500443 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200444 exc_num = 0
445
446 def _instance_state():
447 try:
448 instance.update(validate=True)
449 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100450 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100451 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100452 if cls.ec2_error_code.\
Sean Dague14c68182013-04-14 15:34:30 -0400453 client.InvalidInstanceID.NotFound.match(exc):
Attila Fazekas37f83042013-01-12 16:13:03 +0100454 return "_GONE"
Attila Fazekas3e381f72013-08-01 16:52:23 +0200455 # NOTE(afazekas): incorrect code,
Attila Fazekas37f83042013-01-12 16:13:03 +0100456 # but the resource must be destoreyd
457 if exc.error_code == "InstanceNotFound":
458 return "_GONE"
459
Attila Fazekasa23f5002012-10-23 19:32:45 +0200460 return instance.state
461
462 for instance in reservation.instances:
463 try:
464 instance.terminate()
Attila Fazekas37f83042013-01-12 16:13:03 +0100465 re_search_wait(_instance_state, "_GONE")
Yair Frieda039f872014-01-02 12:11:10 +0200466 except BaseException:
467 LOG.exception("Failed to terminate instance %s " % instance)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200468 exc_num += 1
469 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100470 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200471
Attila Fazekas3e381f72013-08-01 16:52:23 +0200472 # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
Attila Fazekasa23f5002012-10-23 19:32:45 +0200473 # to write better teardown
474
475 @classmethod
476 def destroy_security_group_wait(cls, group):
477 """Delete group.
478 Use just for teardown!
479 """
Attila Fazekas3e381f72013-08-01 16:52:23 +0200480 # NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200481 group.delete()
482
483 @classmethod
484 def destroy_volume_wait(cls, volume):
485 """Delete volume, tryies to detach first.
486 Use just for teardown!
487 """
488 exc_num = 0
489 snaps = volume.snapshots()
490 if len(snaps):
491 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100492 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200493
Attila Fazekas3e381f72013-08-01 16:52:23 +0200494 # NOTE(afazekas): detaching/attching not valid EC2 status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200495 def _volume_state():
496 volume.update(validate=True)
497 try:
498 if volume.status != "available":
499 volume.detach(force=True)
Yair Frieda039f872014-01-02 12:11:10 +0200500 except BaseException:
501 LOG.exception("Failed to detach volume %s" % volume)
Attila Fazekas3e381f72013-08-01 16:52:23 +0200502 # exc_num += 1 "nonlocal" not in python2
Attila Fazekasa23f5002012-10-23 19:32:45 +0200503 return volume.status
504
505 try:
506 re_search_wait(_volume_state, "available") # not validates status
507 LOG.info(_volume_state())
508 volume.delete()
Yair Frieda039f872014-01-02 12:11:10 +0200509 except BaseException:
510 LOG.exception("Failed to delete volume %s" % volume)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200511 exc_num += 1
512 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100513 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200514
515 @classmethod
516 def destroy_snapshot_wait(cls, snapshot):
Sean Daguef237ccb2013-01-04 15:19:14 -0500517 """delete snaphot, wait until not exists."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200518 snapshot.delete()
519
520 def _update():
521 snapshot.update(validate=True)
522
523 wait_exception(_update)
524
525
526# you can specify tuples if you want to specify the status pattern
527for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
528 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
529 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
530 'Gateway.NotAttached', 'IdempotentParameterMismatch',
531 'IncorrectInstanceState', 'IncorrectState',
532 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
533 'InsufficientReservedInstancesCapacity',
534 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
535 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
536 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
537 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
538 'InvalidCustomerGateway.DuplicateIpAddress',
539 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
540 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
541 'InvalidFilter', 'InvalidGatewayID.NotFound',
542 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
543 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
544 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
545 'InvalidInstanceID.NotFound',
546 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
547 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
548 'InvalidKeyPair.NotFound', 'InvalidManifest',
549 'InvalidNetworkAclEntry.NotFound',
550 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
551 'InvalidParameterValue', 'InvalidPermission.Duplicate',
552 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
553 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
554 'InvalidRouteTableID.NotFound',
555 'InvalidSecurity.RequestHasExpired',
556 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
557 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
558 'InvalidReservedInstancesOfferingId',
559 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
560 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
561 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
562 'InvalidVpnConnectionID.NotFound',
563 'InvalidVpnGatewayID.NotFound',
564 'InvalidZone.NotFound', 'LegacySecurityGroup',
565 'MissingParameter', 'NetworkAclEntryAlreadyExists',
566 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
567 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
568 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
569 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
570 'ResourceLimitExceeded', 'RouteAlreadyExists',
571 'RouteLimitExceeded', 'RouteTableLimitExceeded',
572 'RulesPerSecurityGroupLimitExceeded',
573 'SecurityGroupLimitExceeded',
574 'SecurityGroupsPerInstanceLimitExceeded',
575 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
576 'UnknownParameter', 'UnsupportedOperation',
577 'VolumeLimitExceeded', 'VpcLimitExceeded',
578 'VpnConnectionLimitExceeded',
579 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
580 _add_matcher_class(BotoTestCase.ec2_error_code.client,
581 code, base=ClientError)
582
583for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
584 'InsufficientReservedInstanceCapacity', 'InternalError',
585 'Unavailable'):
586 _add_matcher_class(BotoTestCase.ec2_error_code.server,
587 code, base=ServerError)
588
589
590for code in (('AccessDenied', 403),
591 ('AccountProblem', 403),
592 ('AmbiguousGrantByEmailAddress', 400),
593 ('BadDigest', 400),
594 ('BucketAlreadyExists', 409),
595 ('BucketAlreadyOwnedByYou', 409),
596 ('BucketNotEmpty', 409),
597 ('CredentialsNotSupported', 400),
598 ('CrossLocationLoggingProhibited', 403),
599 ('EntityTooSmall', 400),
600 ('EntityTooLarge', 400),
601 ('ExpiredToken', 400),
602 ('IllegalVersioningConfigurationException', 400),
603 ('IncompleteBody', 400),
604 ('IncorrectNumberOfFilesInPostRequest', 400),
605 ('InlineDataTooLarge', 400),
606 ('InvalidAccessKeyId', 403),
607 'InvalidAddressingHeader',
608 ('InvalidArgument', 400),
609 ('InvalidBucketName', 400),
610 ('InvalidBucketState', 409),
611 ('InvalidDigest', 400),
612 ('InvalidLocationConstraint', 400),
613 ('InvalidPart', 400),
614 ('InvalidPartOrder', 400),
615 ('InvalidPayer', 403),
616 ('InvalidPolicyDocument', 400),
617 ('InvalidRange', 416),
618 ('InvalidRequest', 400),
619 ('InvalidSecurity', 403),
620 ('InvalidSOAPRequest', 400),
621 ('InvalidStorageClass', 400),
622 ('InvalidTargetBucketForLogging', 400),
623 ('InvalidToken', 400),
624 ('InvalidURI', 400),
625 ('KeyTooLong', 400),
626 ('MalformedACLError', 400),
627 ('MalformedPOSTRequest', 400),
628 ('MalformedXML', 400),
629 ('MaxMessageLengthExceeded', 400),
630 ('MaxPostPreDataLengthExceededError', 400),
631 ('MetadataTooLarge', 400),
632 ('MethodNotAllowed', 405),
633 ('MissingAttachment'),
634 ('MissingContentLength', 411),
635 ('MissingRequestBodyError', 400),
636 ('MissingSecurityElement', 400),
637 ('MissingSecurityHeader', 400),
638 ('NoLoggingStatusForKey', 400),
639 ('NoSuchBucket', 404),
640 ('NoSuchKey', 404),
641 ('NoSuchLifecycleConfiguration', 404),
642 ('NoSuchUpload', 404),
643 ('NoSuchVersion', 404),
644 ('NotSignedUp', 403),
645 ('NotSuchBucketPolicy', 404),
646 ('OperationAborted', 409),
647 ('PermanentRedirect', 301),
648 ('PreconditionFailed', 412),
649 ('Redirect', 307),
650 ('RequestIsNotMultiPartContent', 400),
651 ('RequestTimeout', 400),
652 ('RequestTimeTooSkewed', 403),
653 ('RequestTorrentOfBucketError', 400),
654 ('SignatureDoesNotMatch', 403),
655 ('TemporaryRedirect', 307),
656 ('TokenRefreshRequired', 400),
657 ('TooManyBuckets', 400),
658 ('UnexpectedContent', 400),
659 ('UnresolvableGrantByEmailAddress', 400),
660 ('UserKeyMustBeSpecified', 400)):
661 _add_matcher_class(BotoTestCase.s3_error_code.client,
662 code, base=ClientError)
663
664
665for code in (('InternalError', 500),
666 ('NotImplemented', 501),
667 ('ServiceUnavailable', 503),
668 ('SlowDown', 503)):
669 _add_matcher_class(BotoTestCase.s3_error_code.server,
670 code, base=ServerError)