Merge lp:~1chb1n/charm-helpers/heat-amulet into lp:charm-helpers

Proposed by Ryan Beisner
Status: Merged
Merged at revision: 388
Proposed branch: lp:~1chb1n/charm-helpers/heat-amulet
Merge into: lp:charm-helpers
Diff against target: 481 lines (+227/-12)
4 files modified
charmhelpers/contrib/amulet/utils.py (+91/-6)
charmhelpers/contrib/openstack/amulet/deployment.py (+6/-2)
charmhelpers/contrib/openstack/amulet/utils.py (+122/-3)
charmhelpers/contrib/openstack/utils.py (+8/-1)
To merge this branch: bzr merge lp:~1chb1n/charm-helpers/heat-amulet
Reviewer Review Type Date Requested Status
Corey Bryant (community) Approve
James Page Pending
Review via email: mp+260723@code.launchpad.net

Description of the change

* Prep helpers for Wily/Liberty; resolve additional Vivid issues.

* Add heat client method.

* Add generic openstack resource status check and delete methods.

* Add create_or_get_keypair method.

* Add file_to_url method.

* Add get_ubuntu_releases - uses distro_info to create a list property containing Ubuntu release codenames, in order of release.

* Add get_ubuntu_release_from_sentry - return lsb_release codename from unit.

* Flag validate_services with deprecation warning. This method should be eventually be deprecated in favor of the following, after charm tests are updated to use validate_services_by_name.

* Add validate_services_by_name - uses a dictionary of units:service-names to check service status, automatically generating command based on init system type. Tests should now be updated to use this.

* Add debug logging.

Tracking bug: https://bugs.launchpad.net/charm-helpers/+bug/1461535

This should be landed in conjunction with the new heat amulet tests @ https://code.launchpad.net/~1chb1n/charms/trusty/heat/next-amulet-init/+merge/258105.

## resource status and resource delete rationale:
New and existing amulet tests can be made with less code effort or duplication.

Rather than having numerous delete_XYZ methods for each of the numerous openstack types/objects, use delete_resource and pass a pointer. Similarly, instead of having wait/check/timeout/confirm loops built into numerous methods, use resource_reaches_status + a pointer.

To post a comment you must log in.
Revision history for this message
Corey Bryant (corey.bryant) wrote :

The changes make sense to me. How about 'resource' instead of 'thing'? Something like delete_openstack_resource() seems like it would be understandable if someone was reading through code.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Also there's one comment inline.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Looks like there are also some lint errors.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

See that's why I ask you! Resource makes too much sense. Ack on the lint, already resolved locally. Also fully agree on the inline comment about comments. Thanks Corey!

Revision history for this message
Ryan Beisner (1chb1n) wrote :

FYI - all amulet tests pass except Vivid, due to bug 1461535. Addressing @ charmhelpers as well as the heat branch.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Looking good. I'd just remove the old validate_services() now and rename validate_services_by_name() to validate_services(), and land all the charm test updates at the same time.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

I'd prefer to not do that actually. I'd like to take it smaller chunks.

This is what I'm aiming for:

1. Land heat amulet tests, at the same time unblocking vivid for all of the other amulet tests.

2. Enable and land vivid coverage in all of the charms with existing amulet tests, while also updating each of them.

3. Deprecate the old validate_services after it is confirmed to be not-used.

4. Add amulet tests for charms which don't yet have them.

Whaddaya think?

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Also, rmq reply inline.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Oh, add to (3): Deprecate delete_image after the relevant tests have been updated to use delete_resource.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

> I'd prefer to not do that actually. I'd like to take it smaller chunks.
>
> This is what I'm aiming for:
>
> 1. Land heat amulet tests, at the same time unblocking vivid for all of the
> other amulet tests.
>
> 2. Enable and land vivid coverage in all of the charms with existing amulet
> tests, while also updating each of them.
>
> 3. Deprecate the old validate_services after it is confirmed to be not-used.
>
> 4. Add amulet tests for charms which don't yet have them.
>
> Whaddaya think?

I just prefer not to land code that's going to be pulled back out in a week. But I can't land to c-h anyway so it's not up to me.

Now that I think of it though, and more importantly, I don't know what the process is for deprecating functions that aren't openstack specific. Someone else might already be using them and some of these changes would break them. I wonder what the precedence is.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

I can definitely see your point, which raises more caution:

