Merge lp:~corey.bryant/charms/trusty/nova-cloud-controller/sync-charm-helpers into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next
- Trusty Tahr (14.04)
- sync-charm-helpers
- Merge into next
Proposed by
Corey Bryant
Status: | Merged |
---|---|
Merged at revision: | 90 |
Proposed branch: | lp:~corey.bryant/charms/trusty/nova-cloud-controller/sync-charm-helpers |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/next |
Diff against target: |
689 lines (+265/-89) 7 files modified
Makefile (+1/-1) hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+13/-7) hooks/charmhelpers/contrib/openstack/amulet/utils.py (+86/-20) tests/charmhelpers/contrib/amulet/deployment.py (+20/-7) tests/charmhelpers/contrib/amulet/utils.py (+46/-27) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+13/-7) tests/charmhelpers/contrib/openstack/amulet/utils.py (+86/-20) |
To merge this branch: | bzr merge lp:~corey.bryant/charms/trusty/nova-cloud-controller/sync-charm-helpers |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack Charmers | Pending | ||
Review via email: mp+228879@code.launchpad.net |
Commit message
Description of the change
The amulet tests are broken without this.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2014-07-28 14:41:41 +0000 |
3 | +++ Makefile 2014-07-30 15:24:19 +0000 |
4 | @@ -20,7 +20,7 @@ |
5 | # https://bugs.launchpad.net/amulet/+bug/1320357 |
6 | @juju test -v -p AMULET_HTTP_PROXY |
7 | |
8 | -sync: |
9 | +sync: bin/charm_helpers_sync.py |
10 | @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml |
11 | @$(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-tests.yaml |
12 | |
13 | |
14 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' |
15 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-28 11:38:42 +0000 |
16 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-30 15:24:19 +0000 |
17 | @@ -4,8 +4,11 @@ |
18 | |
19 | |
20 | class OpenStackAmuletDeployment(AmuletDeployment): |
21 | - """This class inherits from AmuletDeployment and has additional support |
22 | - that is specifically for use by OpenStack charms.""" |
23 | + """OpenStack amulet deployment. |
24 | + |
25 | + This class inherits from AmuletDeployment and has additional support |
26 | + that is specifically for use by OpenStack charms. |
27 | + """ |
28 | |
29 | def __init__(self, series=None, openstack=None, source=None): |
30 | """Initialize the deployment environment.""" |
31 | @@ -40,11 +43,14 @@ |
32 | self.d.configure(service, config) |
33 | |
34 | def _get_openstack_release(self): |
35 | - """Return an integer representing the enum value of the openstack |
36 | - release.""" |
37 | - self.precise_essex, self.precise_folsom, self.precise_grizzly, \ |
38 | - self.precise_havana, self.precise_icehouse, \ |
39 | - self.trusty_icehouse = range(6) |
40 | + """Get openstack release. |
41 | + |
42 | + Return an integer representing the enum value of the openstack |
43 | + release. |
44 | + """ |
45 | + (self.precise_essex, self.precise_folsom, self.precise_grizzly, |
46 | + self.precise_havana, self.precise_icehouse, |
47 | + self.trusty_icehouse) = range(6) |
48 | releases = { |
49 | ('precise', None): self.precise_essex, |
50 | ('precise', 'cloud:precise-folsom'): self.precise_folsom, |
51 | |
52 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' |
53 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-28 11:38:42 +0000 |
54 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-30 15:24:19 +0000 |
55 | @@ -16,8 +16,11 @@ |
56 | |
57 | |
58 | class OpenStackAmuletUtils(AmuletUtils): |
59 | - """This class inherits from AmuletUtils and has additional support |
60 | - that is specifically for use by OpenStack charms.""" |
61 | + """OpenStack amulet utilities. |
62 | + |
63 | + This class inherits from AmuletUtils and has additional support |
64 | + that is specifically for use by OpenStack charms. |
65 | + """ |
66 | |
67 | def __init__(self, log_level=ERROR): |
68 | """Initialize the deployment environment.""" |
69 | @@ -25,13 +28,17 @@ |
70 | |
71 | def validate_endpoint_data(self, endpoints, admin_port, internal_port, |
72 | public_port, expected): |
73 | - """Validate actual endpoint data vs expected endpoint data. The ports |
74 | - are used to find the matching endpoint.""" |
75 | + """Validate endpoint data. |
76 | + |
77 | + Validate actual endpoint data vs expected endpoint data. The ports |
78 | + are used to find the matching endpoint. |
79 | + """ |
80 | found = False |
81 | for ep in endpoints: |
82 | self.log.debug('endpoint: {}'.format(repr(ep))) |
83 | - if admin_port in ep.adminurl and internal_port in ep.internalurl \ |
84 | - and public_port in ep.publicurl: |
85 | + if (admin_port in ep.adminurl and |
86 | + internal_port in ep.internalurl and |
87 | + public_port in ep.publicurl): |
88 | found = True |
89 | actual = {'id': ep.id, |
90 | 'region': ep.region, |
91 | @@ -47,8 +54,11 @@ |
92 | return 'endpoint not found' |
93 | |
94 | def validate_svc_catalog_endpoint_data(self, expected, actual): |
95 | - """Validate a list of actual service catalog endpoints vs a list of |
96 | - expected service catalog endpoints.""" |
97 | + """Validate service catalog endpoint data. |
98 | + |
99 | + Validate a list of actual service catalog endpoints vs a list of |
100 | + expected service catalog endpoints. |
101 | + """ |
102 | self.log.debug('actual: {}'.format(repr(actual))) |
103 | for k, v in expected.iteritems(): |
104 | if k in actual: |
105 | @@ -60,8 +70,11 @@ |
106 | return ret |
107 | |
108 | def validate_tenant_data(self, expected, actual): |
109 | - """Validate a list of actual tenant data vs list of expected tenant |
110 | - data.""" |
111 | + """Validate tenant data. |
112 | + |
113 | + Validate a list of actual tenant data vs list of expected tenant |
114 | + data. |
115 | + """ |
116 | self.log.debug('actual: {}'.format(repr(actual))) |
117 | for e in expected: |
118 | found = False |
119 | @@ -78,8 +91,11 @@ |
120 | return ret |
121 | |
122 | def validate_role_data(self, expected, actual): |
123 | - """Validate a list of actual role data vs a list of expected role |
124 | - data.""" |
125 | + """Validate role data. |
126 | + |
127 | + Validate a list of actual role data vs a list of expected role |
128 | + data. |
129 | + """ |
130 | self.log.debug('actual: {}'.format(repr(actual))) |
131 | for e in expected: |
132 | found = False |
133 | @@ -95,8 +111,11 @@ |
134 | return ret |
135 | |
136 | def validate_user_data(self, expected, actual): |
137 | - """Validate a list of actual user data vs a list of expected user |
138 | - data.""" |
139 | + """Validate user data. |
140 | + |
141 | + Validate a list of actual user data vs a list of expected user |
142 | + data. |
143 | + """ |
144 | self.log.debug('actual: {}'.format(repr(actual))) |
145 | for e in expected: |
146 | found = False |
147 | @@ -114,21 +133,24 @@ |
148 | return ret |
149 | |
150 | def validate_flavor_data(self, expected, actual): |
151 | - """Validate a list of actual flavors vs a list of expected flavors.""" |
152 | + """Validate flavor data. |
153 | + |
154 | + Validate a list of actual flavors vs a list of expected flavors. |
155 | + """ |
156 | self.log.debug('actual: {}'.format(repr(actual))) |
157 | act = [a.name for a in actual] |
158 | return self._validate_list_data(expected, act) |
159 | |
160 | def tenant_exists(self, keystone, tenant): |
161 | - """Return True if tenant exists""" |
162 | + """Return True if tenant exists.""" |
163 | return tenant in [t.name for t in keystone.tenants.list()] |
164 | |
165 | def authenticate_keystone_admin(self, keystone_sentry, user, password, |
166 | tenant): |
167 | """Authenticates admin user with the keystone admin endpoint.""" |
168 | - service_ip = \ |
169 | - keystone_sentry.relation('shared-db', |
170 | - 'mysql:shared-db')['private-address'] |
171 | + unit = keystone_sentry |
172 | + service_ip = unit.relation('shared-db', |
173 | + 'mysql:shared-db')['private-address'] |
174 | ep = "http://{}:35357/v2.0".format(service_ip.strip().decode('utf-8')) |
175 | return keystone_client.Client(username=user, password=password, |
176 | tenant_name=tenant, auth_url=ep) |
177 | @@ -177,12 +199,40 @@ |
178 | image = glance.images.create(name=image_name, is_public=True, |
179 | disk_format='qcow2', |
180 | container_format='bare', data=f) |
181 | + count = 1 |
182 | + status = image.status |
183 | + while status != 'active' and count < 10: |
184 | + time.sleep(3) |
185 | + image = glance.images.get(image.id) |
186 | + status = image.status |
187 | + self.log.debug('image status: {}'.format(status)) |
188 | + count += 1 |
189 | + |
190 | + if status != 'active': |
191 | + self.log.error('image creation timed out') |
192 | + return None |
193 | + |
194 | return image |
195 | |
196 | def delete_image(self, glance, image): |
197 | """Delete the specified image.""" |
198 | + num_before = len(list(glance.images.list())) |
199 | glance.images.delete(image) |
200 | |
201 | + count = 1 |
202 | + num_after = len(list(glance.images.list())) |
203 | + while num_after != (num_before - 1) and count < 10: |
204 | + time.sleep(3) |
205 | + num_after = len(list(glance.images.list())) |
206 | + self.log.debug('number of images: {}'.format(num_after)) |
207 | + count += 1 |
208 | + |
209 | + if num_after != (num_before - 1): |
210 | + self.log.error('image deletion timed out') |
211 | + return False |
212 | + |
213 | + return True |
214 | + |
215 | def create_instance(self, nova, image_name, instance_name, flavor): |
216 | """Create the specified instance.""" |
217 | image = nova.images.find(name=image_name) |
218 | @@ -199,11 +249,27 @@ |
219 | self.log.debug('instance status: {}'.format(status)) |
220 | count += 1 |
221 | |
222 | - if status == 'BUILD': |
223 | + if status != 'ACTIVE': |
224 | + self.log.error('instance creation timed out') |
225 | return None |
226 | |
227 | return instance |
228 | |
229 | def delete_instance(self, nova, instance): |
230 | """Delete the specified instance.""" |
231 | + num_before = len(list(nova.servers.list())) |
232 | nova.servers.delete(instance) |
233 | + |
234 | + count = 1 |
235 | + num_after = len(list(nova.servers.list())) |
236 | + while num_after != (num_before - 1) and count < 10: |
237 | + time.sleep(3) |
238 | + num_after = len(list(nova.servers.list())) |
239 | + self.log.debug('number of instances: {}'.format(num_after)) |
240 | + count += 1 |
241 | + |
242 | + if num_after != (num_before - 1): |
243 | + self.log.error('instance deletion timed out') |
244 | + return False |
245 | + |
246 | + return True |
247 | |
248 | === modified file 'tests/charmhelpers/contrib/amulet/deployment.py' |
249 | --- tests/charmhelpers/contrib/amulet/deployment.py 2014-07-28 11:38:42 +0000 |
250 | +++ tests/charmhelpers/contrib/amulet/deployment.py 2014-07-30 15:24:19 +0000 |
251 | @@ -1,9 +1,14 @@ |
252 | import amulet |
253 | |
254 | +import os |
255 | + |
256 | |
257 | class AmuletDeployment(object): |
258 | - """This class provides generic Amulet deployment and test runner |
259 | - methods.""" |
260 | + """Amulet deployment. |
261 | + |
262 | + This class provides generic Amulet deployment and test runner |
263 | + methods. |
264 | + """ |
265 | |
266 | def __init__(self, series=None): |
267 | """Initialize the deployment environment.""" |
268 | @@ -16,11 +21,19 @@ |
269 | self.d = amulet.Deployment() |
270 | |
271 | def _add_services(self, this_service, other_services): |
272 | - """Add services to the deployment where this_service is the local charm |
273 | + """Add services. |
274 | + |
275 | + Add services to the deployment where this_service is the local charm |
276 | that we're focused on testing and other_services are the other |
277 | - charms that come from the charm store.""" |
278 | + charms that come from the charm store. |
279 | + """ |
280 | name, units = range(2) |
281 | - self.this_service = this_service[name] |
282 | + |
283 | + if this_service[name] != os.path.basename(os.getcwd()): |
284 | + s = this_service[name] |
285 | + msg = "The charm's root directory name needs to be {}".format(s) |
286 | + amulet.raise_status(amulet.FAIL, msg=msg) |
287 | + |
288 | self.d.add(this_service[name], units=this_service[units]) |
289 | |
290 | for svc in other_services: |
291 | @@ -45,10 +58,10 @@ |
292 | """Deploy environment and wait for all hooks to finish executing.""" |
293 | try: |
294 | self.d.setup() |
295 | - self.d.sentry.wait() |
296 | + self.d.sentry.wait(timeout=900) |
297 | except amulet.helpers.TimeoutError: |
298 | amulet.raise_status(amulet.FAIL, msg="Deployment timed out") |
299 | - except: |
300 | + except Exception: |
301 | raise |
302 | |
303 | def run_tests(self): |
304 | |
305 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' |
306 | --- tests/charmhelpers/contrib/amulet/utils.py 2014-07-28 11:38:42 +0000 |
307 | +++ tests/charmhelpers/contrib/amulet/utils.py 2014-07-30 15:24:19 +0000 |
308 | @@ -3,12 +3,15 @@ |
309 | import logging |
310 | import re |
311 | import sys |
312 | -from time import sleep |
313 | +import time |
314 | |
315 | |
316 | class AmuletUtils(object): |
317 | - """This class provides common utility functions that are used by Amulet |
318 | - tests.""" |
319 | + """Amulet utilities. |
320 | + |
321 | + This class provides common utility functions that are used by Amulet |
322 | + tests. |
323 | + """ |
324 | |
325 | def __init__(self, log_level=logging.ERROR): |
326 | self.log = self.get_logger(level=log_level) |
327 | @@ -17,8 +20,8 @@ |
328 | """Get a logger object that will log to stdout.""" |
329 | log = logging |
330 | logger = log.getLogger(name) |
331 | - fmt = \ |
332 | - log.Formatter("%(asctime)s %(funcName)s %(levelname)s: %(message)s") |
333 | + fmt = log.Formatter("%(asctime)s %(funcName)s " |
334 | + "%(levelname)s: %(message)s") |
335 | |
336 | handler = log.StreamHandler(stream=sys.stdout) |
337 | handler.setLevel(level) |
338 | @@ -38,7 +41,7 @@ |
339 | def valid_url(self, url): |
340 | p = re.compile( |
341 | r'^(?:http|ftp)s?://' |
342 | - r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # flake8: noqa |
343 | + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # noqa |
344 | r'localhost|' |
345 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' |
346 | r'(?::\d+)?' |
347 | @@ -50,8 +53,11 @@ |
348 | return False |
349 | |
350 | def validate_services(self, commands): |
351 | - """Verify the specified services are running on the corresponding |
352 | - service units.""" |
353 | + """Validate services. |
354 | + |
355 | + Verify the specified services are running on the corresponding |
356 | + service units. |
357 | + """ |
358 | for k, v in commands.iteritems(): |
359 | for cmd in v: |
360 | output, code = k.run(cmd) |
361 | @@ -66,9 +72,13 @@ |
362 | config.readfp(io.StringIO(file_contents)) |
363 | return config |
364 | |
365 | - def validate_config_data(self, sentry_unit, config_file, section, expected): |
366 | - """Verify that the specified section of the config file contains |
367 | - the expected option key:value pairs.""" |
368 | + def validate_config_data(self, sentry_unit, config_file, section, |
369 | + expected): |
370 | + """Validate config file data. |
371 | + |
372 | + Verify that the specified section of the config file contains |
373 | + the expected option key:value pairs. |
374 | + """ |
375 | config = self._get_config(sentry_unit, config_file) |
376 | |
377 | if section != 'DEFAULT' and not config.has_section(section): |
378 | @@ -78,20 +88,23 @@ |
379 | if not config.has_option(section, k): |
380 | return "section [{}] is missing option {}".format(section, k) |
381 | if config.get(section, k) != expected[k]: |
382 | - return "section [{}] {}:{} != expected {}:{}".format(section, |
383 | - k, config.get(section, k), k, expected[k]) |
384 | + return "section [{}] {}:{} != expected {}:{}".format( |
385 | + section, k, config.get(section, k), k, expected[k]) |
386 | return None |
387 | |
388 | def _validate_dict_data(self, expected, actual): |
389 | - """Compare expected dictionary data vs actual dictionary data. |
390 | + """Validate dictionary data. |
391 | + |
392 | + Compare expected dictionary data vs actual dictionary data. |
393 | The values in the 'expected' dictionary can be strings, bools, ints, |
394 | longs, or can be a function that evaluate a variable and returns a |
395 | - bool.""" |
396 | + bool. |
397 | + """ |
398 | for k, v in expected.iteritems(): |
399 | if k in actual: |
400 | - if isinstance(v, basestring) or \ |
401 | - isinstance(v, bool) or \ |
402 | - isinstance(v, (int, long)): |
403 | + if (isinstance(v, basestring) or |
404 | + isinstance(v, bool) or |
405 | + isinstance(v, (int, long))): |
406 | if v != actual[k]: |
407 | return "{}:{}".format(k, actual[k]) |
408 | elif not v(actual[k]): |
409 | @@ -114,7 +127,7 @@ |
410 | return None |
411 | |
412 | def not_null(self, string): |
413 | - if string != None: |
414 | + if string is not None: |
415 | return True |
416 | else: |
417 | return False |
418 | @@ -128,9 +141,12 @@ |
419 | return sentry_unit.directory_stat(directory)['mtime'] |
420 | |
421 | def _get_proc_start_time(self, sentry_unit, service, pgrep_full=False): |
422 | - """Determine start time of the process based on the last modification |
423 | + """Get process' start time. |
424 | + |
425 | + Determine start time of the process based on the last modification |
426 | time of the /proc/pid directory. If pgrep_full is True, the process |
427 | - name is matched against the full command line.""" |
428 | + name is matched against the full command line. |
429 | + """ |
430 | if pgrep_full: |
431 | cmd = 'pgrep -o -f {}'.format(service) |
432 | else: |
433 | @@ -139,13 +155,16 @@ |
434 | return self._get_dir_mtime(sentry_unit, proc_dir) |
435 | |
436 | def service_restarted(self, sentry_unit, service, filename, |
437 | - pgrep_full=False): |
438 | - """Compare a service's start time vs a file's last modification time |
439 | + pgrep_full=False, sleep_time=20): |
440 | + """Check if service was restarted. |
441 | + |
442 | + Compare a service's start time vs a file's last modification time |
443 | (such as a config file for that service) to determine if the service |
444 | - has been restarted.""" |
445 | - sleep(10) |
446 | - if self._get_proc_start_time(sentry_unit, service, pgrep_full) >= \ |
447 | - self._get_file_mtime(sentry_unit, filename): |
448 | + has been restarted. |
449 | + """ |
450 | + time.sleep(sleep_time) |
451 | + if (self._get_proc_start_time(sentry_unit, service, pgrep_full) >= |
452 | + self._get_file_mtime(sentry_unit, filename)): |
453 | return True |
454 | else: |
455 | return False |
456 | |
457 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' |
458 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-28 11:38:42 +0000 |
459 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2014-07-30 15:24:19 +0000 |
460 | @@ -4,8 +4,11 @@ |
461 | |
462 | |
463 | class OpenStackAmuletDeployment(AmuletDeployment): |
464 | - """This class inherits from AmuletDeployment and has additional support |
465 | - that is specifically for use by OpenStack charms.""" |
466 | + """OpenStack amulet deployment. |
467 | + |
468 | + This class inherits from AmuletDeployment and has additional support |
469 | + that is specifically for use by OpenStack charms. |
470 | + """ |
471 | |
472 | def __init__(self, series=None, openstack=None, source=None): |
473 | """Initialize the deployment environment.""" |
474 | @@ -40,11 +43,14 @@ |
475 | self.d.configure(service, config) |
476 | |
477 | def _get_openstack_release(self): |
478 | - """Return an integer representing the enum value of the openstack |
479 | - release.""" |
480 | - self.precise_essex, self.precise_folsom, self.precise_grizzly, \ |
481 | - self.precise_havana, self.precise_icehouse, \ |
482 | - self.trusty_icehouse = range(6) |
483 | + """Get openstack release. |
484 | + |
485 | + Return an integer representing the enum value of the openstack |
486 | + release. |
487 | + """ |
488 | + (self.precise_essex, self.precise_folsom, self.precise_grizzly, |
489 | + self.precise_havana, self.precise_icehouse, |
490 | + self.trusty_icehouse) = range(6) |
491 | releases = { |
492 | ('precise', None): self.precise_essex, |
493 | ('precise', 'cloud:precise-folsom'): self.precise_folsom, |
494 | |
495 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' |
496 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-28 11:38:42 +0000 |
497 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2014-07-30 15:24:19 +0000 |
498 | @@ -16,8 +16,11 @@ |
499 | |
500 | |
501 | class OpenStackAmuletUtils(AmuletUtils): |
502 | - """This class inherits from AmuletUtils and has additional support |
503 | - that is specifically for use by OpenStack charms.""" |
504 | + """OpenStack amulet utilities. |
505 | + |
506 | + This class inherits from AmuletUtils and has additional support |
507 | + that is specifically for use by OpenStack charms. |
508 | + """ |
509 | |
510 | def __init__(self, log_level=ERROR): |
511 | """Initialize the deployment environment.""" |
512 | @@ -25,13 +28,17 @@ |
513 | |
514 | def validate_endpoint_data(self, endpoints, admin_port, internal_port, |
515 | public_port, expected): |
516 | - """Validate actual endpoint data vs expected endpoint data. The ports |
517 | - are used to find the matching endpoint.""" |
518 | + """Validate endpoint data. |
519 | + |
520 | + Validate actual endpoint data vs expected endpoint data. The ports |
521 | + are used to find the matching endpoint. |
522 | + """ |
523 | found = False |
524 | for ep in endpoints: |
525 | self.log.debug('endpoint: {}'.format(repr(ep))) |
526 | - if admin_port in ep.adminurl and internal_port in ep.internalurl \ |
527 | - and public_port in ep.publicurl: |
528 | + if (admin_port in ep.adminurl and |
529 | + internal_port in ep.internalurl and |
530 | + public_port in ep.publicurl): |
531 | found = True |
532 | actual = {'id': ep.id, |
533 | 'region': ep.region, |
534 | @@ -47,8 +54,11 @@ |
535 | return 'endpoint not found' |
536 | |
537 | def validate_svc_catalog_endpoint_data(self, expected, actual): |
538 | - """Validate a list of actual service catalog endpoints vs a list of |
539 | - expected service catalog endpoints.""" |
540 | + """Validate service catalog endpoint data. |
541 | + |
542 | + Validate a list of actual service catalog endpoints vs a list of |
543 | + expected service catalog endpoints. |
544 | + """ |
545 | self.log.debug('actual: {}'.format(repr(actual))) |
546 | for k, v in expected.iteritems(): |
547 | if k in actual: |
548 | @@ -60,8 +70,11 @@ |
549 | return ret |
550 | |
551 | def validate_tenant_data(self, expected, actual): |
552 | - """Validate a list of actual tenant data vs list of expected tenant |
553 | - data.""" |
554 | + """Validate tenant data. |
555 | + |
556 | + Validate a list of actual tenant data vs list of expected tenant |
557 | + data. |
558 | + """ |
559 | self.log.debug('actual: {}'.format(repr(actual))) |
560 | for e in expected: |
561 | found = False |
562 | @@ -78,8 +91,11 @@ |
563 | return ret |
564 | |
565 | def validate_role_data(self, expected, actual): |
566 | - """Validate a list of actual role data vs a list of expected role |
567 | - data.""" |
568 | + """Validate role data. |
569 | + |
570 | + Validate a list of actual role data vs a list of expected role |
571 | + data. |
572 | + """ |
573 | self.log.debug('actual: {}'.format(repr(actual))) |
574 | for e in expected: |
575 | found = False |
576 | @@ -95,8 +111,11 @@ |
577 | return ret |
578 | |
579 | def validate_user_data(self, expected, actual): |
580 | - """Validate a list of actual user data vs a list of expected user |
581 | - data.""" |
582 | + """Validate user data. |
583 | + |
584 | + Validate a list of actual user data vs a list of expected user |
585 | + data. |
586 | + """ |
587 | self.log.debug('actual: {}'.format(repr(actual))) |
588 | for e in expected: |
589 | found = False |
590 | @@ -114,21 +133,24 @@ |
591 | return ret |
592 | |
593 | def validate_flavor_data(self, expected, actual): |
594 | - """Validate a list of actual flavors vs a list of expected flavors.""" |
595 | + """Validate flavor data. |
596 | + |
597 | + Validate a list of actual flavors vs a list of expected flavors. |
598 | + """ |
599 | self.log.debug('actual: {}'.format(repr(actual))) |
600 | act = [a.name for a in actual] |
601 | return self._validate_list_data(expected, act) |
602 | |
603 | def tenant_exists(self, keystone, tenant): |
604 | - """Return True if tenant exists""" |
605 | + """Return True if tenant exists.""" |
606 | return tenant in [t.name for t in keystone.tenants.list()] |
607 | |
608 | def authenticate_keystone_admin(self, keystone_sentry, user, password, |
609 | tenant): |
610 | """Authenticates admin user with the keystone admin endpoint.""" |
611 | - service_ip = \ |
612 | - keystone_sentry.relation('shared-db', |
613 | - 'mysql:shared-db')['private-address'] |
614 | + unit = keystone_sentry |
615 | + service_ip = unit.relation('shared-db', |
616 | + 'mysql:shared-db')['private-address'] |
617 | ep = "http://{}:35357/v2.0".format(service_ip.strip().decode('utf-8')) |
618 | return keystone_client.Client(username=user, password=password, |
619 | tenant_name=tenant, auth_url=ep) |
620 | @@ -177,12 +199,40 @@ |
621 | image = glance.images.create(name=image_name, is_public=True, |
622 | disk_format='qcow2', |
623 | container_format='bare', data=f) |
624 | + count = 1 |
625 | + status = image.status |
626 | + while status != 'active' and count < 10: |
627 | + time.sleep(3) |
628 | + image = glance.images.get(image.id) |
629 | + status = image.status |
630 | + self.log.debug('image status: {}'.format(status)) |
631 | + count += 1 |
632 | + |
633 | + if status != 'active': |
634 | + self.log.error('image creation timed out') |
635 | + return None |
636 | + |
637 | return image |
638 | |
639 | def delete_image(self, glance, image): |
640 | """Delete the specified image.""" |
641 | + num_before = len(list(glance.images.list())) |
642 | glance.images.delete(image) |
643 | |
644 | + count = 1 |
645 | + num_after = len(list(glance.images.list())) |
646 | + while num_after != (num_before - 1) and count < 10: |
647 | + time.sleep(3) |
648 | + num_after = len(list(glance.images.list())) |
649 | + self.log.debug('number of images: {}'.format(num_after)) |
650 | + count += 1 |
651 | + |
652 | + if num_after != (num_before - 1): |
653 | + self.log.error('image deletion timed out') |
654 | + return False |
655 | + |
656 | + return True |
657 | + |
658 | def create_instance(self, nova, image_name, instance_name, flavor): |
659 | """Create the specified instance.""" |
660 | image = nova.images.find(name=image_name) |
661 | @@ -199,11 +249,27 @@ |
662 | self.log.debug('instance status: {}'.format(status)) |
663 | count += 1 |
664 | |
665 | - if status == 'BUILD': |
666 | + if status != 'ACTIVE': |
667 | + self.log.error('instance creation timed out') |
668 | return None |
669 | |
670 | return instance |
671 | |
672 | def delete_instance(self, nova, instance): |
673 | """Delete the specified instance.""" |
674 | + num_before = len(list(nova.servers.list())) |
675 | nova.servers.delete(instance) |
676 | + |
677 | + count = 1 |
678 | + num_after = len(list(nova.servers.list())) |
679 | + while num_after != (num_before - 1) and count < 10: |
680 | + time.sleep(3) |
681 | + num_after = len(list(nova.servers.list())) |
682 | + self.log.debug('number of instances: {}'.format(num_after)) |
683 | + count += 1 |
684 | + |
685 | + if num_after != (num_before - 1): |
686 | + self.log.error('instance deletion timed out') |
687 | + return False |
688 | + |
689 | + return True |