blob: 3496dce5d315d53d832736f5489b823df548901d [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 Treinish96e9e882014-06-09 18:37:19 -040027import six
Matthew Treinisha83a16e2012-12-07 13:44:02 -050028
Attila Fazekas234d3e82013-02-22 16:39:49 +010029import tempest.clients
Masayuki Igawa224a8272014-02-17 15:07:43 +090030from tempest.common.utils import file_utils
Sean Dague86bd8422013-12-20 09:56:44 -050031from tempest import config
Attila Fazekas40aa3612013-01-19 22:16:38 +010032from tempest import exceptions
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040033from tempest.openstack.common import log as logging
Attila Fazekasdc216422013-01-29 15:12:14 +010034import tempest.test
Masayuki Igawa224a8272014-02-17 15:07:43 +090035from tempest.thirdparty.boto.utils import wait
Matthew Treinisha83a16e2012-12-07 13:44:02 -050036
Sean Dague86bd8422013-12-20 09:56:44 -050037CONF = config.CONF
Attila Fazekasa23f5002012-10-23 19:32:45 +020038LOG = logging.getLogger(__name__)
39
40
Attila Fazekas234d3e82013-02-22 16:39:49 +010041def decision_maker():
42 A_I_IMAGES_READY = True # ari,ami,aki
43 S3_CAN_CONNECT_ERROR = None
44 EC2_CAN_CONNECT_ERROR = None
45 secret_matcher = re.compile("[A-Za-z0-9+/]{32,}") # 40 in other system
46 id_matcher = re.compile("[A-Za-z0-9]{20,}")
47
48 def all_read(*args):
Masayuki Igawa224a8272014-02-17 15:07:43 +090049 return all(map(file_utils.have_effective_read_access, args))
Attila Fazekas234d3e82013-02-22 16:39:49 +010050
Sean Dague86bd8422013-12-20 09:56:44 -050051 materials_path = CONF.boto.s3_materials_path
52 ami_path = materials_path + os.sep + CONF.boto.ami_manifest
53 aki_path = materials_path + os.sep + CONF.boto.aki_manifest
54 ari_path = materials_path + os.sep + CONF.boto.ari_manifest
Attila Fazekas234d3e82013-02-22 16:39:49 +010055
56 A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
57 boto_logger = logging.getLogger('boto')
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -040058 level = boto_logger.logger.level
Matthew Treinishc795b9e2014-06-09 17:01:10 -040059 # suppress logging for boto
60 boto_logger.logger.setLevel(orig_logging.CRITICAL)
Attila Fazekas234d3e82013-02-22 16:39:49 +010061
62 def _cred_sub_check(connection_data):
63 if not id_matcher.match(connection_data["aws_access_key_id"]):
64 raise Exception("Invalid AWS access Key")
65 if not secret_matcher.match(connection_data["aws_secret_access_key"]):
66 raise Exception("Invalid AWS secret Key")
67 raise Exception("Unknown (Authentication?) Error")
68 openstack = tempest.clients.Manager()
69 try:
Sean Dague86bd8422013-12-20 09:56:44 -050070 if urlparse.urlparse(CONF.boto.ec2_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010071 raise Exception("Failed to get hostname from the ec2_url")
72 ec2client = openstack.ec2api_client
73 try:
74 ec2client.get_all_regions()
75 except exception.BotoServerError as exc:
76 if exc.error_code is None:
77 raise Exception("EC2 target does not looks EC2 service")
78 _cred_sub_check(ec2client.connection_data)
79
80 except keystoneclient.exceptions.Unauthorized:
81 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
82 " faild to get them even by keystoneclient"
83 except Exception as exc:
84 EC2_CAN_CONNECT_ERROR = str(exc)
85
86 try:
Sean Dague86bd8422013-12-20 09:56:44 -050087 if urlparse.urlparse(CONF.boto.s3_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010088 raise Exception("Failed to get hostname from the s3_url")
89 s3client = openstack.s3_client
90 try:
91 s3client.get_bucket("^INVALID*#()@INVALID.")
92 except exception.BotoServerError as exc:
93 if exc.status == 403:
94 _cred_sub_check(s3client.connection_data)
95 except Exception as exc:
96 S3_CAN_CONNECT_ERROR = str(exc)
97 except keystoneclient.exceptions.Unauthorized:
98 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
99 " faild to get them even by keystoneclient"
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -0400100 boto_logger.logger.setLevel(level)
Attila Fazekas234d3e82013-02-22 16:39:49 +0100101 return {'A_I_IMAGES_READY': A_I_IMAGES_READY,
102 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR,
103 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR}
104
105
Attila Fazekasa23f5002012-10-23 19:32:45 +0200106class BotoExceptionMatcher(object):
107 STATUS_RE = r'[45]\d\d'
108 CODE_RE = '.*' # regexp makes sense in group match
109
110 def match(self, exc):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700111 """:returns: Returns with an error string if it does not match,
112 returns with None when it matches.
Attila Fazekas55c597d2014-02-21 13:10:41 +0100113 """
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)
Attila Fazekas55c597d2014-02-21 13:10:41 +0100125 return None
Attila Fazekasa23f5002012-10-23 19:32:45 +0200126
127
128class ClientError(BotoExceptionMatcher):
129 STATUS_RE = r'4\d\d'
130
131
132class ServerError(BotoExceptionMatcher):
133 STATUS_RE = r'5\d\d'
134
135
136def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
137 """
138 Usable for adding an ExceptionMatcher(s) into the exception tree.
139 The not leaf elements does wildcard match
140 """
141 # in error_code just literal and '.' characters expected
llg821243b20502014-02-22 10:32:49 +0800142 if not isinstance(error_data, six.string_types):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200143 (error_code, status_code) = map(str, error_data)
144 else:
145 status_code = None
146 error_code = error_data
147 parts = error_code.split('.')
148 basematch = ""
149 num_parts = len(parts)
150 max_index = num_parts - 1
151 add_cls = error_cls
llg821243b20502014-02-22 10:32:49 +0800152 for i_part in six.moves.xrange(num_parts):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200153 part = parts[i_part]
154 leaf = i_part == max_index
155 if not leaf:
156 match = basematch + part + "[.].*"
157 else:
158 match = basematch + part
159
160 basematch += part + "[.]"
161 if not hasattr(add_cls, part):
162 cls_dict = {"CODE_RE": match}
163 if leaf and status_code is not None:
164 cls_dict["STATUS_RE"] = status_code
165 cls = type(part, (base, ), cls_dict)
166 setattr(add_cls, part, cls())
167 add_cls = cls
168 elif leaf:
169 raise LookupError("Tries to redefine an error code \"%s\"" % part)
170 else:
171 add_cls = getattr(add_cls, part)
172
173
Attila Fazekas3e381f72013-08-01 16:52:23 +0200174# TODO(afazekas): classmethod handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200175def friendly_function_name_simple(call_able):
176 name = ""
177 if hasattr(call_able, "im_class"):
178 name += call_able.im_class.__name__ + "."
179 name += call_able.__name__
180 return name
181
182
183def friendly_function_call_str(call_able, *args, **kwargs):
184 string = friendly_function_name_simple(call_able)
185 string += "(" + ", ".join(map(str, args))
186 if len(kwargs):
187 if len(args):
188 string += ", "
189 string += ", ".join("=".join(map(str, (key, value)))
Matthew Treinish1d14c542014-06-17 20:25:40 -0400190 for (key, value) in kwargs.items())
Attila Fazekasa23f5002012-10-23 19:32:45 +0200191 return string + ")"
192
193
Attila Fazekasdc216422013-01-29 15:12:14 +0100194class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500195 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030196
Attila Fazekasa23f5002012-10-23 19:32:45 +0200197 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100198 def resource_setup(cls):
199 super(BotoTestCase, cls).resource_setup()
Matthew Treinishf054a9c2013-10-28 21:34:47 -0400200 cls.conclusion = decision_maker()
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000201 cls.os = cls.get_client_manager()
Attila Fazekasa23f5002012-10-23 19:32:45 +0200202 # The trash contains cleanup functions and paramaters in tuples
203 # (function, *args, **kwargs)
204 cls._resource_trash_bin = {}
205 cls._sequence = -1
206 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100207 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800208 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100209 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200210 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100211 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800212 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100213 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200214
215 @classmethod
216 def addResourceCleanUp(cls, function, *args, **kwargs):
217 """Adds CleanUp callable, used by tearDownClass.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100218 Recommended to a use (deep)copy on the mutable args.
219 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200220 cls._sequence = cls._sequence + 1
221 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
222 return cls._sequence
223
224 @classmethod
225 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500226 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200227 del cls._resource_trash_bin[key]
228
Attila Fazekas3e381f72013-08-01 16:52:23 +0200229 # TODO(afazekas): Add "with" context handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200230 def assertBotoError(self, excMatcher, callableObj,
231 *args, **kwargs):
232 """Example usage:
233 self.assertBotoError(self.ec2_error_code.client.
234 InvalidKeyPair.Duplicate,
235 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100236 key_name)
237 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200238 try:
239 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100240 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200241 error_msg = excMatcher.match(exc)
242 if error_msg is not None:
243 raise self.failureException, error_msg
244 else:
245 raise self.failureException, "BotoServerError not raised"
246
247 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100248 def resource_cleanup(cls):
Attila Fazekasb2902af2013-02-16 16:22:44 +0100249 """Calls the callables added by addResourceCleanUp,
Yair Frieda039f872014-01-02 12:11:10 +0200250 when you overwrite this function don't forget to call this too.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100251 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200252 fail_count = 0
253 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
254 for key in trash_keys:
255 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
256 try:
Yair Frieda039f872014-01-02 12:11:10 +0200257 func_name = friendly_function_call_str(function, *pos_args,
258 **kw_args)
259 LOG.debug("Cleaning up: %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200260 function(*pos_args, **kw_args)
Yair Frieda039f872014-01-02 12:11:10 +0200261 except BaseException:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200262 fail_count += 1
Yair Frieda039f872014-01-02 12:11:10 +0200263 LOG.exception("Cleanup failed %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200264 finally:
265 del cls._resource_trash_bin[key]
Matthew Treinish8004e8c2014-01-27 23:03:14 +0000266 cls.clear_isolated_creds()
Andrea Frittoli29fea352014-09-15 13:31:14 +0100267 super(BotoTestCase, cls).resource_cleanup()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200268 # NOTE(afazekas): let the super called even on exceptions
269 # The real exceptions already logged, if the super throws another,
270 # does not causes hidden issues
Attila Fazekasa23f5002012-10-23 19:32:45 +0200271 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100272 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200273
274 ec2_error_code = BotoExceptionMatcher()
275 # InsufficientInstanceCapacity can be both server and client error
276 ec2_error_code.server = ServerError()
277 ec2_error_code.client = ClientError()
278 s3_error_code = BotoExceptionMatcher()
279 s3_error_code.server = ServerError()
280 s3_error_code.client = ClientError()
281 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekas3e381f72013-08-01 16:52:23 +0200282 # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
Attila Fazekasc66ee652013-01-31 06:56:13 +0100283 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200284 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100285 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200286 valid_volume_status = set(('creating', 'available', 'in-use',
287 'deleting', 'deleted', 'error'))
288 valid_snapshot_status = set(('pending', 'completed', 'error'))
289
Attila Fazekas37f83042013-01-12 16:13:03 +0100290 gone_set = set(('_GONE',))
291
Attila Fazekas40aa3612013-01-19 22:16:38 +0100292 @classmethod
293 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400294 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100295 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400296 is the function.
297 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100298 ec = cls.ec2_error_code
299 if isinstance(obj, ec2.instance.Instance):
300 colusure_matcher = ec.client.InvalidInstanceID.NotFound
301 status_attr = "state"
302 elif isinstance(obj, ec2.image.Image):
303 colusure_matcher = ec.client.InvalidAMIID.NotFound
304 status_attr = "state"
305 elif isinstance(obj, ec2.snapshot.Snapshot):
306 colusure_matcher = ec.client.InvalidSnapshot.NotFound
307 status_attr = "status"
308 elif isinstance(obj, ec2.volume.Volume):
309 colusure_matcher = ec.client.InvalidVolume.NotFound
310 status_attr = "status"
311 else:
312 return obj
313
314 def _status():
315 try:
316 obj.update(validate=True)
317 except ValueError:
318 return "_GONE"
319 except exception.EC2ResponseError as exc:
Attila Fazekas55c597d2014-02-21 13:10:41 +0100320 if colusure_matcher.match(exc) is None:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100321 return "_GONE"
322 else:
323 raise
324 return getattr(obj, status_attr)
325
326 return _status
327
Attila Fazekas37f83042013-01-12 16:13:03 +0100328 def state_wait_gone(self, lfunction, final_set, valid_set):
329 if not isinstance(final_set, set):
330 final_set = set((final_set,))
331 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100332 lfunction = self.get_lfunction_gone(lfunction)
Masayuki Igawa224a8272014-02-17 15:07:43 +0900333 state = wait.state_wait(lfunction, final_set, valid_set)
Attila Fazekas37f83042013-01-12 16:13:03 +0100334 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200335 return state
336
Attila Fazekas37f83042013-01-12 16:13:03 +0100337 def waitImageState(self, lfunction, wait_for):
338 return self.state_wait_gone(lfunction, wait_for,
339 self.valid_image_state)
340
Attila Fazekasa23f5002012-10-23 19:32:45 +0200341 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100342 return self.state_wait_gone(lfunction, wait_for,
343 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200344
Attila Fazekasa23f5002012-10-23 19:32:45 +0200345 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100346 return self.state_wait_gone(lfunction, wait_for,
347 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200348
Attila Fazekas40aa3612013-01-19 22:16:38 +0100349 def waitVolumeStatus(self, lfunction, wait_for):
350 return self.state_wait_gone(lfunction, wait_for,
351 self.valid_volume_status)
352
Attila Fazekasa23f5002012-10-23 19:32:45 +0200353 def assertImageStateWait(self, lfunction, wait_for):
354 state = self.waitImageState(lfunction, wait_for)
355 self.assertIn(state, wait_for)
356
357 def assertInstanceStateWait(self, lfunction, wait_for):
358 state = self.waitInstanceState(lfunction, wait_for)
359 self.assertIn(state, wait_for)
360
361 def assertVolumeStatusWait(self, lfunction, wait_for):
362 state = self.waitVolumeStatus(lfunction, wait_for)
363 self.assertIn(state, wait_for)
364
365 def assertSnapshotStatusWait(self, lfunction, wait_for):
366 state = self.waitSnapshotStatus(lfunction, wait_for)
367 self.assertIn(state, wait_for)
368
369 def assertAddressDissasociatedWait(self, address):
370
371 def _disassociate():
372 cli = self.ec2_client
373 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
374 if len(addresses) != 1:
375 return "INVALID"
376 if addresses[0].instance_id:
377 LOG.info("%s associated to %s",
378 address.public_ip,
379 addresses[0].instance_id)
380 return "ASSOCIATED"
381 return "DISASSOCIATED"
382
Masayuki Igawa224a8272014-02-17 15:07:43 +0900383 state = wait.state_wait(_disassociate, "DISASSOCIATED",
384 set(("ASSOCIATED", "DISASSOCIATED")))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200385 self.assertEqual(state, "DISASSOCIATED")
386
387 def assertAddressReleasedWait(self, address):
388
389 def _address_delete():
Attila Fazekas3e381f72013-08-01 16:52:23 +0200390 # NOTE(afazekas): the filter gives back IP
Attila Fazekasa23f5002012-10-23 19:32:45 +0200391 # even if it is not associated to my tenant
392 if (address.public_ip not in map(lambda a: a.public_ip,
393 self.ec2_client.get_all_addresses())):
394 return "DELETED"
395 return "NOTDELETED"
396
Masayuki Igawa224a8272014-02-17 15:07:43 +0900397 state = wait.state_wait(_address_delete, "DELETED")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200398 self.assertEqual(state, "DELETED")
399
400 def assertReSearch(self, regexp, string):
401 if re.search(regexp, string) is None:
402 raise self.failureException("regexp: '%s' not found in '%s'" %
403 (regexp, string))
404
405 def assertNotReSearch(self, regexp, string):
406 if re.search(regexp, string) is not None:
407 raise self.failureException("regexp: '%s' found in '%s'" %
408 (regexp, string))
409
410 def assertReMatch(self, regexp, string):
411 if re.match(regexp, string) is None:
412 raise self.failureException("regexp: '%s' not matches on '%s'" %
413 (regexp, string))
414
415 def assertNotReMatch(self, regexp, string):
416 if re.match(regexp, string) is not None:
417 raise self.failureException("regexp: '%s' matches on '%s'" %
418 (regexp, string))
419
420 @classmethod
421 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500422 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200423 exc_num = 0
424 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700425 with contextlib.closing(
426 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200427 if isinstance(bucket, basestring):
428 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100429 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200430 for obj in bucket.list():
431 try:
432 bucket.delete_key(obj.key)
433 obj.close()
Yair Frieda039f872014-01-02 12:11:10 +0200434 except BaseException:
435 LOG.exception("Failed to delete key %s " % obj.key)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200436 exc_num += 1
437 conn.delete_bucket(bucket)
Yair Frieda039f872014-01-02 12:11:10 +0200438 except BaseException:
439 LOG.exception("Failed to destroy bucket %s " % bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200440 exc_num += 1
441 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100442 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200443
444 @classmethod
445 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500446 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200447 exc_num = 0
448
449 def _instance_state():
450 try:
451 instance.update(validate=True)
452 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100453 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100454 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100455 if cls.ec2_error_code.\
Attila Fazekas55c597d2014-02-21 13:10:41 +0100456 client.InvalidInstanceID.NotFound.match(exc) is None:
Attila Fazekas37f83042013-01-12 16:13:03 +0100457 return "_GONE"
Attila Fazekas3e381f72013-08-01 16:52:23 +0200458 # NOTE(afazekas): incorrect code,
Attila Fazekas37f83042013-01-12 16:13:03 +0100459 # but the resource must be destoreyd
460 if exc.error_code == "InstanceNotFound":
461 return "_GONE"
462
Attila Fazekasa23f5002012-10-23 19:32:45 +0200463 return instance.state
464
465 for instance in reservation.instances:
466 try:
467 instance.terminate()
Masayuki Igawa224a8272014-02-17 15:07:43 +0900468 wait.re_search_wait(_instance_state, "_GONE")
Yair Frieda039f872014-01-02 12:11:10 +0200469 except BaseException:
470 LOG.exception("Failed to terminate instance %s " % instance)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200471 exc_num += 1
472 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100473 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200474
Attila Fazekas3e381f72013-08-01 16:52:23 +0200475 # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
Attila Fazekasa23f5002012-10-23 19:32:45 +0200476 # to write better teardown
477
478 @classmethod
479 def destroy_security_group_wait(cls, group):
480 """Delete group.
481 Use just for teardown!
482 """
Attila Fazekas3e381f72013-08-01 16:52:23 +0200483 # NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200484 group.delete()
485
486 @classmethod
487 def destroy_volume_wait(cls, volume):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700488 """Delete volume, tries to detach first.
Attila Fazekasa23f5002012-10-23 19:32:45 +0200489 Use just for teardown!
490 """
491 exc_num = 0
492 snaps = volume.snapshots()
493 if len(snaps):
494 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100495 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200496
Attila Fazekas3e381f72013-08-01 16:52:23 +0200497 # NOTE(afazekas): detaching/attching not valid EC2 status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200498 def _volume_state():
499 volume.update(validate=True)
500 try:
501 if volume.status != "available":
502 volume.detach(force=True)
Yair Frieda039f872014-01-02 12:11:10 +0200503 except BaseException:
504 LOG.exception("Failed to detach volume %s" % volume)
Attila Fazekas3e381f72013-08-01 16:52:23 +0200505 # exc_num += 1 "nonlocal" not in python2
Attila Fazekasa23f5002012-10-23 19:32:45 +0200506 return volume.status
507
508 try:
Masayuki Igawa224a8272014-02-17 15:07:43 +0900509 wait.re_search_wait(_volume_state, "available")
510 # not validates status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200511 LOG.info(_volume_state())
512 volume.delete()
Yair Frieda039f872014-01-02 12:11:10 +0200513 except BaseException:
514 LOG.exception("Failed to delete volume %s" % volume)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200515 exc_num += 1
516 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100517 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200518
519 @classmethod
520 def destroy_snapshot_wait(cls, snapshot):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700521 """delete snapshot, wait until it ceases to exist."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200522 snapshot.delete()
523
524 def _update():
525 snapshot.update(validate=True)
526
Masayuki Igawa224a8272014-02-17 15:07:43 +0900527 wait.wait_exception(_update)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200528
529
530# you can specify tuples if you want to specify the status pattern
531for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
532 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
533 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
534 'Gateway.NotAttached', 'IdempotentParameterMismatch',
535 'IncorrectInstanceState', 'IncorrectState',
536 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
537 'InsufficientReservedInstancesCapacity',
538 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
539 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
540 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
541 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
542 'InvalidCustomerGateway.DuplicateIpAddress',
543 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
544 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
545 'InvalidFilter', 'InvalidGatewayID.NotFound',
546 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
547 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
548 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
549 'InvalidInstanceID.NotFound',
550 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
551 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
552 'InvalidKeyPair.NotFound', 'InvalidManifest',
553 'InvalidNetworkAclEntry.NotFound',
554 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
555 'InvalidParameterValue', 'InvalidPermission.Duplicate',
556 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
557 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
558 'InvalidRouteTableID.NotFound',
559 'InvalidSecurity.RequestHasExpired',
560 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
561 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
562 'InvalidReservedInstancesOfferingId',
563 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
564 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
565 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
566 'InvalidVpnConnectionID.NotFound',
567 'InvalidVpnGatewayID.NotFound',
568 'InvalidZone.NotFound', 'LegacySecurityGroup',
569 'MissingParameter', 'NetworkAclEntryAlreadyExists',
570 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
571 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
572 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
573 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
574 'ResourceLimitExceeded', 'RouteAlreadyExists',
575 'RouteLimitExceeded', 'RouteTableLimitExceeded',
576 'RulesPerSecurityGroupLimitExceeded',
577 'SecurityGroupLimitExceeded',
578 'SecurityGroupsPerInstanceLimitExceeded',
579 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
580 'UnknownParameter', 'UnsupportedOperation',
581 'VolumeLimitExceeded', 'VpcLimitExceeded',
582 'VpnConnectionLimitExceeded',
583 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
584 _add_matcher_class(BotoTestCase.ec2_error_code.client,
585 code, base=ClientError)
586
587for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
588 'InsufficientReservedInstanceCapacity', 'InternalError',
589 'Unavailable'):
590 _add_matcher_class(BotoTestCase.ec2_error_code.server,
591 code, base=ServerError)
592
593
594for code in (('AccessDenied', 403),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400595 ('AccountProblem', 403),
596 ('AmbiguousGrantByEmailAddress', 400),
597 ('BadDigest', 400),
598 ('BucketAlreadyExists', 409),
599 ('BucketAlreadyOwnedByYou', 409),
600 ('BucketNotEmpty', 409),
601 ('CredentialsNotSupported', 400),
602 ('CrossLocationLoggingProhibited', 403),
603 ('EntityTooSmall', 400),
604 ('EntityTooLarge', 400),
605 ('ExpiredToken', 400),
606 ('IllegalVersioningConfigurationException', 400),
607 ('IncompleteBody', 400),
608 ('IncorrectNumberOfFilesInPostRequest', 400),
609 ('InlineDataTooLarge', 400),
610 ('InvalidAccessKeyId', 403),
Attila Fazekasa23f5002012-10-23 19:32:45 +0200611 'InvalidAddressingHeader',
Matthew Treinish1d14c542014-06-17 20:25:40 -0400612 ('InvalidArgument', 400),
613 ('InvalidBucketName', 400),
614 ('InvalidBucketState', 409),
615 ('InvalidDigest', 400),
616 ('InvalidLocationConstraint', 400),
617 ('InvalidPart', 400),
618 ('InvalidPartOrder', 400),
619 ('InvalidPayer', 403),
620 ('InvalidPolicyDocument', 400),
621 ('InvalidRange', 416),
622 ('InvalidRequest', 400),
623 ('InvalidSecurity', 403),
624 ('InvalidSOAPRequest', 400),
625 ('InvalidStorageClass', 400),
626 ('InvalidTargetBucketForLogging', 400),
627 ('InvalidToken', 400),
628 ('InvalidURI', 400),
629 ('KeyTooLong', 400),
630 ('MalformedACLError', 400),
631 ('MalformedPOSTRequest', 400),
632 ('MalformedXML', 400),
633 ('MaxMessageLengthExceeded', 400),
634 ('MaxPostPreDataLengthExceededError', 400),
635 ('MetadataTooLarge', 400),
636 ('MethodNotAllowed', 405),
637 ('MissingAttachment'),
638 ('MissingContentLength', 411),
639 ('MissingRequestBodyError', 400),
640 ('MissingSecurityElement', 400),
641 ('MissingSecurityHeader', 400),
642 ('NoLoggingStatusForKey', 400),
643 ('NoSuchBucket', 404),
644 ('NoSuchKey', 404),
645 ('NoSuchLifecycleConfiguration', 404),
646 ('NoSuchUpload', 404),
647 ('NoSuchVersion', 404),
648 ('NotSignedUp', 403),
649 ('NotSuchBucketPolicy', 404),
650 ('OperationAborted', 409),
651 ('PermanentRedirect', 301),
652 ('PreconditionFailed', 412),
653 ('Redirect', 307),
654 ('RequestIsNotMultiPartContent', 400),
655 ('RequestTimeout', 400),
656 ('RequestTimeTooSkewed', 403),
657 ('RequestTorrentOfBucketError', 400),
658 ('SignatureDoesNotMatch', 403),
659 ('TemporaryRedirect', 307),
660 ('TokenRefreshRequired', 400),
661 ('TooManyBuckets', 400),
662 ('UnexpectedContent', 400),
663 ('UnresolvableGrantByEmailAddress', 400),
664 ('UserKeyMustBeSpecified', 400)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200665 _add_matcher_class(BotoTestCase.s3_error_code.client,
666 code, base=ClientError)
667
668
669for code in (('InternalError', 500),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400670 ('NotImplemented', 501),
671 ('ServiceUnavailable', 503),
672 ('SlowDown', 503)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200673 _add_matcher_class(BotoTestCase.s3_error_code.server,
674 code, base=ServerError)