The original validate_services method isn't actually broken in any way. Misnamed, maybe, but not broken. It just runs commands and reports back pass/fail. If the test writer passes the proper service-checking (or other arbitrary) commands, it will happily run them. A more appropriate name for the method may have been something like run_these_commands_on_these_units(), as it's really not service-aware at all.

Maybe we shouldn't deprecate or change the behavior of validate_services at all, as there is no way to know who that will break.

I'd suggest that we:

leave validate_services untouched, not "fixed"

add validate_services_by_name, as proposed, and roll tests to it as we update each charm.

discuss and handle the topic of deprecation separately, if at all.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Good points. I think that sounds like a good plan, and keep your deprecation comments to steer folks to the new functions.

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Ok, thanks - let's proceed as such.

Just a reminder, the "create or get keypair" method will be movedto this MP once that's reworked from the heat MP.

lp:~1chb1n/charm-helpers/heat-amulet updated
385. By Ryan Beisner

Return validate_services to original functionality
Add create_or_get_keypair
Update comments and docstrings

386. By Ryan Beisner

Add Wily/Liberty awareness.

387. By Ryan Beisner

add file_to_url

Revision history for this message
Ryan Beisner (1chb1n) wrote :

Ready for review.

Revision history for this message
Corey Bryant (corey.bryant) wrote :

Looks good. 2 really minor inline comments.

review: Approve
lp:~1chb1n/charm-helpers/heat-amulet updated
388. By Ryan Beisner

