blob: b36e8c73175ec3a608c62157c6527685780d9889 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Attila Fazekasa23f5002012-10-23 19:32:45 +02002# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Monty Taylorb2ca5ca2013-04-28 18:00:21 -070016import contextlib
Mitsuhiko Yamazaki46818aa2013-04-18 17:49:17 +090017import logging as orig_logging
Attila Fazekas234d3e82013-02-22 16:39:49 +010018import os
Matthew Treinisha83a16e2012-12-07 13:44:02 -050019import re
Attila Fazekas234d3e82013-02-22 16:39:49 +010020import urlparse
Attila Fazekasa23f5002012-10-23 19:32:45 +020021
Matthew Treinisha83a16e2012-12-07 13:44:02 -050022import boto
Attila Fazekas40aa3612013-01-19 22:16:38 +010023from boto import ec2
24from boto import exception
25from boto import s3
Attila Fazekas234d3e82013-02-22 16:39:49 +010026import keystoneclient.exceptions
Matthew Treinisha83a16e2012-12-07 13:44:02 -050027
Attila Fazekas234d3e82013-02-22 16:39:49 +010028import tempest.clients
29from tempest.common.utils.file_utils import have_effective_read_access
Sean Dague86bd8422013-12-20 09:56:44 -050030from tempest import config
Attila Fazekas40aa3612013-01-19 22:16:38 +010031from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040032from tempest.openstack.common import log as logging
Attila Fazekasdc216422013-01-29 15:12:14 +010033import tempest.test
Sean Dague09761f62013-05-13 15:20:40 -040034from tempest.thirdparty.boto.utils.wait import re_search_wait
35from tempest.thirdparty.boto.utils.wait import state_wait
36from tempest.thirdparty.boto.utils.wait import wait_exception
Matthew Treinisha83a16e2012-12-07 13:44:02 -050037
Sean Dague86bd8422013-12-20 09:56:44 -050038CONF = config.CONF
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
Sean Dague86bd8422013-12-20 09:56:44 -050052 materials_path = CONF.boto.s3_materials_path
53 ami_path = materials_path + os.sep + CONF.boto.ami_manifest
54 aki_path = materials_path + os.sep + CONF.boto.aki_manifest
55 ari_path = materials_path + os.sep + CONF.boto.ari_manifest
Attila Fazekas234d3e82013-02-22 16:39:49 +010056
57 A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
58 boto_logger = logging.getLogger('boto')
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040059 level = boto_logger.logger.level
60 boto_logger.logger.setLevel(orig_logging.CRITICAL) # suppress logging
61 # for these
Attila Fazekas234d3e82013-02-22 16:39:49 +010062
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:
Sean Dague86bd8422013-12-20 09:56:44 -050071 if urlparse.urlparse(CONF.boto.ec2_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010072 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:
Sean Dague86bd8422013-12-20 09:56:44 -050088 if urlparse.urlparse(CONF.boto.s3_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010089 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"
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -0400101 boto_logger.logger.setLevel(level)
Attila Fazekas234d3e82013-02-22 16:39:49 +0100102 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
Attila Fazekas3e381f72013-08-01 16:52:23 +0200171# TODO(afazekas): classmethod handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200172def 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 Fazekasa23f5002012-10-23 19:32:45 +0200194 @classmethod
195 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200196 super(BotoTestCase, cls).setUpClass()
Matthew Treinishf054a9c2013-10-28 21:34:47 -0400197 cls.conclusion = decision_maker()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000198 cls.os = cls.get_client_manager()
Attila Fazekasa23f5002012-10-23 19:32:45 +0200199 # 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
Attila Fazekas3e381f72013-08-01 16:52:23 +0200226 # TODO(afazekas): Add "with" context handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200227 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,
Yair Frieda039f872014-01-02 12:11:10 +0200247 when you overwrite this function don't forget to call this too.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100248 """
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:
Yair Frieda039f872014-01-02 12:11:10 +0200254 func_name = friendly_function_call_str(function, *pos_args,
255 **kw_args)
256 LOG.debug("Cleaning up: %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200257 function(*pos_args, **kw_args)
Yair Frieda039f872014-01-02 12:11:10 +0200258 except BaseException:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200259 fail_count += 1
Yair Frieda039f872014-01-02 12:11:10 +0200260 LOG.exception("Cleanup failed %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200261 finally:
262 del cls._resource_trash_bin[key]
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000263 cls.clear_isolated_creds()
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)