blob: cd35e7f0a8efa2e4565b7118b123801f03a12afd [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
Doug Hellmann583ce2c2015-03-11 14:55:46 +000026from oslo_log import log as logging
Matthew Treinish96e9e882014-06-09 18:37:19 -040027import six
Matthew Treinisha83a16e2012-12-07 13:44:02 -050028
Andrea Frittoli32d74992015-03-06 17:01:07 +000029from tempest_lib import exceptions as lib_exc
30
Attila Fazekas234d3e82013-02-22 16:39:49 +010031import tempest.clients
Masayuki Igawa224a8272014-02-17 15:07:43 +090032from tempest.common.utils import file_utils
Sean Dague86bd8422013-12-20 09:56:44 -050033from tempest import config
Attila Fazekas40aa3612013-01-19 22:16:38 +010034from tempest import exceptions
Attila Fazekasdc216422013-01-29 15:12:14 +010035import tempest.test
Masayuki Igawa224a8272014-02-17 15:07:43 +090036from tempest.thirdparty.boto.utils import wait
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):
Masayuki Igawa224a8272014-02-17 15:07:43 +090050 return all(map(file_utils.have_effective_read_access, args))
Attila Fazekas234d3e82013-02-22 16:39:49 +010051
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
Matthew Treinishc795b9e2014-06-09 17:01:10 -040060 # suppress logging for boto
61 boto_logger.logger.setLevel(orig_logging.CRITICAL)
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")
Andrea Frittoli32d74992015-03-06 17:01:07 +000069 # NOTE(andreaf) Setting up an extra manager here is redundant,
70 # and should be removed.
Attila Fazekas234d3e82013-02-22 16:39:49 +010071 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
Andrea Frittoli32d74992015-03-06 17:01:07 +000083 except lib_exc.Unauthorized:
Attila Fazekas234d3e82013-02-22 16:39:49 +010084 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
Attila Fazekas62aebc42015-03-12 07:38:19 +010085 " also failed to get it from keystone"
Attila Fazekas234d3e82013-02-22 16:39:49 +010086 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)
Attila Fazekas62aebc42015-03-12 07:38:19 +0100100 except lib_exc.Unauthorized:
Attila Fazekas234d3e82013-02-22 16:39:49 +0100101 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
Matt Riedemann78b0f802015-02-09 14:06:07 -0800102 " failed 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):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700114 """:returns: Returns with an error string if it does not match,
115 returns with None when it matches.
Attila Fazekas55c597d2014-02-21 13:10:41 +0100116 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100117 if not isinstance(exc, exception.BotoServerError):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200118 return "%r not an BotoServerError instance" % exc
119 LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
120 if re.match(self.STATUS_RE, str(exc.status)) is None:
121 return ("Status code (%s) does not match"
122 "the expected re pattern \"%s\""
123 % (exc.status, self.STATUS_RE))
124 if re.match(self.CODE_RE, str(exc.error_code)) is None:
125 return ("Error code (%s) does not match" +
126 "the expected re pattern \"%s\"") %\
127 (exc.error_code, self.CODE_RE)
Attila Fazekas55c597d2014-02-21 13:10:41 +0100128 return None
Attila Fazekasa23f5002012-10-23 19:32:45 +0200129
130
131class ClientError(BotoExceptionMatcher):
132 STATUS_RE = r'4\d\d'
133
134
135class ServerError(BotoExceptionMatcher):
136 STATUS_RE = r'5\d\d'
137
138
139def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
140 """
141 Usable for adding an ExceptionMatcher(s) into the exception tree.
142 The not leaf elements does wildcard match
143 """
144 # in error_code just literal and '.' characters expected
llg821243b20502014-02-22 10:32:49 +0800145 if not isinstance(error_data, six.string_types):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200146 (error_code, status_code) = map(str, error_data)
147 else:
148 status_code = None
149 error_code = error_data
150 parts = error_code.split('.')
151 basematch = ""
152 num_parts = len(parts)
153 max_index = num_parts - 1
154 add_cls = error_cls
llg821243b20502014-02-22 10:32:49 +0800155 for i_part in six.moves.xrange(num_parts):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200156 part = parts[i_part]
157 leaf = i_part == max_index
158 if not leaf:
159 match = basematch + part + "[.].*"
160 else:
161 match = basematch + part
162
163 basematch += part + "[.]"
164 if not hasattr(add_cls, part):
165 cls_dict = {"CODE_RE": match}
166 if leaf and status_code is not None:
167 cls_dict["STATUS_RE"] = status_code
168 cls = type(part, (base, ), cls_dict)
169 setattr(add_cls, part, cls())
170 add_cls = cls
171 elif leaf:
172 raise LookupError("Tries to redefine an error code \"%s\"" % part)
173 else:
174 add_cls = getattr(add_cls, part)
175
176
Attila Fazekas3e381f72013-08-01 16:52:23 +0200177# TODO(afazekas): classmethod handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200178def friendly_function_name_simple(call_able):
179 name = ""
180 if hasattr(call_able, "im_class"):
181 name += call_able.im_class.__name__ + "."
182 name += call_able.__name__
183 return name
184
185
186def friendly_function_call_str(call_able, *args, **kwargs):
187 string = friendly_function_name_simple(call_able)
188 string += "(" + ", ".join(map(str, args))
189 if len(kwargs):
190 if len(args):
191 string += ", "
192 string += ", ".join("=".join(map(str, (key, value)))
Matthew Treinish1d14c542014-06-17 20:25:40 -0400193 for (key, value) in kwargs.items())
Attila Fazekasa23f5002012-10-23 19:32:45 +0200194 return string + ")"
195
196
Attila Fazekasdc216422013-01-29 15:12:14 +0100197class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500198 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030199
Attila Fazekasa23f5002012-10-23 19:32:45 +0200200 @classmethod
Matthew Treinishdfd7ac02015-02-09 17:47:31 -0500201 def skip_checks(cls):
202 super(BotoTestCase, cls).skip_checks()
203 if not CONF.compute_feature_enabled.ec2_api:
204 raise cls.skipException("The EC2 API is not available")
Andrea Frittoli046953c2015-03-12 11:20:04 +0000205 if not CONF.identity_feature_enabled.api_v2 or \
206 not CONF.identity.auth_version == 'v2':
207 raise cls.skipException("Identity v2 is not available")
Matthew Treinishdfd7ac02015-02-09 17:47:31 -0500208
209 @classmethod
Emily Hugenbruche252a4a2015-02-27 15:43:12 +0000210 def setup_credentials(cls):
211 super(BotoTestCase, cls).setup_credentials()
212 cls.os = cls.get_client_manager()
213
214 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100215 def resource_setup(cls):
216 super(BotoTestCase, cls).resource_setup()
Matthew Treinishf054a9c2013-10-28 21:34:47 -0400217 cls.conclusion = decision_maker()
Attila Fazekasa23f5002012-10-23 19:32:45 +0200218 # The trash contains cleanup functions and paramaters in tuples
219 # (function, *args, **kwargs)
220 cls._resource_trash_bin = {}
221 cls._sequence = -1
222 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100223 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800224 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100225 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200226 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100227 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800228 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100229 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200230
231 @classmethod
232 def addResourceCleanUp(cls, function, *args, **kwargs):
233 """Adds CleanUp callable, used by tearDownClass.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100234 Recommended to a use (deep)copy on the mutable args.
235 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200236 cls._sequence = cls._sequence + 1
237 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
238 return cls._sequence
239
240 @classmethod
241 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500242 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200243 del cls._resource_trash_bin[key]
244
Attila Fazekas3e381f72013-08-01 16:52:23 +0200245 # TODO(afazekas): Add "with" context handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200246 def assertBotoError(self, excMatcher, callableObj,
247 *args, **kwargs):
248 """Example usage:
249 self.assertBotoError(self.ec2_error_code.client.
250 InvalidKeyPair.Duplicate,
251 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100252 key_name)
253 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200254 try:
255 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100256 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200257 error_msg = excMatcher.match(exc)
258 if error_msg is not None:
259 raise self.failureException, error_msg
260 else:
261 raise self.failureException, "BotoServerError not raised"
262
263 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100264 def resource_cleanup(cls):
Attila Fazekasb2902af2013-02-16 16:22:44 +0100265 """Calls the callables added by addResourceCleanUp,
Yair Frieda039f872014-01-02 12:11:10 +0200266 when you overwrite this function don't forget to call this too.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100267 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200268 fail_count = 0
269 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
270 for key in trash_keys:
271 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
272 try:
Yair Frieda039f872014-01-02 12:11:10 +0200273 func_name = friendly_function_call_str(function, *pos_args,
274 **kw_args)
275 LOG.debug("Cleaning up: %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200276 function(*pos_args, **kw_args)
Yair Frieda039f872014-01-02 12:11:10 +0200277 except BaseException:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200278 fail_count += 1
Yair Frieda039f872014-01-02 12:11:10 +0200279 LOG.exception("Cleanup failed %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200280 finally:
281 del cls._resource_trash_bin[key]
Andrea Frittoli29fea352014-09-15 13:31:14 +0100282 super(BotoTestCase, cls).resource_cleanup()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200283 # NOTE(afazekas): let the super called even on exceptions
284 # The real exceptions already logged, if the super throws another,
285 # does not causes hidden issues
Attila Fazekasa23f5002012-10-23 19:32:45 +0200286 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100287 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200288
289 ec2_error_code = BotoExceptionMatcher()
290 # InsufficientInstanceCapacity can be both server and client error
291 ec2_error_code.server = ServerError()
292 ec2_error_code.client = ClientError()
293 s3_error_code = BotoExceptionMatcher()
294 s3_error_code.server = ServerError()
295 s3_error_code.client = ClientError()
296 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekas3e381f72013-08-01 16:52:23 +0200297 # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
Attila Fazekasc66ee652013-01-31 06:56:13 +0100298 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200299 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100300 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200301 valid_volume_status = set(('creating', 'available', 'in-use',
302 'deleting', 'deleted', 'error'))
303 valid_snapshot_status = set(('pending', 'completed', 'error'))
304
Attila Fazekas37f83042013-01-12 16:13:03 +0100305 gone_set = set(('_GONE',))
306
Attila Fazekas40aa3612013-01-19 22:16:38 +0100307 @classmethod
308 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400309 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100310 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400311 is the function.
312 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100313 ec = cls.ec2_error_code
314 if isinstance(obj, ec2.instance.Instance):
315 colusure_matcher = ec.client.InvalidInstanceID.NotFound
316 status_attr = "state"
317 elif isinstance(obj, ec2.image.Image):
318 colusure_matcher = ec.client.InvalidAMIID.NotFound
319 status_attr = "state"
320 elif isinstance(obj, ec2.snapshot.Snapshot):
321 colusure_matcher = ec.client.InvalidSnapshot.NotFound
322 status_attr = "status"
323 elif isinstance(obj, ec2.volume.Volume):
324 colusure_matcher = ec.client.InvalidVolume.NotFound
325 status_attr = "status"
326 else:
327 return obj
328
329 def _status():
330 try:
331 obj.update(validate=True)
332 except ValueError:
333 return "_GONE"
334 except exception.EC2ResponseError as exc:
Attila Fazekas55c597d2014-02-21 13:10:41 +0100335 if colusure_matcher.match(exc) is None:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100336 return "_GONE"
337 else:
338 raise
339 return getattr(obj, status_attr)
340
341 return _status
342
Attila Fazekas37f83042013-01-12 16:13:03 +0100343 def state_wait_gone(self, lfunction, final_set, valid_set):
344 if not isinstance(final_set, set):
345 final_set = set((final_set,))
346 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100347 lfunction = self.get_lfunction_gone(lfunction)
Masayuki Igawa224a8272014-02-17 15:07:43 +0900348 state = wait.state_wait(lfunction, final_set, valid_set)
Attila Fazekas37f83042013-01-12 16:13:03 +0100349 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200350 return state
351
Attila Fazekas37f83042013-01-12 16:13:03 +0100352 def waitImageState(self, lfunction, wait_for):
353 return self.state_wait_gone(lfunction, wait_for,
354 self.valid_image_state)
355
Attila Fazekasa23f5002012-10-23 19:32:45 +0200356 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100357 return self.state_wait_gone(lfunction, wait_for,
358 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200359
Attila Fazekasa23f5002012-10-23 19:32:45 +0200360 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100361 return self.state_wait_gone(lfunction, wait_for,
362 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200363
Attila Fazekas40aa3612013-01-19 22:16:38 +0100364 def waitVolumeStatus(self, lfunction, wait_for):
365 return self.state_wait_gone(lfunction, wait_for,
366 self.valid_volume_status)
367
Attila Fazekasa23f5002012-10-23 19:32:45 +0200368 def assertImageStateWait(self, lfunction, wait_for):
369 state = self.waitImageState(lfunction, wait_for)
370 self.assertIn(state, wait_for)
371
372 def assertInstanceStateWait(self, lfunction, wait_for):
373 state = self.waitInstanceState(lfunction, wait_for)
374 self.assertIn(state, wait_for)
375
376 def assertVolumeStatusWait(self, lfunction, wait_for):
377 state = self.waitVolumeStatus(lfunction, wait_for)
378 self.assertIn(state, wait_for)
379
380 def assertSnapshotStatusWait(self, lfunction, wait_for):
381 state = self.waitSnapshotStatus(lfunction, wait_for)
382 self.assertIn(state, wait_for)
383
384 def assertAddressDissasociatedWait(self, address):
385
386 def _disassociate():
387 cli = self.ec2_client
388 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
389 if len(addresses) != 1:
390 return "INVALID"
391 if addresses[0].instance_id:
392 LOG.info("%s associated to %s",
393 address.public_ip,
394 addresses[0].instance_id)
395 return "ASSOCIATED"
396 return "DISASSOCIATED"
397
Masayuki Igawa224a8272014-02-17 15:07:43 +0900398 state = wait.state_wait(_disassociate, "DISASSOCIATED",
399 set(("ASSOCIATED", "DISASSOCIATED")))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200400 self.assertEqual(state, "DISASSOCIATED")
401
402 def assertAddressReleasedWait(self, address):
403
404 def _address_delete():
Attila Fazekas3e381f72013-08-01 16:52:23 +0200405 # NOTE(afazekas): the filter gives back IP
Attila Fazekasa23f5002012-10-23 19:32:45 +0200406 # even if it is not associated to my tenant
407 if (address.public_ip not in map(lambda a: a.public_ip,
408 self.ec2_client.get_all_addresses())):
409 return "DELETED"
410 return "NOTDELETED"
411
Masayuki Igawa224a8272014-02-17 15:07:43 +0900412 state = wait.state_wait(_address_delete, "DELETED")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200413 self.assertEqual(state, "DELETED")
414
415 def assertReSearch(self, regexp, string):
416 if re.search(regexp, string) is None:
417 raise self.failureException("regexp: '%s' not found in '%s'" %
418 (regexp, string))
419
420 def assertNotReSearch(self, regexp, string):
421 if re.search(regexp, string) is not None:
422 raise self.failureException("regexp: '%s' found in '%s'" %
423 (regexp, string))
424
425 def assertReMatch(self, regexp, string):
426 if re.match(regexp, string) is None:
427 raise self.failureException("regexp: '%s' not matches on '%s'" %
428 (regexp, string))
429
430 def assertNotReMatch(self, regexp, string):
431 if re.match(regexp, string) is not None:
432 raise self.failureException("regexp: '%s' matches on '%s'" %
433 (regexp, string))
434
435 @classmethod
436 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500437 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200438 exc_num = 0
439 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700440 with contextlib.closing(
441 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200442 if isinstance(bucket, basestring):
443 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100444 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200445 for obj in bucket.list():
446 try:
447 bucket.delete_key(obj.key)
448 obj.close()
Yair Frieda039f872014-01-02 12:11:10 +0200449 except BaseException:
450 LOG.exception("Failed to delete key %s " % obj.key)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200451 exc_num += 1
452 conn.delete_bucket(bucket)
Yair Frieda039f872014-01-02 12:11:10 +0200453 except BaseException:
454 LOG.exception("Failed to destroy bucket %s " % bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200455 exc_num += 1
456 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100457 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200458
459 @classmethod
460 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500461 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200462 exc_num = 0
463
464 def _instance_state():
465 try:
466 instance.update(validate=True)
467 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100468 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100469 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100470 if cls.ec2_error_code.\
Attila Fazekas55c597d2014-02-21 13:10:41 +0100471 client.InvalidInstanceID.NotFound.match(exc) is None:
Attila Fazekas37f83042013-01-12 16:13:03 +0100472 return "_GONE"
Attila Fazekas3e381f72013-08-01 16:52:23 +0200473 # NOTE(afazekas): incorrect code,
Attila Fazekas37f83042013-01-12 16:13:03 +0100474 # but the resource must be destoreyd
475 if exc.error_code == "InstanceNotFound":
476 return "_GONE"
477
Attila Fazekasa23f5002012-10-23 19:32:45 +0200478 return instance.state
479
480 for instance in reservation.instances:
481 try:
482 instance.terminate()
Masayuki Igawa224a8272014-02-17 15:07:43 +0900483 wait.re_search_wait(_instance_state, "_GONE")
Yair Frieda039f872014-01-02 12:11:10 +0200484 except BaseException:
485 LOG.exception("Failed to terminate instance %s " % instance)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200486 exc_num += 1
487 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100488 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200489
Attila Fazekas3e381f72013-08-01 16:52:23 +0200490 # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
Attila Fazekasa23f5002012-10-23 19:32:45 +0200491 # to write better teardown
492
493 @classmethod
494 def destroy_security_group_wait(cls, group):
495 """Delete group.
496 Use just for teardown!
497 """
Attila Fazekas3e381f72013-08-01 16:52:23 +0200498 # NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200499 group.delete()
500
501 @classmethod
502 def destroy_volume_wait(cls, volume):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700503 """Delete volume, tries to detach first.
Attila Fazekasa23f5002012-10-23 19:32:45 +0200504 Use just for teardown!
505 """
506 exc_num = 0
507 snaps = volume.snapshots()
508 if len(snaps):
509 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100510 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200511
Attila Fazekas3e381f72013-08-01 16:52:23 +0200512 # NOTE(afazekas): detaching/attching not valid EC2 status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200513 def _volume_state():
514 volume.update(validate=True)
515 try:
ghanshyam308640c2014-10-14 17:23:31 +0900516 # NOTE(gmann): Make sure volume is attached.
517 # Checking status as 'not "available"' is not enough to make
518 # sure volume is attached as it can be in "error" state
519 if volume.status == "in-use":
Attila Fazekasa23f5002012-10-23 19:32:45 +0200520 volume.detach(force=True)
Yair Frieda039f872014-01-02 12:11:10 +0200521 except BaseException:
522 LOG.exception("Failed to detach volume %s" % volume)
Attila Fazekas3e381f72013-08-01 16:52:23 +0200523 # exc_num += 1 "nonlocal" not in python2
Attila Fazekasa23f5002012-10-23 19:32:45 +0200524 return volume.status
525
526 try:
Masayuki Igawa224a8272014-02-17 15:07:43 +0900527 wait.re_search_wait(_volume_state, "available")
528 # not validates status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200529 LOG.info(_volume_state())
530 volume.delete()
Yair Frieda039f872014-01-02 12:11:10 +0200531 except BaseException:
532 LOG.exception("Failed to delete volume %s" % volume)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200533 exc_num += 1
534 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100535 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200536
537 @classmethod
538 def destroy_snapshot_wait(cls, snapshot):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700539 """delete snapshot, wait until it ceases to exist."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200540 snapshot.delete()
541
542 def _update():
543 snapshot.update(validate=True)
544
Masayuki Igawa224a8272014-02-17 15:07:43 +0900545 wait.wait_exception(_update)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200546
547
548# you can specify tuples if you want to specify the status pattern
549for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
550 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
551 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
552 'Gateway.NotAttached', 'IdempotentParameterMismatch',
553 'IncorrectInstanceState', 'IncorrectState',
554 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
555 'InsufficientReservedInstancesCapacity',
556 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
557 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
558 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
559 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
560 'InvalidCustomerGateway.DuplicateIpAddress',
561 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
562 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
563 'InvalidFilter', 'InvalidGatewayID.NotFound',
564 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
565 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
566 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
567 'InvalidInstanceID.NotFound',
568 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
569 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
570 'InvalidKeyPair.NotFound', 'InvalidManifest',
571 'InvalidNetworkAclEntry.NotFound',
572 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
573 'InvalidParameterValue', 'InvalidPermission.Duplicate',
574 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
575 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
576 'InvalidRouteTableID.NotFound',
577 'InvalidSecurity.RequestHasExpired',
578 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
579 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
580 'InvalidReservedInstancesOfferingId',
581 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
582 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
583 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
584 'InvalidVpnConnectionID.NotFound',
585 'InvalidVpnGatewayID.NotFound',
586 'InvalidZone.NotFound', 'LegacySecurityGroup',
587 'MissingParameter', 'NetworkAclEntryAlreadyExists',
588 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
589 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
590 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
591 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
592 'ResourceLimitExceeded', 'RouteAlreadyExists',
593 'RouteLimitExceeded', 'RouteTableLimitExceeded',
594 'RulesPerSecurityGroupLimitExceeded',
595 'SecurityGroupLimitExceeded',
596 'SecurityGroupsPerInstanceLimitExceeded',
597 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
598 'UnknownParameter', 'UnsupportedOperation',
599 'VolumeLimitExceeded', 'VpcLimitExceeded',
600 'VpnConnectionLimitExceeded',
601 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
602 _add_matcher_class(BotoTestCase.ec2_error_code.client,
603 code, base=ClientError)
604
605for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
606 'InsufficientReservedInstanceCapacity', 'InternalError',
607 'Unavailable'):
608 _add_matcher_class(BotoTestCase.ec2_error_code.server,
609 code, base=ServerError)
610
611
612for code in (('AccessDenied', 403),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400613 ('AccountProblem', 403),
614 ('AmbiguousGrantByEmailAddress', 400),
615 ('BadDigest', 400),
616 ('BucketAlreadyExists', 409),
617 ('BucketAlreadyOwnedByYou', 409),
618 ('BucketNotEmpty', 409),
619 ('CredentialsNotSupported', 400),
620 ('CrossLocationLoggingProhibited', 403),
621 ('EntityTooSmall', 400),
622 ('EntityTooLarge', 400),
623 ('ExpiredToken', 400),
624 ('IllegalVersioningConfigurationException', 400),
625 ('IncompleteBody', 400),
626 ('IncorrectNumberOfFilesInPostRequest', 400),
627 ('InlineDataTooLarge', 400),
628 ('InvalidAccessKeyId', 403),
Attila Fazekasa23f5002012-10-23 19:32:45 +0200629 'InvalidAddressingHeader',
Matthew Treinish1d14c542014-06-17 20:25:40 -0400630 ('InvalidArgument', 400),
631 ('InvalidBucketName', 400),
632 ('InvalidBucketState', 409),
633 ('InvalidDigest', 400),
634 ('InvalidLocationConstraint', 400),
635 ('InvalidPart', 400),
636 ('InvalidPartOrder', 400),
637 ('InvalidPayer', 403),
638 ('InvalidPolicyDocument', 400),
639 ('InvalidRange', 416),
640 ('InvalidRequest', 400),
641 ('InvalidSecurity', 403),
642 ('InvalidSOAPRequest', 400),
643 ('InvalidStorageClass', 400),
644 ('InvalidTargetBucketForLogging', 400),
645 ('InvalidToken', 400),
646 ('InvalidURI', 400),
647 ('KeyTooLong', 400),
648 ('MalformedACLError', 400),
649 ('MalformedPOSTRequest', 400),
650 ('MalformedXML', 400),
651 ('MaxMessageLengthExceeded', 400),
652 ('MaxPostPreDataLengthExceededError', 400),
653 ('MetadataTooLarge', 400),
654 ('MethodNotAllowed', 405),
655 ('MissingAttachment'),
656 ('MissingContentLength', 411),
657 ('MissingRequestBodyError', 400),
658 ('MissingSecurityElement', 400),
659 ('MissingSecurityHeader', 400),
660 ('NoLoggingStatusForKey', 400),
661 ('NoSuchBucket', 404),
662 ('NoSuchKey', 404),
663 ('NoSuchLifecycleConfiguration', 404),
664 ('NoSuchUpload', 404),
665 ('NoSuchVersion', 404),
666 ('NotSignedUp', 403),
667 ('NotSuchBucketPolicy', 404),
668 ('OperationAborted', 409),
669 ('PermanentRedirect', 301),
670 ('PreconditionFailed', 412),
671 ('Redirect', 307),
672 ('RequestIsNotMultiPartContent', 400),
673 ('RequestTimeout', 400),
674 ('RequestTimeTooSkewed', 403),
675 ('RequestTorrentOfBucketError', 400),
676 ('SignatureDoesNotMatch', 403),
677 ('TemporaryRedirect', 307),
678 ('TokenRefreshRequired', 400),
679 ('TooManyBuckets', 400),
680 ('UnexpectedContent', 400),
681 ('UnresolvableGrantByEmailAddress', 400),
682 ('UserKeyMustBeSpecified', 400)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200683 _add_matcher_class(BotoTestCase.s3_error_code.client,
684 code, base=ClientError)
685
686
687for code in (('InternalError', 500),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400688 ('NotImplemented', 501),
689 ('ServiceUnavailable', 503),
690 ('SlowDown', 503)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200691 _add_matcher_class(BotoTestCase.s3_error_code.server,
692 code, base=ServerError)