light cleanup and comment correction

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmhelpers/contrib/amulet/utils.py'
--- charmhelpers/contrib/amulet/utils.py 2015-04-21 15:40:51 +0000
+++ charmhelpers/contrib/amulet/utils.py 2015-06-11 19:47:33 +0000
@@ -15,13 +15,15 @@
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import ConfigParser17import ConfigParser
18import distro_info
18import io19import io
19import logging20import logging
21import os
20import re22import re
23import six
21import sys24import sys
22import time25import time
2326import urlparse
24import six
2527
2628
27class AmuletUtils(object):29class AmuletUtils(object):
@@ -33,6 +35,7 @@
3335
34 def __init__(self, log_level=logging.ERROR):36 def __init__(self, log_level=logging.ERROR):
35 self.log = self.get_logger(level=log_level)37 self.log = self.get_logger(level=log_level)
38 self.ubuntu_releases = self.get_ubuntu_releases()
3639
37 def get_logger(self, name="amulet-logger", level=logging.DEBUG):40 def get_logger(self, name="amulet-logger", level=logging.DEBUG):
38 """Get a logger object that will log to stdout."""41 """Get a logger object that will log to stdout."""
@@ -70,12 +73,44 @@
70 else:73 else:
71 return False74 return False
7275
76 def get_ubuntu_release_from_sentry(self, sentry_unit):
77 """Get Ubuntu release codename from sentry unit.
78
79 :param sentry_unit: amulet sentry/service unit pointer
80 :returns: list of strings - release codename, failure message
81 """
82 msg = None
83 cmd = 'lsb_release -cs'
84 release, code = sentry_unit.run(cmd)
85 if code == 0:
86 self.log.debug('{} lsb_release: {}'.format(
87 sentry_unit.info['unit_name'], release))
88 else:
89 msg = ('{} `{}` returned {} '
90 '{}'.format(sentry_unit.info['unit_name'],
91 cmd, release, code))
92 if release not in self.ubuntu_releases:
93 msg = ("Release ({}) not found in Ubuntu releases "
94 "({})".format(release, self.ubuntu_releases))
95 return release, msg
96
73 def validate_services(self, commands):97 def validate_services(self, commands):
74 """Validate services.98 """Validate that lists of commands succeed on service units. Can be
7599 used to verify system services are running on the corresponding
76 Verify the specified services are running on the corresponding
77 service units.100 service units.
78 """101
102 :param commands: dict with sentry keys and arbitrary command list values
103 :returns: None if successful, Failure string message otherwise
104 """
105 self.log.debug('Checking status of system services...')
106
107 # /!\ DEPRECATION WARNING (beisner):
108 # New and existing tests should be rewritten to use
109 # validate_services_by_name() as it is aware of init systems.
110 self.log.warn('/!\\ DEPRECATION WARNING: use '
111 'validate_services_by_name instead of validate_services '
112 'due to init system differences.')
113
79 for k, v in six.iteritems(commands):114 for k, v in six.iteritems(commands):
80 for cmd in v:115 for cmd in v:
81 output, code = k.run(cmd)116 output, code = k.run(cmd)
@@ -86,6 +121,41 @@
86 return "command `{}` returned {}".format(cmd, str(code))121 return "command `{}` returned {}".format(cmd, str(code))
87 return None122 return None
88123
124 def validate_services_by_name(self, sentry_services):
125 """Validate system service status by service name, automatically
126 detecting init system based on Ubuntu release codename.
127
128 :param sentry_services: dict with sentry keys and svc list values
129 :returns: None if successful, Failure string message otherwise
130 """
131 self.log.debug('Checking status of system services...')
132
133 # Point at which systemd became a thing
134 systemd_switch = self.ubuntu_releases.index('vivid')
135
136 for sentry_unit, services_list in six.iteritems(sentry_services):
137 # Get lsb_release codename from unit
138 release, ret = self.get_ubuntu_release_from_sentry(sentry_unit)
139 if ret:
140 return ret
141
142 for service_name in services_list:
143 if (self.ubuntu_releases.index(release) >= systemd_switch or
144 service_name == "rabbitmq-server"):
145 # init is systemd
146 cmd = 'sudo service {} status'.format(service_name)
147 elif self.ubuntu_releases.index(release) < systemd_switch:
148 # init is upstart
149 cmd = 'sudo status {}'.format(service_name)
150
151 output, code = sentry_unit.run(cmd)
152 self.log.debug('{} `{}` returned '
153 '{}'.format(sentry_unit.info['unit_name'],
154 cmd, code))
155 if code != 0:
156 return "command `{}` returned {}".format(cmd, str(code))
157 return None
158
89 def _get_config(self, unit, filename):159 def _get_config(self, unit, filename):
90 """Get a ConfigParser object for parsing a unit's config file."""160 """Get a ConfigParser object for parsing a unit's config file."""
91 file_contents = unit.file_contents(filename)161 file_contents = unit.file_contents(filename)
@@ -104,6 +174,9 @@
104 Verify that the specified section of the config file contains174 Verify that the specified section of the config file contains
105 the expected option key:value pairs.175 the expected option key:value pairs.
106 """176 """
177 self.log.debug('Validating config file data ({} in {} on {})'
178 '...'.format(section, config_file,
179 sentry_unit.info['unit_name']))
107 config = self._get_config(sentry_unit, config_file)180 config = self._get_config(sentry_unit, config_file)
108181
109 if section != 'DEFAULT' and not config.has_section(section):182 if section != 'DEFAULT' and not config.has_section(section):
@@ -321,3 +394,15 @@
321394
322 def endpoint_error(self, name, data):395 def endpoint_error(self, name, data):
323 return 'unexpected endpoint data in {} - {}'.format(name, data)396 return 'unexpected endpoint data in {} - {}'.format(name, data)
397
398 def get_ubuntu_releases(self):
399 """Return a list of all Ubuntu releases in order of release."""
400 _d = distro_info.UbuntuDistroInfo()
401 _release_list = _d.all
402 self.log.debug('Ubuntu release list: {}'.format(_release_list))
403 return _release_list
404
405 def file_to_url(self, file_rel_path):
406 """Convert a relative file path to a file URL."""
407 _abs_path = os.path.abspath(file_rel_path)
408 return urlparse.urlparse(_abs_path, scheme='file').geturl()
324409
=== modified file 'charmhelpers/contrib/openstack/amulet/deployment.py'
--- charmhelpers/contrib/openstack/amulet/deployment.py 2015-04-21 15:40:51 +0000
+++ charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-11 19:47:33 +0000
@@ -110,7 +110,8 @@
110 (self.precise_essex, self.precise_folsom, self.precise_grizzly,110 (self.precise_essex, self.precise_folsom, self.precise_grizzly,
111 self.precise_havana, self.precise_icehouse,111 self.precise_havana, self.precise_icehouse,
112 self.trusty_icehouse, self.trusty_juno, self.utopic_juno,112 self.trusty_icehouse, self.trusty_juno, self.utopic_juno,
113 self.trusty_kilo, self.vivid_kilo) = range(10)113 self.trusty_kilo, self.vivid_kilo, self.trusty_liberty,
114 self.wily_liberty) = range(12)
114115
115 releases = {116 releases = {
116 ('precise', None): self.precise_essex,117 ('precise', None): self.precise_essex,
@@ -121,8 +122,10 @@
121 ('trusty', None): self.trusty_icehouse,122 ('trusty', None): self.trusty_icehouse,
122 ('trusty', 'cloud:trusty-juno'): self.trusty_juno,123 ('trusty', 'cloud:trusty-juno'): self.trusty_juno,
123 ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,124 ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo,
125 ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty,
124 ('utopic', None): self.utopic_juno,126 ('utopic', None): self.utopic_juno,
125 ('vivid', None): self.vivid_kilo}127 ('vivid', None): self.vivid_kilo,
128 ('wily', None): self.wily_liberty}
126 return releases[(self.series, self.openstack)]129 return releases[(self.series, self.openstack)]
127130
128 def _get_openstack_release_string(self):131 def _get_openstack_release_string(self):
@@ -138,6 +141,7 @@
138 ('trusty', 'icehouse'),141 ('trusty', 'icehouse'),
139 ('utopic', 'juno'),142 ('utopic', 'juno'),
140 ('vivid', 'kilo'),143 ('vivid', 'kilo'),
144 ('wily', 'liberty'),
141 ])145 ])
142 if self.openstack:146 if self.openstack:
143 os_origin = self.openstack.split(':')[1]147 os_origin = self.openstack.split(':')[1]
144148
=== modified file 'charmhelpers/contrib/openstack/amulet/utils.py'
--- charmhelpers/contrib/openstack/amulet/utils.py 2015-01-22 06:06:03 +0000
+++ charmhelpers/contrib/openstack/amulet/utils.py 2015-06-11 19:47:33 +0000
@@ -16,15 +16,15 @@
1616
17import logging17import logging
18import os18import os
19import six
19import time20import time
20import urllib21import urllib
2122
22import glanceclient.v1.client as glance_client23import glanceclient.v1.client as glance_client
24import heatclient.v1.client as heat_client
23import keystoneclient.v2_0 as keystone_client25import keystoneclient.v2_0 as keystone_client
24import novaclient.v1_1.client as nova_client26import novaclient.v1_1.client as nova_client
2527
26import six
27
28from charmhelpers.contrib.amulet.utils import (28from charmhelpers.contrib.amulet.utils import (
29 AmuletUtils29 AmuletUtils
30)30)
@@ -37,7 +37,7 @@
37 """OpenStack amulet utilities.37 """OpenStack amulet utilities.
3838
39 This class inherits from AmuletUtils and has additional support39 This class inherits from AmuletUtils and has additional support
40 that is specifically for use by OpenStack charms.40 that is specifically for use by OpenStack charm tests.
41 """41 """
4242
43 def __init__(self, log_level=ERROR):43 def __init__(self, log_level=ERROR):
@@ -51,6 +51,8 @@
51 Validate actual endpoint data vs expected endpoint data. The ports51 Validate actual endpoint data vs expected endpoint data. The ports
52 are used to find the matching endpoint.52 are used to find the matching endpoint.
53 """53 """
54 self.log.debug('Validating endpoint data...')
55 self.log.debug('actual: {}'.format(repr(endpoints)))
54 found = False56 found = False
55 for ep in endpoints:57 for ep in endpoints:
56 self.log.debug('endpoint: {}'.format(repr(ep)))58 self.log.debug('endpoint: {}'.format(repr(ep)))
@@ -77,6 +79,7 @@
77 Validate a list of actual service catalog endpoints vs a list of79 Validate a list of actual service catalog endpoints vs a list of
78 expected service catalog endpoints.80 expected service catalog endpoints.
79 """81 """
82 self.log.debug('Validating service catalog endpoint data...')
80 self.log.debug('actual: {}'.format(repr(actual)))83 self.log.debug('actual: {}'.format(repr(actual)))
81 for k, v in six.iteritems(expected):84 for k, v in six.iteritems(expected):
82 if k in actual:85 if k in actual:
@@ -93,6 +96,7 @@
93 Validate a list of actual tenant data vs list of expected tenant96 Validate a list of actual tenant data vs list of expected tenant
94 data.97 data.
95 """98 """
99 self.log.debug('Validating tenant data...')
96 self.log.debug('actual: {}'.format(repr(actual)))100 self.log.debug('actual: {}'.format(repr(actual)))
97 for e in expected:101 for e in expected:
98 found = False102 found = False
@@ -114,6 +118,7 @@
114 Validate a list of actual role data vs a list of expected role118 Validate a list of actual role data vs a list of expected role
115 data.119 data.
116 """120 """
121 self.log.debug('Validating role data...')
117 self.log.debug('actual: {}'.format(repr(actual)))122 self.log.debug('actual: {}'.format(repr(actual)))
118 for e in expected:123 for e in expected:
119 found = False124 found = False
@@ -134,6 +139,7 @@
134 Validate a list of actual user data vs a list of expected user139 Validate a list of actual user data vs a list of expected user
135 data.140 data.
136 """141 """
142 self.log.debug('Validating user data...')
137 self.log.debug('actual: {}'.format(repr(actual)))143 self.log.debug('actual: {}'.format(repr(actual)))
138 for e in expected:144 for e in expected:
139 found = False145 found = False
@@ -155,17 +161,20 @@
155161
156 Validate a list of actual flavors vs a list of expected flavors.162 Validate a list of actual flavors vs a list of expected flavors.
157 """163 """
164 self.log.debug('Validating flavor data...')
158 self.log.debug('actual: {}'.format(repr(actual)))165 self.log.debug('actual: {}'.format(repr(actual)))
159 act = [a.name for a in actual]166 act = [a.name for a in actual]
160 return self._validate_list_data(expected, act)167 return self._validate_list_data(expected, act)
161168
162 def tenant_exists(self, keystone, tenant):169 def tenant_exists(self, keystone, tenant):
163 """Return True if tenant exists."""170 """Return True if tenant exists."""
171 self.log.debug('Checking if tenant exists ({})...'.format(tenant))
164 return tenant in [t.name for t in keystone.tenants.list()]172 return tenant in [t.name for t in keystone.tenants.list()]
165173
166 def authenticate_keystone_admin(self, keystone_sentry, user, password,174 def authenticate_keystone_admin(self, keystone_sentry, user, password,
167 tenant):175 tenant):
168 """Authenticates admin user with the keystone admin endpoint."""176 """Authenticates admin user with the keystone admin endpoint."""
177 self.log.debug('Authenticating keystone admin...')
169 unit = keystone_sentry178 unit = keystone_sentry
170 service_ip = unit.relation('shared-db',179 service_ip = unit.relation('shared-db',
171 'mysql:shared-db')['private-address']180 'mysql:shared-db')['private-address']
@@ -175,6 +184,7 @@
175184
176 def authenticate_keystone_user(self, keystone, user, password, tenant):185 def authenticate_keystone_user(self, keystone, user, password, tenant):
177 """Authenticates a regular user with the keystone public endpoint."""186 """Authenticates a regular user with the keystone public endpoint."""
187 self.log.debug('Authenticating keystone user ({})...'.format(user))
178 ep = keystone.service_catalog.url_for(service_type='identity',188 ep = keystone.service_catalog.url_for(service_type='identity',
179 endpoint_type='publicURL')189 endpoint_type='publicURL')
180 return keystone_client.Client(username=user, password=password,190 return keystone_client.Client(username=user, password=password,
@@ -182,12 +192,21 @@
182192
183 def authenticate_glance_admin(self, keystone):193 def authenticate_glance_admin(self, keystone):
184 """Authenticates admin user with glance."""194 """Authenticates admin user with glance."""
195 self.log.debug('Authenticating glance admin...')
185 ep = keystone.service_catalog.url_for(service_type='image',196 ep = keystone.service_catalog.url_for(service_type='image',
186 endpoint_type='adminURL')197 endpoint_type='adminURL')
187 return glance_client.Client(ep, token=keystone.auth_token)198 return glance_client.Client(ep, token=keystone.auth_token)
188199
200 def authenticate_heat_admin(self, keystone):
201 """Authenticates the admin user with heat."""
202 self.log.debug('Authenticating heat admin...')
203 ep = keystone.service_catalog.url_for(service_type='orchestration',
204 endpoint_type='publicURL')
205 return heat_client.Client(endpoint=ep, token=keystone.auth_token)
206
189 def authenticate_nova_user(self, keystone, user, password, tenant):207 def authenticate_nova_user(self, keystone, user, password, tenant):
190 """Authenticates a regular user with nova-api."""208 """Authenticates a regular user with nova-api."""
209 self.log.debug('Authenticating nova user ({})...'.format(user))
191 ep = keystone.service_catalog.url_for(service_type='identity',210 ep = keystone.service_catalog.url_for(service_type='identity',
192 endpoint_type='publicURL')211 endpoint_type='publicURL')
193 return nova_client.Client(username=user, api_key=password,212 return nova_client.Client(username=user, api_key=password,
@@ -195,6 +214,7 @@
195214
196 def create_cirros_image(self, glance, image_name):215 def create_cirros_image(self, glance, image_name):
197 """Download the latest cirros image and upload it to glance."""216 """Download the latest cirros image and upload it to glance."""
217 self.log.debug('Creating glance image ({})...'.format(image_name))
198 http_proxy = os.getenv('AMULET_HTTP_PROXY')218 http_proxy = os.getenv('AMULET_HTTP_PROXY')
199 self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy))219 self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy))
200 if http_proxy:220 if http_proxy:
@@ -235,6 +255,11 @@
235255
236 def delete_image(self, glance, image):256 def delete_image(self, glance, image):
237 """Delete the specified image."""257 """Delete the specified image."""
258
259 # /!\ DEPRECATION WARNING
260 self.log.warn('/!\\ DEPRECATION WARNING: use '
261 'delete_resource instead of delete_image.')
262 self.log.debug('Deleting glance image ({})...'.format(image))
238 num_before = len(list(glance.images.list()))263 num_before = len(list(glance.images.list()))
239 glance.images.delete(image)264 glance.images.delete(image)
240265
@@ -254,6 +279,8 @@
254279
255 def create_instance(self, nova, image_name, instance_name, flavor):280 def create_instance(self, nova, image_name, instance_name, flavor):
256 """Create the specified instance."""281 """Create the specified instance."""
282 self.log.debug('Creating instance '
283 '({}|{}|{})'.format(instance_name, image_name, flavor))
257 image = nova.images.find(name=image_name)284 image = nova.images.find(name=image_name)
258 flavor = nova.flavors.find(name=flavor)285 flavor = nova.flavors.find(name=flavor)
259 instance = nova.servers.create(name=instance_name, image=image,286 instance = nova.servers.create(name=instance_name, image=image,
@@ -276,6 +303,11 @@
276303
277 def delete_instance(self, nova, instance):304 def delete_instance(self, nova, instance):
278 """Delete the specified instance."""305 """Delete the specified instance."""
306
307 # /!\ DEPRECATION WARNING
308 self.log.warn('/!\\ DEPRECATION WARNING: use '
309 'delete_resource instead of delete_instance.')
310 self.log.debug('Deleting instance ({})...'.format(instance))
279 num_before = len(list(nova.servers.list()))311 num_before = len(list(nova.servers.list()))
280 nova.servers.delete(instance)312 nova.servers.delete(instance)
281313
@@ -292,3 +324,90 @@
292 return False324 return False
293325
294 return True326 return True
327
328 def create_or_get_keypair(self, nova, keypair_name="testkey"):
329 """Create a new keypair, or return pointer if it already exists."""
330 try:
331 _keypair = nova.keypairs.get(keypair_name)
332 self.log.debug('Keypair ({}) already exists, '
333 'using it.'.format(keypair_name))
334 return _keypair
335 except:
336 self.log.debug('Keypair ({}) does not exist, '
337 'creating it.'.format(keypair_name))
338
339 _keypair = nova.keypairs.create(name=keypair_name)
340 return _keypair
341
342 def delete_resource(self, resource, resource_id,
343 msg="resource", max_wait=120):
344 """Delete one openstack resource, such as one instance, keypair,
345 image, volume, stack, etc., and confirm deletion within max wait time.
346
347 :param resource: pointer to os resource type, ex:glance_client.images
348 :param resource_id: unique name or id for the openstack resource
349 :param msg: text to identify purpose in logging
350 :param max_wait: maximum wait time in seconds
351 :returns: True if successful, otherwise False
352 """
353 num_before = len(list(resource.list()))
354 resource.delete(resource_id)
355
356 tries = 0
357 num_after = len(list(resource.list()))
358 while num_after != (num_before - 1) and tries < (max_wait / 4):
359 self.log.debug('{} delete check: '
360 '{} [{}:{}] {}'.format(msg, tries,
361 num_before,
362 num_after,
363 resource_id))
364 time.sleep(4)
365 num_after = len(list(resource.list()))
366 tries += 1
367
368 self.log.debug('{}: expected, actual count = {}, '
369 '{}'.format(msg, num_before - 1, num_after))
370
371 if num_after == (num_before - 1):
372 return True
373 else:
374 self.log.error('{} delete timed out'.format(msg))
375 return False
376
377 def resource_reaches_status(self, resource, resource_id,
378 expected_stat='available',
379 msg='resource', max_wait=120):
380 """Wait for an openstack resources status to reach an
381 expected status within a specified time. Useful to confirm that
382 nova instances, cinder vols, snapshots, glance images, heat stacks
383 and other resources eventually reach the expected status.
384
385 :param resource: pointer to os resource type, ex: heat_client.stacks
386 :param resource_id: unique id for the openstack resource
387 :param expected_stat: status to expect resource to reach
388 :param msg: text to identify purpose in logging
389 :param max_wait: maximum wait time in seconds
390 :returns: True if successful, False if status is not reached
391 """
392
393 tries = 0
394 resource_stat = resource.get(resource_id).status
395 while resource_stat != expected_stat and tries < (max_wait / 4):
396 self.log.debug('{} status check: '
397 '{} [{}:{}] {}'.format(msg, tries,
398 resource_stat,
399 expected_stat,
400 resource_id))
401 time.sleep(4)
402 resource_stat = resource.get(resource_id).status
403 tries += 1
404
405 self.log.debug('{}: expected, actual status = {}, '
406 '{}'.format(msg, resource_stat, expected_stat))
407
408 if resource_stat == expected_stat:
409 return True
410 else:
411 self.log.debug('{} never reached expected status: '
412 '{}'.format(resource_id, expected_stat))
413 return False
295414
=== modified file 'charmhelpers/contrib/openstack/utils.py'
--- charmhelpers/contrib/openstack/utils.py 2015-05-11 18:53:44 +0000
+++ charmhelpers/contrib/openstack/utils.py 2015-06-11 19:47:33 +0000
@@ -79,6 +79,7 @@
79 ('trusty', 'icehouse'),79 ('trusty', 'icehouse'),
80 ('utopic', 'juno'),80 ('utopic', 'juno'),
81 ('vivid', 'kilo'),81 ('vivid', 'kilo'),
82 ('wily', 'liberty'),
82])83])
8384
8485
@@ -91,6 +92,7 @@
91 ('2014.1', 'icehouse'),92 ('2014.1', 'icehouse'),
92 ('2014.2', 'juno'),93 ('2014.2', 'juno'),
93 ('2015.1', 'kilo'),94 ('2015.1', 'kilo'),
95 ('2015.2', 'liberty'),
94])96])
9597
96# The ugly duckling98# The ugly duckling
@@ -113,6 +115,7 @@
113 ('2.2.0', 'juno'),115 ('2.2.0', 'juno'),
114 ('2.2.1', 'kilo'),116 ('2.2.1', 'kilo'),
115 ('2.2.2', 'kilo'),117 ('2.2.2', 'kilo'),
118 ('2.3.0', 'liberty'),
116])119])
117120
118DEFAULT_LOOPBACK_SIZE = '5G'121DEFAULT_LOOPBACK_SIZE = '5G'
@@ -321,6 +324,9 @@
321 'kilo': 'trusty-updates/kilo',324 'kilo': 'trusty-updates/kilo',
322 'kilo/updates': 'trusty-updates/kilo',325 'kilo/updates': 'trusty-updates/kilo',
323 'kilo/proposed': 'trusty-proposed/kilo',326 'kilo/proposed': 'trusty-proposed/kilo',
327 'liberty': 'trusty-updates/liberty',
328 'liberty/updates': 'trusty-updates/liberty',
329 'liberty/proposed': 'trusty-proposed/liberty',
324 }330 }
325331
326 try:332 try:
@@ -641,7 +647,8 @@
641 subprocess.check_call(cmd)647 subprocess.check_call(cmd)
642 except subprocess.CalledProcessError:648 except subprocess.CalledProcessError:
643 package = os.path.basename(package_dir)649 package = os.path.basename(package_dir)
644 error_out("Error updating {} from global-requirements.txt".format(package))650 error_out("Error updating {} from "
651 "global-requirements.txt".format(package))
645 os.chdir(orig_dir)652 os.chdir(orig_dir)
646653
647654

Subscribers

People subscribed via source and target branches