blob: 9f119b4533817d05f913e533df6722bc29883648 [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 Fazekasa23f5002012-10-23 19:32:45 +020020
Matthew Treinisha83a16e2012-12-07 13:44:02 -050021import boto
Attila Fazekas40aa3612013-01-19 22:16:38 +010022from boto import ec2
23from boto import exception
24from boto import s3
Doug Hellmann583ce2c2015-03-11 14:55:46 +000025from oslo_log import log as logging
Matthew Treinish96e9e882014-06-09 18:37:19 -040026import six
Matthew Treinishf077dd22015-04-23 09:37:41 -040027from six.moves.urllib import parse as urlparse
Andrea Frittoli32d74992015-03-06 17:01:07 +000028from tempest_lib import exceptions as lib_exc
29
Attila Fazekas234d3e82013-02-22 16:39:49 +010030import tempest.clients
Masayuki Igawa224a8272014-02-17 15:07:43 +090031from tempest.common.utils import file_utils
Sean Dague86bd8422013-12-20 09:56:44 -050032from tempest import config
Attila Fazekas40aa3612013-01-19 22:16:38 +010033from tempest import exceptions
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")
Andrea Frittoli32d74992015-03-06 17:01:07 +000068 # NOTE(andreaf) Setting up an extra manager here is redundant,
69 # and should be removed.
Attila Fazekas234d3e82013-02-22 16:39:49 +010070 openstack = tempest.clients.Manager()
71 try:
Sean Dague86bd8422013-12-20 09:56:44 -050072 if urlparse.urlparse(CONF.boto.ec2_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010073 raise Exception("Failed to get hostname from the ec2_url")
74 ec2client = openstack.ec2api_client
75 try:
76 ec2client.get_all_regions()
77 except exception.BotoServerError as exc:
78 if exc.error_code is None:
79 raise Exception("EC2 target does not looks EC2 service")
80 _cred_sub_check(ec2client.connection_data)
81
Andrea Frittoli32d74992015-03-06 17:01:07 +000082 except lib_exc.Unauthorized:
Attila Fazekas234d3e82013-02-22 16:39:49 +010083 EC2_CAN_CONNECT_ERROR = "AWS credentials not set," +\
Attila Fazekas62aebc42015-03-12 07:38:19 +010084 " also failed to get it from keystone"
Attila Fazekas234d3e82013-02-22 16:39:49 +010085 except Exception as exc:
86 EC2_CAN_CONNECT_ERROR = str(exc)
87
88 try:
Sean Dague86bd8422013-12-20 09:56:44 -050089 if urlparse.urlparse(CONF.boto.s3_url).hostname is None:
Attila Fazekas234d3e82013-02-22 16:39:49 +010090 raise Exception("Failed to get hostname from the s3_url")
91 s3client = openstack.s3_client
92 try:
93 s3client.get_bucket("^INVALID*#()@INVALID.")
94 except exception.BotoServerError as exc:
95 if exc.status == 403:
96 _cred_sub_check(s3client.connection_data)
97 except Exception as exc:
98 S3_CAN_CONNECT_ERROR = str(exc)
Attila Fazekas62aebc42015-03-12 07:38:19 +010099 except lib_exc.Unauthorized:
Attila Fazekas234d3e82013-02-22 16:39:49 +0100100 S3_CAN_CONNECT_ERROR = "AWS credentials not set," +\
Matt Riedemann78b0f802015-02-09 14:06:07 -0800101 " failed to get them even by keystoneclient"
Matthew Treinishf4a9b0f2013-07-26 16:58:26 -0400102 boto_logger.logger.setLevel(level)
Attila Fazekas234d3e82013-02-22 16:39:49 +0100103 return {'A_I_IMAGES_READY': A_I_IMAGES_READY,
104 'S3_CAN_CONNECT_ERROR': S3_CAN_CONNECT_ERROR,
105 'EC2_CAN_CONNECT_ERROR': EC2_CAN_CONNECT_ERROR}
106
107
Attila Fazekasa23f5002012-10-23 19:32:45 +0200108class BotoExceptionMatcher(object):
109 STATUS_RE = r'[45]\d\d'
110 CODE_RE = '.*' # regexp makes sense in group match
111
112 def match(self, exc):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700113 """:returns: Returns with an error string if it does not match,
114 returns with None when it matches.
Attila Fazekas55c597d2014-02-21 13:10:41 +0100115 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100116 if not isinstance(exc, exception.BotoServerError):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200117 return "%r not an BotoServerError instance" % exc
118 LOG.info("Status: %s , error_code: %s", exc.status, exc.error_code)
119 if re.match(self.STATUS_RE, str(exc.status)) is None:
120 return ("Status code (%s) does not match"
121 "the expected re pattern \"%s\""
122 % (exc.status, self.STATUS_RE))
123 if re.match(self.CODE_RE, str(exc.error_code)) is None:
124 return ("Error code (%s) does not match" +
125 "the expected re pattern \"%s\"") %\
126 (exc.error_code, self.CODE_RE)
Attila Fazekas55c597d2014-02-21 13:10:41 +0100127 return None
Attila Fazekasa23f5002012-10-23 19:32:45 +0200128
129
130class ClientError(BotoExceptionMatcher):
131 STATUS_RE = r'4\d\d'
132
133
134class ServerError(BotoExceptionMatcher):
135 STATUS_RE = r'5\d\d'
136
137
138def _add_matcher_class(error_cls, error_data, base=BotoExceptionMatcher):
139 """
140 Usable for adding an ExceptionMatcher(s) into the exception tree.
141 The not leaf elements does wildcard match
142 """
143 # in error_code just literal and '.' characters expected
llg821243b20502014-02-22 10:32:49 +0800144 if not isinstance(error_data, six.string_types):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200145 (error_code, status_code) = map(str, error_data)
146 else:
147 status_code = None
148 error_code = error_data
149 parts = error_code.split('.')
150 basematch = ""
151 num_parts = len(parts)
152 max_index = num_parts - 1
153 add_cls = error_cls
llg821243b20502014-02-22 10:32:49 +0800154 for i_part in six.moves.xrange(num_parts):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200155 part = parts[i_part]
156 leaf = i_part == max_index
157 if not leaf:
158 match = basematch + part + "[.].*"
159 else:
160 match = basematch + part
161
162 basematch += part + "[.]"
163 if not hasattr(add_cls, part):
164 cls_dict = {"CODE_RE": match}
165 if leaf and status_code is not None:
166 cls_dict["STATUS_RE"] = status_code
167 cls = type(part, (base, ), cls_dict)
168 setattr(add_cls, part, cls())
169 add_cls = cls
170 elif leaf:
171 raise LookupError("Tries to redefine an error code \"%s\"" % part)
172 else:
173 add_cls = getattr(add_cls, part)
174
175
Attila Fazekas3e381f72013-08-01 16:52:23 +0200176# TODO(afazekas): classmethod handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200177def friendly_function_name_simple(call_able):
178 name = ""
179 if hasattr(call_able, "im_class"):
180 name += call_able.im_class.__name__ + "."
181 name += call_able.__name__
182 return name
183
184
185def friendly_function_call_str(call_able, *args, **kwargs):
186 string = friendly_function_name_simple(call_able)
187 string += "(" + ", ".join(map(str, args))
188 if len(kwargs):
189 if len(args):
190 string += ", "
191 string += ", ".join("=".join(map(str, (key, value)))
Matthew Treinish1d14c542014-06-17 20:25:40 -0400192 for (key, value) in kwargs.items())
Attila Fazekasa23f5002012-10-23 19:32:45 +0200193 return string + ")"
194
195
Attila Fazekasdc216422013-01-29 15:12:14 +0100196class BotoTestCase(tempest.test.BaseTestCase):
Sean Daguef237ccb2013-01-04 15:19:14 -0500197 """Recommended to use as base class for boto related test."""
Chris Yeoh8a79b9d2013-01-18 19:32:47 +1030198
Andrea Frittolib21de6c2015-02-06 20:12:38 +0000199 credentials = ['primary']
200
Attila Fazekasa23f5002012-10-23 19:32:45 +0200201 @classmethod
Matthew Treinishdfd7ac02015-02-09 17:47:31 -0500202 def skip_checks(cls):
203 super(BotoTestCase, cls).skip_checks()
204 if not CONF.compute_feature_enabled.ec2_api:
205 raise cls.skipException("The EC2 API is not available")
Andrea Frittoli046953c2015-03-12 11:20:04 +0000206 if not CONF.identity_feature_enabled.api_v2 or \
207 not CONF.identity.auth_version == 'v2':
208 raise cls.skipException("Identity v2 is not available")
Matthew Treinishdfd7ac02015-02-09 17:47:31 -0500209
210 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100211 def resource_setup(cls):
212 super(BotoTestCase, cls).resource_setup()
Matthew Treinishf054a9c2013-10-28 21:34:47 -0400213 cls.conclusion = decision_maker()
Attila Fazekasa23f5002012-10-23 19:32:45 +0200214 # The trash contains cleanup functions and paramaters in tuples
215 # (function, *args, **kwargs)
216 cls._resource_trash_bin = {}
217 cls._sequence = -1
218 if (hasattr(cls, "EC2") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100219 cls.conclusion['EC2_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800220 raise cls.skipException("EC2 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100221 cls.conclusion['EC2_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200222 if (hasattr(cls, "S3") and
Attila Fazekas234d3e82013-02-22 16:39:49 +0100223 cls.conclusion['S3_CAN_CONNECT_ERROR'] is not None):
ivan-zhu1feeb382013-01-24 10:14:39 +0800224 raise cls.skipException("S3 " + cls.__name__ + ": " +
Attila Fazekas234d3e82013-02-22 16:39:49 +0100225 cls.conclusion['S3_CAN_CONNECT_ERROR'])
Attila Fazekasa23f5002012-10-23 19:32:45 +0200226
227 @classmethod
228 def addResourceCleanUp(cls, function, *args, **kwargs):
229 """Adds CleanUp callable, used by tearDownClass.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100230 Recommended to a use (deep)copy on the mutable args.
231 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200232 cls._sequence = cls._sequence + 1
233 cls._resource_trash_bin[cls._sequence] = (function, args, kwargs)
234 return cls._sequence
235
236 @classmethod
237 def cancelResourceCleanUp(cls, key):
Sean Daguef237ccb2013-01-04 15:19:14 -0500238 """Cancel Clean up request."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200239 del cls._resource_trash_bin[key]
240
Attila Fazekas3e381f72013-08-01 16:52:23 +0200241 # TODO(afazekas): Add "with" context handling
Attila Fazekasa23f5002012-10-23 19:32:45 +0200242 def assertBotoError(self, excMatcher, callableObj,
243 *args, **kwargs):
244 """Example usage:
245 self.assertBotoError(self.ec2_error_code.client.
246 InvalidKeyPair.Duplicate,
247 self.client.create_keypair,
Attila Fazekasb2902af2013-02-16 16:22:44 +0100248 key_name)
249 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200250 try:
251 callableObj(*args, **kwargs)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100252 except exception.BotoServerError as exc:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200253 error_msg = excMatcher.match(exc)
254 if error_msg is not None:
Matthew Treinish843227d2015-04-23 10:17:17 -0400255 raise self.failureException(error_msg)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200256 else:
Matthew Treinish843227d2015-04-23 10:17:17 -0400257 raise self.failureException("BotoServerError not raised")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200258
259 @classmethod
Andrea Frittoli29fea352014-09-15 13:31:14 +0100260 def resource_cleanup(cls):
Attila Fazekasb2902af2013-02-16 16:22:44 +0100261 """Calls the callables added by addResourceCleanUp,
Yair Frieda039f872014-01-02 12:11:10 +0200262 when you overwrite this function don't forget to call this too.
Attila Fazekasb2902af2013-02-16 16:22:44 +0100263 """
Attila Fazekasa23f5002012-10-23 19:32:45 +0200264 fail_count = 0
265 trash_keys = sorted(cls._resource_trash_bin, reverse=True)
266 for key in trash_keys:
267 (function, pos_args, kw_args) = cls._resource_trash_bin[key]
268 try:
Yair Frieda039f872014-01-02 12:11:10 +0200269 func_name = friendly_function_call_str(function, *pos_args,
270 **kw_args)
271 LOG.debug("Cleaning up: %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200272 function(*pos_args, **kw_args)
Yair Frieda039f872014-01-02 12:11:10 +0200273 except BaseException:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200274 fail_count += 1
Yair Frieda039f872014-01-02 12:11:10 +0200275 LOG.exception("Cleanup failed %s" % func_name)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200276 finally:
277 del cls._resource_trash_bin[key]
Andrea Frittoli29fea352014-09-15 13:31:14 +0100278 super(BotoTestCase, cls).resource_cleanup()
Attila Fazekasf86fa312013-07-30 19:56:39 +0200279 # NOTE(afazekas): let the super called even on exceptions
280 # The real exceptions already logged, if the super throws another,
281 # does not causes hidden issues
Attila Fazekasa23f5002012-10-23 19:32:45 +0200282 if fail_count:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100283 raise exceptions.TearDownException(num=fail_count)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200284
285 ec2_error_code = BotoExceptionMatcher()
286 # InsufficientInstanceCapacity can be both server and client error
287 ec2_error_code.server = ServerError()
288 ec2_error_code.client = ClientError()
289 s3_error_code = BotoExceptionMatcher()
290 s3_error_code.server = ServerError()
291 s3_error_code.client = ClientError()
292 valid_image_state = set(('available', 'pending', 'failed'))
Attila Fazekas3e381f72013-08-01 16:52:23 +0200293 # NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
Attila Fazekasc66ee652013-01-31 06:56:13 +0100294 # a good mapping, because it uses memory, but not really a running machine
Attila Fazekasa23f5002012-10-23 19:32:45 +0200295 valid_instance_state = set(('pending', 'running', 'shutting-down',
Attila Fazekasc66ee652013-01-31 06:56:13 +0100296 'terminated', 'stopping', 'stopped', 'paused'))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200297 valid_volume_status = set(('creating', 'available', 'in-use',
298 'deleting', 'deleted', 'error'))
299 valid_snapshot_status = set(('pending', 'completed', 'error'))
300
Attila Fazekas37f83042013-01-12 16:13:03 +0100301 gone_set = set(('_GONE',))
302
Attila Fazekas40aa3612013-01-19 22:16:38 +0100303 @classmethod
304 def get_lfunction_gone(cls, obj):
Sean Dague2416cf32013-04-10 08:29:07 -0400305 """If the object is instance of a well know type returns back with
Attila Fazekas40aa3612013-01-19 22:16:38 +0100306 with the correspoding function otherwise it assumes the obj itself
Sean Dague2416cf32013-04-10 08:29:07 -0400307 is the function.
308 """
Attila Fazekas40aa3612013-01-19 22:16:38 +0100309 ec = cls.ec2_error_code
310 if isinstance(obj, ec2.instance.Instance):
311 colusure_matcher = ec.client.InvalidInstanceID.NotFound
312 status_attr = "state"
313 elif isinstance(obj, ec2.image.Image):
314 colusure_matcher = ec.client.InvalidAMIID.NotFound
315 status_attr = "state"
316 elif isinstance(obj, ec2.snapshot.Snapshot):
317 colusure_matcher = ec.client.InvalidSnapshot.NotFound
318 status_attr = "status"
319 elif isinstance(obj, ec2.volume.Volume):
320 colusure_matcher = ec.client.InvalidVolume.NotFound
321 status_attr = "status"
322 else:
323 return obj
324
325 def _status():
326 try:
327 obj.update(validate=True)
328 except ValueError:
329 return "_GONE"
330 except exception.EC2ResponseError as exc:
Attila Fazekas55c597d2014-02-21 13:10:41 +0100331 if colusure_matcher.match(exc) is None:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100332 return "_GONE"
333 else:
334 raise
335 return getattr(obj, status_attr)
336
337 return _status
338
Attila Fazekas37f83042013-01-12 16:13:03 +0100339 def state_wait_gone(self, lfunction, final_set, valid_set):
340 if not isinstance(final_set, set):
341 final_set = set((final_set,))
342 final_set |= self.gone_set
Attila Fazekas40aa3612013-01-19 22:16:38 +0100343 lfunction = self.get_lfunction_gone(lfunction)
Masayuki Igawa224a8272014-02-17 15:07:43 +0900344 state = wait.state_wait(lfunction, final_set, valid_set)
Attila Fazekas37f83042013-01-12 16:13:03 +0100345 self.assertIn(state, valid_set | self.gone_set)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200346 return state
347
Attila Fazekas37f83042013-01-12 16:13:03 +0100348 def waitImageState(self, lfunction, wait_for):
349 return self.state_wait_gone(lfunction, wait_for,
350 self.valid_image_state)
351
Attila Fazekasa23f5002012-10-23 19:32:45 +0200352 def waitInstanceState(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100353 return self.state_wait_gone(lfunction, wait_for,
354 self.valid_instance_state)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200355
Attila Fazekasa23f5002012-10-23 19:32:45 +0200356 def waitSnapshotStatus(self, lfunction, wait_for):
Attila Fazekas37f83042013-01-12 16:13:03 +0100357 return self.state_wait_gone(lfunction, wait_for,
358 self.valid_snapshot_status)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200359
Attila Fazekas40aa3612013-01-19 22:16:38 +0100360 def waitVolumeStatus(self, lfunction, wait_for):
361 return self.state_wait_gone(lfunction, wait_for,
362 self.valid_volume_status)
363
Attila Fazekasa23f5002012-10-23 19:32:45 +0200364 def assertImageStateWait(self, lfunction, wait_for):
365 state = self.waitImageState(lfunction, wait_for)
366 self.assertIn(state, wait_for)
367
368 def assertInstanceStateWait(self, lfunction, wait_for):
369 state = self.waitInstanceState(lfunction, wait_for)
370 self.assertIn(state, wait_for)
371
372 def assertVolumeStatusWait(self, lfunction, wait_for):
373 state = self.waitVolumeStatus(lfunction, wait_for)
374 self.assertIn(state, wait_for)
375
376 def assertSnapshotStatusWait(self, lfunction, wait_for):
377 state = self.waitSnapshotStatus(lfunction, wait_for)
378 self.assertIn(state, wait_for)
379
380 def assertAddressDissasociatedWait(self, address):
381
382 def _disassociate():
383 cli = self.ec2_client
384 addresses = cli.get_all_addresses(addresses=(address.public_ip,))
385 if len(addresses) != 1:
386 return "INVALID"
387 if addresses[0].instance_id:
388 LOG.info("%s associated to %s",
389 address.public_ip,
390 addresses[0].instance_id)
391 return "ASSOCIATED"
392 return "DISASSOCIATED"
393
Masayuki Igawa224a8272014-02-17 15:07:43 +0900394 state = wait.state_wait(_disassociate, "DISASSOCIATED",
395 set(("ASSOCIATED", "DISASSOCIATED")))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200396 self.assertEqual(state, "DISASSOCIATED")
397
398 def assertAddressReleasedWait(self, address):
399
400 def _address_delete():
Attila Fazekas3e381f72013-08-01 16:52:23 +0200401 # NOTE(afazekas): the filter gives back IP
Attila Fazekasa23f5002012-10-23 19:32:45 +0200402 # even if it is not associated to my tenant
403 if (address.public_ip not in map(lambda a: a.public_ip,
404 self.ec2_client.get_all_addresses())):
405 return "DELETED"
406 return "NOTDELETED"
407
Masayuki Igawa224a8272014-02-17 15:07:43 +0900408 state = wait.state_wait(_address_delete, "DELETED")
Attila Fazekasa23f5002012-10-23 19:32:45 +0200409 self.assertEqual(state, "DELETED")
410
411 def assertReSearch(self, regexp, string):
412 if re.search(regexp, string) is None:
413 raise self.failureException("regexp: '%s' not found in '%s'" %
414 (regexp, string))
415
416 def assertNotReSearch(self, regexp, string):
417 if re.search(regexp, string) is not None:
418 raise self.failureException("regexp: '%s' found in '%s'" %
419 (regexp, string))
420
421 def assertReMatch(self, regexp, string):
422 if re.match(regexp, string) is None:
423 raise self.failureException("regexp: '%s' not matches on '%s'" %
424 (regexp, string))
425
426 def assertNotReMatch(self, regexp, string):
427 if re.match(regexp, string) is not None:
428 raise self.failureException("regexp: '%s' matches on '%s'" %
429 (regexp, string))
430
431 @classmethod
432 def destroy_bucket(cls, connection_data, bucket):
Sean Daguef237ccb2013-01-04 15:19:14 -0500433 """Destroys the bucket and its content, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200434 exc_num = 0
435 try:
Monty Taylorb2ca5ca2013-04-28 18:00:21 -0700436 with contextlib.closing(
437 boto.connect_s3(**connection_data)) as conn:
Attila Fazekasa23f5002012-10-23 19:32:45 +0200438 if isinstance(bucket, basestring):
439 bucket = conn.lookup(bucket)
Attila Fazekas40aa3612013-01-19 22:16:38 +0100440 assert isinstance(bucket, s3.bucket.Bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200441 for obj in bucket.list():
442 try:
443 bucket.delete_key(obj.key)
444 obj.close()
Yair Frieda039f872014-01-02 12:11:10 +0200445 except BaseException:
446 LOG.exception("Failed to delete key %s " % obj.key)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200447 exc_num += 1
448 conn.delete_bucket(bucket)
Yair Frieda039f872014-01-02 12:11:10 +0200449 except BaseException:
450 LOG.exception("Failed to destroy bucket %s " % bucket)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200451 exc_num += 1
452 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100453 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200454
455 @classmethod
456 def destroy_reservation(cls, reservation):
Sean Daguef237ccb2013-01-04 15:19:14 -0500457 """Terminate instances in a reservation, just for teardown."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200458 exc_num = 0
459
460 def _instance_state():
461 try:
462 instance.update(validate=True)
463 except ValueError:
Attila Fazekas37f83042013-01-12 16:13:03 +0100464 return "_GONE"
Attila Fazekas40aa3612013-01-19 22:16:38 +0100465 except exception.EC2ResponseError as exc:
Attila Fazekas37f83042013-01-12 16:13:03 +0100466 if cls.ec2_error_code.\
Attila Fazekas55c597d2014-02-21 13:10:41 +0100467 client.InvalidInstanceID.NotFound.match(exc) is None:
Attila Fazekas37f83042013-01-12 16:13:03 +0100468 return "_GONE"
Attila Fazekas3e381f72013-08-01 16:52:23 +0200469 # NOTE(afazekas): incorrect code,
Attila Fazekas37f83042013-01-12 16:13:03 +0100470 # but the resource must be destoreyd
471 if exc.error_code == "InstanceNotFound":
472 return "_GONE"
473
Attila Fazekasa23f5002012-10-23 19:32:45 +0200474 return instance.state
475
476 for instance in reservation.instances:
477 try:
478 instance.terminate()
Masayuki Igawa224a8272014-02-17 15:07:43 +0900479 wait.re_search_wait(_instance_state, "_GONE")
Yair Frieda039f872014-01-02 12:11:10 +0200480 except BaseException:
481 LOG.exception("Failed to terminate instance %s " % instance)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200482 exc_num += 1
483 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100484 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200485
Attila Fazekas3e381f72013-08-01 16:52:23 +0200486 # NOTE(afazekas): The incorrect ErrorCodes makes very, very difficult
Attila Fazekasa23f5002012-10-23 19:32:45 +0200487 # to write better teardown
488
489 @classmethod
490 def destroy_security_group_wait(cls, group):
491 """Delete group.
492 Use just for teardown!
493 """
Attila Fazekas3e381f72013-08-01 16:52:23 +0200494 # NOTE(afazekas): should wait/try until all related instance terminates
Attila Fazekasa23f5002012-10-23 19:32:45 +0200495 group.delete()
496
497 @classmethod
498 def destroy_volume_wait(cls, volume):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700499 """Delete volume, tries to detach first.
Attila Fazekasa23f5002012-10-23 19:32:45 +0200500 Use just for teardown!
501 """
502 exc_num = 0
503 snaps = volume.snapshots()
504 if len(snaps):
505 LOG.critical("%s Volume has %s snapshot(s)", volume.id,
Attila Fazekasfa756cb2013-02-12 10:52:42 +0100506 map(snaps.id, snaps))
Attila Fazekasa23f5002012-10-23 19:32:45 +0200507
Zhao Lei17548d92015-08-07 21:42:51 +0800508 # NOTE(afazekas): detaching/attaching not valid EC2 status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200509 def _volume_state():
510 volume.update(validate=True)
511 try:
ghanshyam308640c2014-10-14 17:23:31 +0900512 # NOTE(gmann): Make sure volume is attached.
513 # Checking status as 'not "available"' is not enough to make
514 # sure volume is attached as it can be in "error" state
515 if volume.status == "in-use":
Attila Fazekasa23f5002012-10-23 19:32:45 +0200516 volume.detach(force=True)
Yair Frieda039f872014-01-02 12:11:10 +0200517 except BaseException:
518 LOG.exception("Failed to detach volume %s" % volume)
Attila Fazekas3e381f72013-08-01 16:52:23 +0200519 # exc_num += 1 "nonlocal" not in python2
Attila Fazekasa23f5002012-10-23 19:32:45 +0200520 return volume.status
521
522 try:
Masayuki Igawa224a8272014-02-17 15:07:43 +0900523 wait.re_search_wait(_volume_state, "available")
524 # not validates status
Attila Fazekasa23f5002012-10-23 19:32:45 +0200525 LOG.info(_volume_state())
526 volume.delete()
Yair Frieda039f872014-01-02 12:11:10 +0200527 except BaseException:
528 LOG.exception("Failed to delete volume %s" % volume)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200529 exc_num += 1
530 if exc_num:
Attila Fazekas40aa3612013-01-19 22:16:38 +0100531 raise exceptions.TearDownException(num=exc_num)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200532
533 @classmethod
534 def destroy_snapshot_wait(cls, snapshot):
Rafael Riveroc61bec72014-09-18 15:49:20 -0700535 """delete snapshot, wait until it ceases to exist."""
Attila Fazekasa23f5002012-10-23 19:32:45 +0200536 snapshot.delete()
537
538 def _update():
539 snapshot.update(validate=True)
540
Masayuki Igawa224a8272014-02-17 15:07:43 +0900541 wait.wait_exception(_update)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200542
543
544# you can specify tuples if you want to specify the status pattern
545for code in ('AddressLimitExceeded', 'AttachmentLimitExceeded', 'AuthFailure',
546 'Blocked', 'CustomerGatewayLimitExceeded', 'DependencyViolation',
547 'DiskImageSizeTooLarge', 'FilterLimitExceeded',
548 'Gateway.NotAttached', 'IdempotentParameterMismatch',
549 'IncorrectInstanceState', 'IncorrectState',
550 'InstanceLimitExceeded', 'InsufficientInstanceCapacity',
551 'InsufficientReservedInstancesCapacity',
552 'InternetGatewayLimitExceeded', 'InvalidAMIAttributeItemValue',
553 'InvalidAMIID.Malformed', 'InvalidAMIID.NotFound',
554 'InvalidAMIID.Unavailable', 'InvalidAssociationID.NotFound',
555 'InvalidAttachment.NotFound', 'InvalidConversionTaskId',
556 'InvalidCustomerGateway.DuplicateIpAddress',
557 'InvalidCustomerGatewayID.NotFound', 'InvalidDevice.InUse',
558 'InvalidDhcpOptionsID.NotFound', 'InvalidFormat',
559 'InvalidFilter', 'InvalidGatewayID.NotFound',
560 'InvalidGroup.Duplicate', 'InvalidGroupId.Malformed',
561 'InvalidGroup.InUse', 'InvalidGroup.NotFound',
562 'InvalidGroup.Reserved', 'InvalidInstanceID.Malformed',
563 'InvalidInstanceID.NotFound',
564 'InvalidInternetGatewayID.NotFound', 'InvalidIPAddress.InUse',
565 'InvalidKeyPair.Duplicate', 'InvalidKeyPair.Format',
566 'InvalidKeyPair.NotFound', 'InvalidManifest',
567 'InvalidNetworkAclEntry.NotFound',
568 'InvalidNetworkAclID.NotFound', 'InvalidParameterCombination',
569 'InvalidParameterValue', 'InvalidPermission.Duplicate',
570 'InvalidPermission.Malformed', 'InvalidReservationID.Malformed',
571 'InvalidReservationID.NotFound', 'InvalidRoute.NotFound',
572 'InvalidRouteTableID.NotFound',
573 'InvalidSecurity.RequestHasExpired',
574 'InvalidSnapshotID.Malformed', 'InvalidSnapshot.NotFound',
575 'InvalidUserID.Malformed', 'InvalidReservedInstancesId',
576 'InvalidReservedInstancesOfferingId',
577 'InvalidSubnetID.NotFound', 'InvalidVolumeID.Duplicate',
578 'InvalidVolumeID.Malformed', 'InvalidVolumeID.ZoneMismatch',
579 'InvalidVolume.NotFound', 'InvalidVpcID.NotFound',
580 'InvalidVpnConnectionID.NotFound',
581 'InvalidVpnGatewayID.NotFound',
582 'InvalidZone.NotFound', 'LegacySecurityGroup',
583 'MissingParameter', 'NetworkAclEntryAlreadyExists',
584 'NetworkAclEntryLimitExceeded', 'NetworkAclLimitExceeded',
585 'NonEBSInstance', 'PendingSnapshotLimitExceeded',
586 'PendingVerification', 'OptInRequired', 'RequestLimitExceeded',
587 'ReservedInstancesLimitExceeded', 'Resource.AlreadyAssociated',
588 'ResourceLimitExceeded', 'RouteAlreadyExists',
589 'RouteLimitExceeded', 'RouteTableLimitExceeded',
590 'RulesPerSecurityGroupLimitExceeded',
591 'SecurityGroupLimitExceeded',
592 'SecurityGroupsPerInstanceLimitExceeded',
593 'SnapshotLimitExceeded', 'SubnetLimitExceeded',
594 'UnknownParameter', 'UnsupportedOperation',
595 'VolumeLimitExceeded', 'VpcLimitExceeded',
596 'VpnConnectionLimitExceeded',
597 'VpnGatewayAttachmentLimitExceeded', 'VpnGatewayLimitExceeded'):
598 _add_matcher_class(BotoTestCase.ec2_error_code.client,
599 code, base=ClientError)
600
601for code in ('InsufficientAddressCapacity', 'InsufficientInstanceCapacity',
602 'InsufficientReservedInstanceCapacity', 'InternalError',
603 'Unavailable'):
604 _add_matcher_class(BotoTestCase.ec2_error_code.server,
605 code, base=ServerError)
606
607
608for code in (('AccessDenied', 403),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400609 ('AccountProblem', 403),
610 ('AmbiguousGrantByEmailAddress', 400),
611 ('BadDigest', 400),
612 ('BucketAlreadyExists', 409),
613 ('BucketAlreadyOwnedByYou', 409),
614 ('BucketNotEmpty', 409),
615 ('CredentialsNotSupported', 400),
616 ('CrossLocationLoggingProhibited', 403),
617 ('EntityTooSmall', 400),
618 ('EntityTooLarge', 400),
619 ('ExpiredToken', 400),
620 ('IllegalVersioningConfigurationException', 400),
621 ('IncompleteBody', 400),
622 ('IncorrectNumberOfFilesInPostRequest', 400),
623 ('InlineDataTooLarge', 400),
624 ('InvalidAccessKeyId', 403),
Attila Fazekasa23f5002012-10-23 19:32:45 +0200625 'InvalidAddressingHeader',
Matthew Treinish1d14c542014-06-17 20:25:40 -0400626 ('InvalidArgument', 400),
627 ('InvalidBucketName', 400),
628 ('InvalidBucketState', 409),
629 ('InvalidDigest', 400),
630 ('InvalidLocationConstraint', 400),
631 ('InvalidPart', 400),
632 ('InvalidPartOrder', 400),
633 ('InvalidPayer', 403),
634 ('InvalidPolicyDocument', 400),
635 ('InvalidRange', 416),
636 ('InvalidRequest', 400),
637 ('InvalidSecurity', 403),
638 ('InvalidSOAPRequest', 400),
639 ('InvalidStorageClass', 400),
640 ('InvalidTargetBucketForLogging', 400),
641 ('InvalidToken', 400),
642 ('InvalidURI', 400),
643 ('KeyTooLong', 400),
644 ('MalformedACLError', 400),
645 ('MalformedPOSTRequest', 400),
646 ('MalformedXML', 400),
647 ('MaxMessageLengthExceeded', 400),
648 ('MaxPostPreDataLengthExceededError', 400),
649 ('MetadataTooLarge', 400),
650 ('MethodNotAllowed', 405),
651 ('MissingAttachment'),
652 ('MissingContentLength', 411),
653 ('MissingRequestBodyError', 400),
654 ('MissingSecurityElement', 400),
655 ('MissingSecurityHeader', 400),
656 ('NoLoggingStatusForKey', 400),
657 ('NoSuchBucket', 404),
658 ('NoSuchKey', 404),
659 ('NoSuchLifecycleConfiguration', 404),
660 ('NoSuchUpload', 404),
661 ('NoSuchVersion', 404),
662 ('NotSignedUp', 403),
663 ('NotSuchBucketPolicy', 404),
664 ('OperationAborted', 409),
665 ('PermanentRedirect', 301),
666 ('PreconditionFailed', 412),
667 ('Redirect', 307),
668 ('RequestIsNotMultiPartContent', 400),
669 ('RequestTimeout', 400),
670 ('RequestTimeTooSkewed', 403),
671 ('RequestTorrentOfBucketError', 400),
672 ('SignatureDoesNotMatch', 403),
673 ('TemporaryRedirect', 307),
674 ('TokenRefreshRequired', 400),
675 ('TooManyBuckets', 400),
676 ('UnexpectedContent', 400),
677 ('UnresolvableGrantByEmailAddress', 400),
678 ('UserKeyMustBeSpecified', 400)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200679 _add_matcher_class(BotoTestCase.s3_error_code.client,
680 code, base=ClientError)
681
682
683for code in (('InternalError', 500),
Matthew Treinish1d14c542014-06-17 20:25:40 -0400684 ('NotImplemented', 501),
685 ('ServiceUnavailable', 503),
686 ('SlowDown', 503)):
Attila Fazekasa23f5002012-10-23 19:32:45 +0200687 _add_matcher_class(BotoTestCase.s3_error_code.server,
688 code, base=ServerError)