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