Merge lp:~corey.bryant/charms/trusty/keystone/fix-global-reqs into lp:~openstack-charmers-archive/charms/trusty/keystone/next
- Trusty Tahr (14.04)
- fix-global-reqs
- Merge into next
Proposed by
Corey Bryant
Status: | Merged |
---|---|
Approved by: | Billy Olsen |
Approved revision: | 155 |
Merged at revision: | 155 |
Proposed branch: | lp:~corey.bryant/charms/trusty/keystone/fix-global-reqs |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/keystone/next |
Diff against target: |
969 lines (+411/-38) 10 files modified
hooks/charmhelpers/contrib/hahelpers/cluster.py (+12/-3) hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+6/-2) hooks/charmhelpers/contrib/openstack/amulet/utils.py (+122/-3) hooks/charmhelpers/contrib/openstack/context.py (+1/-1) hooks/charmhelpers/contrib/openstack/neutron.py (+6/-4) hooks/charmhelpers/contrib/openstack/utils.py (+21/-8) hooks/charmhelpers/core/host.py (+24/-6) tests/charmhelpers/contrib/amulet/utils.py (+91/-6) tests/charmhelpers/contrib/openstack/amulet/deployment.py (+6/-2) tests/charmhelpers/contrib/openstack/amulet/utils.py (+122/-3) |
To merge this branch: | bzr merge lp:~corey.bryant/charms/trusty/keystone/fix-global-reqs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Billy Olsen | Approve | ||
Review via email: mp+262483@code.launchpad.net |
Commit message
Description of the change
See https:/
for details explaining this merge.
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_unit_test #5113 keystone-next for corey.bryant mp262483
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #4688 keystone-next for corey.bryant mp262483
AMULET OK: passed
Build: http://
Revision history for this message
Billy Olsen (billy-olsen) wrote : | # |
LGTM, Approved.
review:
Approve
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 2015-06-10 20:44:02 +0000 | |||
3 | +++ hooks/charmhelpers/contrib/hahelpers/cluster.py 2015-06-19 16:34:56 +0000 | |||
4 | @@ -64,6 +64,10 @@ | |||
5 | 64 | pass | 64 | pass |
6 | 65 | 65 | ||
7 | 66 | 66 | ||
8 | 67 | class CRMDCNotFound(Exception): | ||
9 | 68 | pass | ||
10 | 69 | |||
11 | 70 | |||
12 | 67 | def is_elected_leader(resource): | 71 | def is_elected_leader(resource): |
13 | 68 | """ | 72 | """ |
14 | 69 | Returns True if the charm executing this is the elected cluster leader. | 73 | Returns True if the charm executing this is the elected cluster leader. |
15 | @@ -116,8 +120,9 @@ | |||
16 | 116 | status = subprocess.check_output(cmd, stderr=subprocess.STDOUT) | 120 | status = subprocess.check_output(cmd, stderr=subprocess.STDOUT) |
17 | 117 | if not isinstance(status, six.text_type): | 121 | if not isinstance(status, six.text_type): |
18 | 118 | status = six.text_type(status, "utf-8") | 122 | status = six.text_type(status, "utf-8") |
21 | 119 | except subprocess.CalledProcessError: | 123 | except subprocess.CalledProcessError as ex: |
22 | 120 | return False | 124 | raise CRMDCNotFound(str(ex)) |
23 | 125 | |||
24 | 121 | current_dc = '' | 126 | current_dc = '' |
25 | 122 | for line in status.split('\n'): | 127 | for line in status.split('\n'): |
26 | 123 | if line.startswith('Current DC'): | 128 | if line.startswith('Current DC'): |
27 | @@ -125,10 +130,14 @@ | |||
28 | 125 | current_dc = line.split(':')[1].split()[0] | 130 | current_dc = line.split(':')[1].split()[0] |
29 | 126 | if current_dc == get_unit_hostname(): | 131 | if current_dc == get_unit_hostname(): |
30 | 127 | return True | 132 | return True |
31 | 133 | elif current_dc == 'NONE': | ||
32 | 134 | raise CRMDCNotFound('Current DC: NONE') | ||
33 | 135 | |||
34 | 128 | return False | 136 | return False |
35 | 129 | 137 | ||
36 | 130 | 138 | ||
38 | 131 | @retry_on_exception(5, base_delay=2, exc_type=CRMResourceNotFound) | 139 | @retry_on_exception(5, base_delay=2, |
39 | 140 | exc_type=(CRMResourceNotFound, CRMDCNotFound)) | ||
40 | 132 | def is_crm_leader(resource, retry=False): | 141 | def is_crm_leader(resource, retry=False): |
41 | 133 | """ | 142 | """ |
42 | 134 | Returns True if the charm calling this is the elected corosync leader, | 143 | Returns True if the charm calling this is the elected corosync leader, |
43 | 135 | 144 | ||
44 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
45 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-10 13:59:24 +0000 | |||
46 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-19 16:34:56 +0000 | |||
47 | @@ -110,7 +110,8 @@ | |||
48 | 110 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, | 110 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, |
49 | 111 | self.precise_havana, self.precise_icehouse, | 111 | self.precise_havana, self.precise_icehouse, |
50 | 112 | self.trusty_icehouse, self.trusty_juno, self.utopic_juno, | 112 | self.trusty_icehouse, self.trusty_juno, self.utopic_juno, |
52 | 113 | self.trusty_kilo, self.vivid_kilo) = range(10) | 113 | self.trusty_kilo, self.vivid_kilo, self.trusty_liberty, |
53 | 114 | self.wily_liberty) = range(12) | ||
54 | 114 | 115 | ||
55 | 115 | releases = { | 116 | releases = { |
56 | 116 | ('precise', None): self.precise_essex, | 117 | ('precise', None): self.precise_essex, |
57 | @@ -121,8 +122,10 @@ | |||
58 | 121 | ('trusty', None): self.trusty_icehouse, | 122 | ('trusty', None): self.trusty_icehouse, |
59 | 122 | ('trusty', 'cloud:trusty-juno'): self.trusty_juno, | 123 | ('trusty', 'cloud:trusty-juno'): self.trusty_juno, |
60 | 123 | ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo, | 124 | ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo, |
61 | 125 | ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty, | ||
62 | 124 | ('utopic', None): self.utopic_juno, | 126 | ('utopic', None): self.utopic_juno, |
64 | 125 | ('vivid', None): self.vivid_kilo} | 127 | ('vivid', None): self.vivid_kilo, |
65 | 128 | ('wily', None): self.wily_liberty} | ||
66 | 126 | return releases[(self.series, self.openstack)] | 129 | return releases[(self.series, self.openstack)] |
67 | 127 | 130 | ||
68 | 128 | def _get_openstack_release_string(self): | 131 | def _get_openstack_release_string(self): |
69 | @@ -138,6 +141,7 @@ | |||
70 | 138 | ('trusty', 'icehouse'), | 141 | ('trusty', 'icehouse'), |
71 | 139 | ('utopic', 'juno'), | 142 | ('utopic', 'juno'), |
72 | 140 | ('vivid', 'kilo'), | 143 | ('vivid', 'kilo'), |
73 | 144 | ('wily', 'liberty'), | ||
74 | 141 | ]) | 145 | ]) |
75 | 142 | if self.openstack: | 146 | if self.openstack: |
76 | 143 | os_origin = self.openstack.split(':')[1] | 147 | os_origin = self.openstack.split(':')[1] |
77 | 144 | 148 | ||
78 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' | |||
79 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-03-11 11:45:09 +0000 | |||
80 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-06-19 16:34:56 +0000 | |||
81 | @@ -16,15 +16,15 @@ | |||
82 | 16 | 16 | ||
83 | 17 | import logging | 17 | import logging |
84 | 18 | import os | 18 | import os |
85 | 19 | import six | ||
86 | 19 | import time | 20 | import time |
87 | 20 | import urllib | 21 | import urllib |
88 | 21 | 22 | ||
89 | 22 | import glanceclient.v1.client as glance_client | 23 | import glanceclient.v1.client as glance_client |
90 | 24 | import heatclient.v1.client as heat_client | ||
91 | 23 | import keystoneclient.v2_0 as keystone_client | 25 | import keystoneclient.v2_0 as keystone_client |
92 | 24 | import novaclient.v1_1.client as nova_client | 26 | import novaclient.v1_1.client as nova_client |
93 | 25 | 27 | ||
94 | 26 | import six | ||
95 | 27 | |||
96 | 28 | from charmhelpers.contrib.amulet.utils import ( | 28 | from charmhelpers.contrib.amulet.utils import ( |
97 | 29 | AmuletUtils | 29 | AmuletUtils |
98 | 30 | ) | 30 | ) |
99 | @@ -37,7 +37,7 @@ | |||
100 | 37 | """OpenStack amulet utilities. | 37 | """OpenStack amulet utilities. |
101 | 38 | 38 | ||
102 | 39 | This class inherits from AmuletUtils and has additional support | 39 | This class inherits from AmuletUtils and has additional support |
104 | 40 | that is specifically for use by OpenStack charms. | 40 | that is specifically for use by OpenStack charm tests. |
105 | 41 | """ | 41 | """ |
106 | 42 | 42 | ||
107 | 43 | def __init__(self, log_level=ERROR): | 43 | def __init__(self, log_level=ERROR): |
108 | @@ -51,6 +51,8 @@ | |||
109 | 51 | Validate actual endpoint data vs expected endpoint data. The ports | 51 | Validate actual endpoint data vs expected endpoint data. The ports |
110 | 52 | are used to find the matching endpoint. | 52 | are used to find the matching endpoint. |
111 | 53 | """ | 53 | """ |
112 | 54 | self.log.debug('Validating endpoint data...') | ||
113 | 55 | self.log.debug('actual: {}'.format(repr(endpoints))) | ||
114 | 54 | found = False | 56 | found = False |
115 | 55 | for ep in endpoints: | 57 | for ep in endpoints: |
116 | 56 | self.log.debug('endpoint: {}'.format(repr(ep))) | 58 | self.log.debug('endpoint: {}'.format(repr(ep))) |
117 | @@ -77,6 +79,7 @@ | |||
118 | 77 | Validate a list of actual service catalog endpoints vs a list of | 79 | Validate a list of actual service catalog endpoints vs a list of |
119 | 78 | expected service catalog endpoints. | 80 | expected service catalog endpoints. |
120 | 79 | """ | 81 | """ |
121 | 82 | self.log.debug('Validating service catalog endpoint data...') | ||
122 | 80 | self.log.debug('actual: {}'.format(repr(actual))) | 83 | self.log.debug('actual: {}'.format(repr(actual))) |
123 | 81 | for k, v in six.iteritems(expected): | 84 | for k, v in six.iteritems(expected): |
124 | 82 | if k in actual: | 85 | if k in actual: |
125 | @@ -93,6 +96,7 @@ | |||
126 | 93 | Validate a list of actual tenant data vs list of expected tenant | 96 | Validate a list of actual tenant data vs list of expected tenant |
127 | 94 | data. | 97 | data. |
128 | 95 | """ | 98 | """ |
129 | 99 | self.log.debug('Validating tenant data...') | ||
130 | 96 | self.log.debug('actual: {}'.format(repr(actual))) | 100 | self.log.debug('actual: {}'.format(repr(actual))) |
131 | 97 | for e in expected: | 101 | for e in expected: |
132 | 98 | found = False | 102 | found = False |
133 | @@ -114,6 +118,7 @@ | |||
134 | 114 | Validate a list of actual role data vs a list of expected role | 118 | Validate a list of actual role data vs a list of expected role |
135 | 115 | data. | 119 | data. |
136 | 116 | """ | 120 | """ |
137 | 121 | self.log.debug('Validating role data...') | ||
138 | 117 | self.log.debug('actual: {}'.format(repr(actual))) | 122 | self.log.debug('actual: {}'.format(repr(actual))) |
139 | 118 | for e in expected: | 123 | for e in expected: |
140 | 119 | found = False | 124 | found = False |
141 | @@ -134,6 +139,7 @@ | |||
142 | 134 | Validate a list of actual user data vs a list of expected user | 139 | Validate a list of actual user data vs a list of expected user |
143 | 135 | data. | 140 | data. |
144 | 136 | """ | 141 | """ |
145 | 142 | self.log.debug('Validating user data...') | ||
146 | 137 | self.log.debug('actual: {}'.format(repr(actual))) | 143 | self.log.debug('actual: {}'.format(repr(actual))) |
147 | 138 | for e in expected: | 144 | for e in expected: |
148 | 139 | found = False | 145 | found = False |
149 | @@ -155,17 +161,20 @@ | |||
150 | 155 | 161 | ||
151 | 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. |
152 | 157 | """ | 163 | """ |
153 | 164 | self.log.debug('Validating flavor data...') | ||
154 | 158 | self.log.debug('actual: {}'.format(repr(actual))) | 165 | self.log.debug('actual: {}'.format(repr(actual))) |
155 | 159 | act = [a.name for a in actual] | 166 | act = [a.name for a in actual] |
156 | 160 | return self._validate_list_data(expected, act) | 167 | return self._validate_list_data(expected, act) |
157 | 161 | 168 | ||
158 | 162 | def tenant_exists(self, keystone, tenant): | 169 | def tenant_exists(self, keystone, tenant): |
159 | 163 | """Return True if tenant exists.""" | 170 | """Return True if tenant exists.""" |
160 | 171 | self.log.debug('Checking if tenant exists ({})...'.format(tenant)) | ||
161 | 164 | return tenant in [t.name for t in keystone.tenants.list()] | 172 | return tenant in [t.name for t in keystone.tenants.list()] |
162 | 165 | 173 | ||
163 | 166 | def authenticate_keystone_admin(self, keystone_sentry, user, password, | 174 | def authenticate_keystone_admin(self, keystone_sentry, user, password, |
164 | 167 | tenant): | 175 | tenant): |
165 | 168 | """Authenticates admin user with the keystone admin endpoint.""" | 176 | """Authenticates admin user with the keystone admin endpoint.""" |
166 | 177 | self.log.debug('Authenticating keystone admin...') | ||
167 | 169 | unit = keystone_sentry | 178 | unit = keystone_sentry |
168 | 170 | service_ip = unit.relation('shared-db', | 179 | service_ip = unit.relation('shared-db', |
169 | 171 | 'mysql:shared-db')['private-address'] | 180 | 'mysql:shared-db')['private-address'] |
170 | @@ -175,6 +184,7 @@ | |||
171 | 175 | 184 | ||
172 | 176 | def authenticate_keystone_user(self, keystone, user, password, tenant): | 185 | def authenticate_keystone_user(self, keystone, user, password, tenant): |
173 | 177 | """Authenticates a regular user with the keystone public endpoint.""" | 186 | """Authenticates a regular user with the keystone public endpoint.""" |
174 | 187 | self.log.debug('Authenticating keystone user ({})...'.format(user)) | ||
175 | 178 | ep = keystone.service_catalog.url_for(service_type='identity', | 188 | ep = keystone.service_catalog.url_for(service_type='identity', |
176 | 179 | endpoint_type='publicURL') | 189 | endpoint_type='publicURL') |
177 | 180 | return keystone_client.Client(username=user, password=password, | 190 | return keystone_client.Client(username=user, password=password, |
178 | @@ -182,12 +192,21 @@ | |||
179 | 182 | 192 | ||
180 | 183 | def authenticate_glance_admin(self, keystone): | 193 | def authenticate_glance_admin(self, keystone): |
181 | 184 | """Authenticates admin user with glance.""" | 194 | """Authenticates admin user with glance.""" |
182 | 195 | self.log.debug('Authenticating glance admin...') | ||
183 | 185 | ep = keystone.service_catalog.url_for(service_type='image', | 196 | ep = keystone.service_catalog.url_for(service_type='image', |
184 | 186 | endpoint_type='adminURL') | 197 | endpoint_type='adminURL') |
185 | 187 | return glance_client.Client(ep, token=keystone.auth_token) | 198 | return glance_client.Client(ep, token=keystone.auth_token) |
186 | 188 | 199 | ||
187 | 200 | def authenticate_heat_admin(self, keystone): | ||
188 | 201 | """Authenticates the admin user with heat.""" | ||
189 | 202 | self.log.debug('Authenticating heat admin...') | ||
190 | 203 | ep = keystone.service_catalog.url_for(service_type='orchestration', | ||
191 | 204 | endpoint_type='publicURL') | ||
192 | 205 | return heat_client.Client(endpoint=ep, token=keystone.auth_token) | ||
193 | 206 | |||
194 | 189 | def authenticate_nova_user(self, keystone, user, password, tenant): | 207 | def authenticate_nova_user(self, keystone, user, password, tenant): |
195 | 190 | """Authenticates a regular user with nova-api.""" | 208 | """Authenticates a regular user with nova-api.""" |
196 | 209 | self.log.debug('Authenticating nova user ({})...'.format(user)) | ||
197 | 191 | ep = keystone.service_catalog.url_for(service_type='identity', | 210 | ep = keystone.service_catalog.url_for(service_type='identity', |
198 | 192 | endpoint_type='publicURL') | 211 | endpoint_type='publicURL') |
199 | 193 | return nova_client.Client(username=user, api_key=password, | 212 | return nova_client.Client(username=user, api_key=password, |
200 | @@ -195,6 +214,7 @@ | |||
201 | 195 | 214 | ||
202 | 196 | def create_cirros_image(self, glance, image_name): | 215 | def create_cirros_image(self, glance, image_name): |
203 | 197 | """Download the latest cirros image and upload it to glance.""" | 216 | """Download the latest cirros image and upload it to glance.""" |
204 | 217 | self.log.debug('Creating glance image ({})...'.format(image_name)) | ||
205 | 198 | http_proxy = os.getenv('AMULET_HTTP_PROXY') | 218 | http_proxy = os.getenv('AMULET_HTTP_PROXY') |
206 | 199 | self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy)) | 219 | self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy)) |
207 | 200 | if http_proxy: | 220 | if http_proxy: |
208 | @@ -235,6 +255,11 @@ | |||
209 | 235 | 255 | ||
210 | 236 | def delete_image(self, glance, image): | 256 | def delete_image(self, glance, image): |
211 | 237 | """Delete the specified image.""" | 257 | """Delete the specified image.""" |
212 | 258 | |||
213 | 259 | # /!\ DEPRECATION WARNING | ||
214 | 260 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | ||
215 | 261 | 'delete_resource instead of delete_image.') | ||
216 | 262 | self.log.debug('Deleting glance image ({})...'.format(image)) | ||
217 | 238 | num_before = len(list(glance.images.list())) | 263 | num_before = len(list(glance.images.list())) |
218 | 239 | glance.images.delete(image) | 264 | glance.images.delete(image) |
219 | 240 | 265 | ||
220 | @@ -254,6 +279,8 @@ | |||
221 | 254 | 279 | ||
222 | 255 | def create_instance(self, nova, image_name, instance_name, flavor): | 280 | def create_instance(self, nova, image_name, instance_name, flavor): |
223 | 256 | """Create the specified instance.""" | 281 | """Create the specified instance.""" |
224 | 282 | self.log.debug('Creating instance ' | ||
225 | 283 | '({}|{}|{})'.format(instance_name, image_name, flavor)) | ||
226 | 257 | image = nova.images.find(name=image_name) | 284 | image = nova.images.find(name=image_name) |
227 | 258 | flavor = nova.flavors.find(name=flavor) | 285 | flavor = nova.flavors.find(name=flavor) |
228 | 259 | instance = nova.servers.create(name=instance_name, image=image, | 286 | instance = nova.servers.create(name=instance_name, image=image, |
229 | @@ -276,6 +303,11 @@ | |||
230 | 276 | 303 | ||
231 | 277 | def delete_instance(self, nova, instance): | 304 | def delete_instance(self, nova, instance): |
232 | 278 | """Delete the specified instance.""" | 305 | """Delete the specified instance.""" |
233 | 306 | |||
234 | 307 | # /!\ DEPRECATION WARNING | ||
235 | 308 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | ||
236 | 309 | 'delete_resource instead of delete_instance.') | ||
237 | 310 | self.log.debug('Deleting instance ({})...'.format(instance)) | ||
238 | 279 | num_before = len(list(nova.servers.list())) | 311 | num_before = len(list(nova.servers.list())) |
239 | 280 | nova.servers.delete(instance) | 312 | nova.servers.delete(instance) |
240 | 281 | 313 | ||
241 | @@ -292,3 +324,90 @@ | |||
242 | 292 | return False | 324 | return False |
243 | 293 | 325 | ||
244 | 294 | return True | 326 | return True |
245 | 327 | |||
246 | 328 | def create_or_get_keypair(self, nova, keypair_name="testkey"): | ||
247 | 329 | """Create a new keypair, or return pointer if it already exists.""" | ||
248 | 330 | try: | ||
249 | 331 | _keypair = nova.keypairs.get(keypair_name) | ||
250 | 332 | self.log.debug('Keypair ({}) already exists, ' | ||
251 | 333 | 'using it.'.format(keypair_name)) | ||
252 | 334 | return _keypair | ||
253 | 335 | except: | ||
254 | 336 | self.log.debug('Keypair ({}) does not exist, ' | ||
255 | 337 | 'creating it.'.format(keypair_name)) | ||
256 | 338 | |||
257 | 339 | _keypair = nova.keypairs.create(name=keypair_name) | ||
258 | 340 | return _keypair | ||
259 | 341 | |||
260 | 342 | def delete_resource(self, resource, resource_id, | ||
261 | 343 | msg="resource", max_wait=120): | ||
262 | 344 | """Delete one openstack resource, such as one instance, keypair, | ||
263 | 345 | image, volume, stack, etc., and confirm deletion within max wait time. | ||
264 | 346 | |||
265 | 347 | :param resource: pointer to os resource type, ex:glance_client.images | ||
266 | 348 | :param resource_id: unique name or id for the openstack resource | ||
267 | 349 | :param msg: text to identify purpose in logging | ||
268 | 350 | :param max_wait: maximum wait time in seconds | ||
269 | 351 | :returns: True if successful, otherwise False | ||
270 | 352 | """ | ||
271 | 353 | num_before = len(list(resource.list())) | ||
272 | 354 | resource.delete(resource_id) | ||
273 | 355 | |||
274 | 356 | tries = 0 | ||
275 | 357 | num_after = len(list(resource.list())) | ||
276 | 358 | while num_after != (num_before - 1) and tries < (max_wait / 4): | ||
277 | 359 | self.log.debug('{} delete check: ' | ||
278 | 360 | '{} [{}:{}] {}'.format(msg, tries, | ||
279 | 361 | num_before, | ||
280 | 362 | num_after, | ||
281 | 363 | resource_id)) | ||
282 | 364 | time.sleep(4) | ||
283 | 365 | num_after = len(list(resource.list())) | ||
284 | 366 | tries += 1 | ||
285 | 367 | |||
286 | 368 | self.log.debug('{}: expected, actual count = {}, ' | ||
287 | 369 | '{}'.format(msg, num_before - 1, num_after)) | ||
288 | 370 | |||
289 | 371 | if num_after == (num_before - 1): | ||
290 | 372 | return True | ||
291 | 373 | else: | ||
292 | 374 | self.log.error('{} delete timed out'.format(msg)) | ||
293 | 375 | return False | ||
294 | 376 | |||
295 | 377 | def resource_reaches_status(self, resource, resource_id, | ||
296 | 378 | expected_stat='available', | ||
297 | 379 | msg='resource', max_wait=120): | ||
298 | 380 | """Wait for an openstack resources status to reach an | ||
299 | 381 | expected status within a specified time. Useful to confirm that | ||
300 | 382 | nova instances, cinder vols, snapshots, glance images, heat stacks | ||
301 | 383 | and other resources eventually reach the expected status. | ||
302 | 384 | |||
303 | 385 | :param resource: pointer to os resource type, ex: heat_client.stacks | ||
304 | 386 | :param resource_id: unique id for the openstack resource | ||
305 | 387 | :param expected_stat: status to expect resource to reach | ||
306 | 388 | :param msg: text to identify purpose in logging | ||
307 | 389 | :param max_wait: maximum wait time in seconds | ||
308 | 390 | :returns: True if successful, False if status is not reached | ||
309 | 391 | """ | ||
310 | 392 | |||
311 | 393 | tries = 0 | ||
312 | 394 | resource_stat = resource.get(resource_id).status | ||
313 | 395 | while resource_stat != expected_stat and tries < (max_wait / 4): | ||
314 | 396 | self.log.debug('{} status check: ' | ||
315 | 397 | '{} [{}:{}] {}'.format(msg, tries, | ||
316 | 398 | resource_stat, | ||
317 | 399 | expected_stat, | ||
318 | 400 | resource_id)) | ||
319 | 401 | time.sleep(4) | ||
320 | 402 | resource_stat = resource.get(resource_id).status | ||
321 | 403 | tries += 1 | ||
322 | 404 | |||
323 | 405 | self.log.debug('{}: expected, actual status = {}, ' | ||
324 | 406 | '{}'.format(msg, resource_stat, expected_stat)) | ||
325 | 407 | |||
326 | 408 | if resource_stat == expected_stat: | ||
327 | 409 | return True | ||
328 | 410 | else: | ||
329 | 411 | self.log.debug('{} never reached expected status: ' | ||
330 | 412 | '{}'.format(resource_id, expected_stat)) | ||
331 | 413 | return False | ||
332 | 295 | 414 | ||
333 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' | |||
334 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-05-11 07:38:06 +0000 | |||
335 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-06-19 16:34:56 +0000 | |||
336 | @@ -240,7 +240,7 @@ | |||
337 | 240 | if self.relation_prefix: | 240 | if self.relation_prefix: |
338 | 241 | password_setting = self.relation_prefix + '_password' | 241 | password_setting = self.relation_prefix + '_password' |
339 | 242 | 242 | ||
341 | 243 | for rid in relation_ids('shared-db'): | 243 | for rid in relation_ids(self.interfaces[0]): |
342 | 244 | for unit in related_units(rid): | 244 | for unit in related_units(rid): |
343 | 245 | rdata = relation_get(rid=rid, unit=unit) | 245 | rdata = relation_get(rid=rid, unit=unit) |
344 | 246 | host = rdata.get('db_host') | 246 | host = rdata.get('db_host') |
345 | 247 | 247 | ||
346 | === modified file 'hooks/charmhelpers/contrib/openstack/neutron.py' | |||
347 | --- hooks/charmhelpers/contrib/openstack/neutron.py 2015-06-10 20:44:02 +0000 | |||
348 | +++ hooks/charmhelpers/contrib/openstack/neutron.py 2015-06-19 16:34:56 +0000 | |||
349 | @@ -172,14 +172,16 @@ | |||
350 | 172 | 'services': ['calico-felix', | 172 | 'services': ['calico-felix', |
351 | 173 | 'bird', | 173 | 'bird', |
352 | 174 | 'neutron-dhcp-agent', | 174 | 'neutron-dhcp-agent', |
354 | 175 | 'nova-api-metadata'], | 175 | 'nova-api-metadata', |
355 | 176 | 'etcd'], | ||
356 | 176 | 'packages': [[headers_package()] + determine_dkms_package(), | 177 | 'packages': [[headers_package()] + determine_dkms_package(), |
357 | 177 | ['calico-compute', | 178 | ['calico-compute', |
358 | 178 | 'bird', | 179 | 'bird', |
359 | 179 | 'neutron-dhcp-agent', | 180 | 'neutron-dhcp-agent', |
363 | 180 | 'nova-api-metadata']], | 181 | 'nova-api-metadata', |
364 | 181 | 'server_packages': ['neutron-server', 'calico-control'], | 182 | 'etcd']], |
365 | 182 | 'server_services': ['neutron-server'] | 183 | 'server_packages': ['neutron-server', 'calico-control', 'etcd'], |
366 | 184 | 'server_services': ['neutron-server', 'etcd'] | ||
367 | 183 | }, | 185 | }, |
368 | 184 | 'vsp': { | 186 | 'vsp': { |
369 | 185 | 'config': '/etc/neutron/plugins/nuage/nuage_plugin.ini', | 187 | 'config': '/etc/neutron/plugins/nuage/nuage_plugin.ini', |
370 | 186 | 188 | ||
371 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' | |||
372 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-06-10 20:44:02 +0000 | |||
373 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2015-06-19 16:34:56 +0000 | |||
374 | @@ -79,6 +79,7 @@ | |||
375 | 79 | ('trusty', 'icehouse'), | 79 | ('trusty', 'icehouse'), |
376 | 80 | ('utopic', 'juno'), | 80 | ('utopic', 'juno'), |
377 | 81 | ('vivid', 'kilo'), | 81 | ('vivid', 'kilo'), |
378 | 82 | ('wily', 'liberty'), | ||
379 | 82 | ]) | 83 | ]) |
380 | 83 | 84 | ||
381 | 84 | 85 | ||
382 | @@ -91,6 +92,7 @@ | |||
383 | 91 | ('2014.1', 'icehouse'), | 92 | ('2014.1', 'icehouse'), |
384 | 92 | ('2014.2', 'juno'), | 93 | ('2014.2', 'juno'), |
385 | 93 | ('2015.1', 'kilo'), | 94 | ('2015.1', 'kilo'), |
386 | 95 | ('2015.2', 'liberty'), | ||
387 | 94 | ]) | 96 | ]) |
388 | 95 | 97 | ||
389 | 96 | # The ugly duckling | 98 | # The ugly duckling |
390 | @@ -113,6 +115,7 @@ | |||
391 | 113 | ('2.2.0', 'juno'), | 115 | ('2.2.0', 'juno'), |
392 | 114 | ('2.2.1', 'kilo'), | 116 | ('2.2.1', 'kilo'), |
393 | 115 | ('2.2.2', 'kilo'), | 117 | ('2.2.2', 'kilo'), |
394 | 118 | ('2.3.0', 'liberty'), | ||
395 | 116 | ]) | 119 | ]) |
396 | 117 | 120 | ||
397 | 118 | DEFAULT_LOOPBACK_SIZE = '5G' | 121 | DEFAULT_LOOPBACK_SIZE = '5G' |
398 | @@ -321,6 +324,9 @@ | |||
399 | 321 | 'kilo': 'trusty-updates/kilo', | 324 | 'kilo': 'trusty-updates/kilo', |
400 | 322 | 'kilo/updates': 'trusty-updates/kilo', | 325 | 'kilo/updates': 'trusty-updates/kilo', |
401 | 323 | 'kilo/proposed': 'trusty-proposed/kilo', | 326 | 'kilo/proposed': 'trusty-proposed/kilo', |
402 | 327 | 'liberty': 'trusty-updates/liberty', | ||
403 | 328 | 'liberty/updates': 'trusty-updates/liberty', | ||
404 | 329 | 'liberty/proposed': 'trusty-proposed/liberty', | ||
405 | 324 | } | 330 | } |
406 | 325 | 331 | ||
407 | 326 | try: | 332 | try: |
408 | @@ -549,6 +555,11 @@ | |||
409 | 549 | 555 | ||
410 | 550 | pip_create_virtualenv(os.path.join(parent_dir, 'venv')) | 556 | pip_create_virtualenv(os.path.join(parent_dir, 'venv')) |
411 | 551 | 557 | ||
412 | 558 | # Upgrade setuptools from default virtualenv version. The default version | ||
413 | 559 | # in trusty breaks update.py in global requirements master branch. | ||
414 | 560 | pip_install('setuptools', upgrade=True, proxy=http_proxy, | ||
415 | 561 | venv=os.path.join(parent_dir, 'venv')) | ||
416 | 562 | |||
417 | 552 | for p in projects['repositories']: | 563 | for p in projects['repositories']: |
418 | 553 | repo = p['repository'] | 564 | repo = p['repository'] |
419 | 554 | branch = p['branch'] | 565 | branch = p['branch'] |
420 | @@ -610,24 +621,24 @@ | |||
421 | 610 | else: | 621 | else: |
422 | 611 | repo_dir = dest_dir | 622 | repo_dir = dest_dir |
423 | 612 | 623 | ||
424 | 624 | venv = os.path.join(parent_dir, 'venv') | ||
425 | 625 | |||
426 | 613 | if update_requirements: | 626 | if update_requirements: |
427 | 614 | if not requirements_dir: | 627 | if not requirements_dir: |
428 | 615 | error_out('requirements repo must be cloned before ' | 628 | error_out('requirements repo must be cloned before ' |
429 | 616 | 'updating from global requirements.') | 629 | 'updating from global requirements.') |
431 | 617 | _git_update_requirements(repo_dir, requirements_dir) | 630 | _git_update_requirements(venv, repo_dir, requirements_dir) |
432 | 618 | 631 | ||
433 | 619 | juju_log('Installing git repo from dir: {}'.format(repo_dir)) | 632 | juju_log('Installing git repo from dir: {}'.format(repo_dir)) |
434 | 620 | if http_proxy: | 633 | if http_proxy: |
437 | 621 | pip_install(repo_dir, proxy=http_proxy, | 634 | pip_install(repo_dir, proxy=http_proxy, venv=venv) |
436 | 622 | venv=os.path.join(parent_dir, 'venv')) | ||
438 | 623 | else: | 635 | else: |
441 | 624 | pip_install(repo_dir, | 636 | pip_install(repo_dir, venv=venv) |
440 | 625 | venv=os.path.join(parent_dir, 'venv')) | ||
442 | 626 | 637 | ||
443 | 627 | return repo_dir | 638 | return repo_dir |
444 | 628 | 639 | ||
445 | 629 | 640 | ||
447 | 630 | def _git_update_requirements(package_dir, reqs_dir): | 641 | def _git_update_requirements(venv, package_dir, reqs_dir): |
448 | 631 | """ | 642 | """ |
449 | 632 | Update from global requirements. | 643 | Update from global requirements. |
450 | 633 | 644 | ||
451 | @@ -636,12 +647,14 @@ | |||
452 | 636 | """ | 647 | """ |
453 | 637 | orig_dir = os.getcwd() | 648 | orig_dir = os.getcwd() |
454 | 638 | os.chdir(reqs_dir) | 649 | os.chdir(reqs_dir) |
456 | 639 | cmd = ['python', 'update.py', package_dir] | 650 | python = os.path.join(venv, 'bin/python') |
457 | 651 | cmd = [python, 'update.py', package_dir] | ||
458 | 640 | try: | 652 | try: |
459 | 641 | subprocess.check_call(cmd) | 653 | subprocess.check_call(cmd) |
460 | 642 | except subprocess.CalledProcessError: | 654 | except subprocess.CalledProcessError: |
461 | 643 | package = os.path.basename(package_dir) | 655 | package = os.path.basename(package_dir) |
463 | 644 | error_out("Error updating {} from global-requirements.txt".format(package)) | 656 | error_out("Error updating {} from " |
464 | 657 | "global-requirements.txt".format(package)) | ||
465 | 645 | os.chdir(orig_dir) | 658 | os.chdir(orig_dir) |
466 | 646 | 659 | ||
467 | 647 | 660 | ||
468 | 648 | 661 | ||
469 | === modified file 'hooks/charmhelpers/core/host.py' | |||
470 | --- hooks/charmhelpers/core/host.py 2015-06-10 20:44:02 +0000 | |||
471 | +++ hooks/charmhelpers/core/host.py 2015-06-19 16:34:56 +0000 | |||
472 | @@ -24,6 +24,7 @@ | |||
473 | 24 | import os | 24 | import os |
474 | 25 | import re | 25 | import re |
475 | 26 | import pwd | 26 | import pwd |
476 | 27 | import glob | ||
477 | 27 | import grp | 28 | import grp |
478 | 28 | import random | 29 | import random |
479 | 29 | import string | 30 | import string |
480 | @@ -269,6 +270,21 @@ | |||
481 | 269 | return None | 270 | return None |
482 | 270 | 271 | ||
483 | 271 | 272 | ||
484 | 273 | def path_hash(path): | ||
485 | 274 | """ | ||
486 | 275 | Generate a hash checksum of all files matching 'path'. Standard wildcards | ||
487 | 276 | like '*' and '?' are supported, see documentation for the 'glob' module for | ||
488 | 277 | more information. | ||
489 | 278 | |||
490 | 279 | :return: dict: A { filename: hash } dictionary for all matched files. | ||
491 | 280 | Empty if none found. | ||
492 | 281 | """ | ||
493 | 282 | return { | ||
494 | 283 | filename: file_hash(filename) | ||
495 | 284 | for filename in glob.iglob(path) | ||
496 | 285 | } | ||
497 | 286 | |||
498 | 287 | |||
499 | 272 | def check_hash(path, checksum, hash_type='md5'): | 288 | def check_hash(path, checksum, hash_type='md5'): |
500 | 273 | """ | 289 | """ |
501 | 274 | Validate a file using a cryptographic checksum. | 290 | Validate a file using a cryptographic checksum. |
502 | @@ -296,23 +312,25 @@ | |||
503 | 296 | 312 | ||
504 | 297 | @restart_on_change({ | 313 | @restart_on_change({ |
505 | 298 | '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ] | 314 | '/etc/ceph/ceph.conf': [ 'cinder-api', 'cinder-volume' ] |
506 | 315 | '/etc/apache/sites-enabled/*': [ 'apache2' ] | ||
507 | 299 | }) | 316 | }) |
509 | 300 | def ceph_client_changed(): | 317 | def config_changed(): |
510 | 301 | pass # your code here | 318 | pass # your code here |
511 | 302 | 319 | ||
512 | 303 | In this example, the cinder-api and cinder-volume services | 320 | In this example, the cinder-api and cinder-volume services |
513 | 304 | would be restarted if /etc/ceph/ceph.conf is changed by the | 321 | would be restarted if /etc/ceph/ceph.conf is changed by the |
515 | 305 | ceph_client_changed function. | 322 | ceph_client_changed function. The apache2 service would be |
516 | 323 | restarted if any file matching the pattern got changed, created | ||
517 | 324 | or removed. Standard wildcards are supported, see documentation | ||
518 | 325 | for the 'glob' module for more information. | ||
519 | 306 | """ | 326 | """ |
520 | 307 | def wrap(f): | 327 | def wrap(f): |
521 | 308 | def wrapped_f(*args, **kwargs): | 328 | def wrapped_f(*args, **kwargs): |
525 | 309 | checksums = {} | 329 | checksums = {path: path_hash(path) for path in restart_map} |
523 | 310 | for path in restart_map: | ||
524 | 311 | checksums[path] = file_hash(path) | ||
526 | 312 | f(*args, **kwargs) | 330 | f(*args, **kwargs) |
527 | 313 | restarts = [] | 331 | restarts = [] |
528 | 314 | for path in restart_map: | 332 | for path in restart_map: |
530 | 315 | if checksums[path] != file_hash(path): | 333 | if path_hash(path) != checksums[path]: |
531 | 316 | restarts += restart_map[path] | 334 | restarts += restart_map[path] |
532 | 317 | services_list = list(OrderedDict.fromkeys(restarts)) | 335 | services_list = list(OrderedDict.fromkeys(restarts)) |
533 | 318 | if not stopstart: | 336 | if not stopstart: |
534 | 319 | 337 | ||
535 | === modified file 'tests/charmhelpers/contrib/amulet/utils.py' | |||
536 | --- tests/charmhelpers/contrib/amulet/utils.py 2015-05-05 20:29:17 +0000 | |||
537 | +++ tests/charmhelpers/contrib/amulet/utils.py 2015-06-19 16:34:56 +0000 | |||
538 | @@ -15,13 +15,15 @@ | |||
539 | 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/>. |
540 | 16 | 16 | ||
541 | 17 | import ConfigParser | 17 | import ConfigParser |
542 | 18 | import distro_info | ||
543 | 18 | import io | 19 | import io |
544 | 19 | import logging | 20 | import logging |
545 | 21 | import os | ||
546 | 20 | import re | 22 | import re |
547 | 23 | import six | ||
548 | 21 | import sys | 24 | import sys |
549 | 22 | import time | 25 | import time |
552 | 23 | 26 | import urlparse | |
551 | 24 | import six | ||
553 | 25 | 27 | ||
554 | 26 | 28 | ||
555 | 27 | class AmuletUtils(object): | 29 | class AmuletUtils(object): |
556 | @@ -33,6 +35,7 @@ | |||
557 | 33 | 35 | ||
558 | 34 | def __init__(self, log_level=logging.ERROR): | 36 | def __init__(self, log_level=logging.ERROR): |
559 | 35 | self.log = self.get_logger(level=log_level) | 37 | self.log = self.get_logger(level=log_level) |
560 | 38 | self.ubuntu_releases = self.get_ubuntu_releases() | ||
561 | 36 | 39 | ||
562 | 37 | def get_logger(self, name="amulet-logger", level=logging.DEBUG): | 40 | def get_logger(self, name="amulet-logger", level=logging.DEBUG): |
563 | 38 | """Get a logger object that will log to stdout.""" | 41 | """Get a logger object that will log to stdout.""" |
564 | @@ -70,12 +73,44 @@ | |||
565 | 70 | else: | 73 | else: |
566 | 71 | return False | 74 | return False |
567 | 72 | 75 | ||
568 | 76 | def get_ubuntu_release_from_sentry(self, sentry_unit): | ||
569 | 77 | """Get Ubuntu release codename from sentry unit. | ||
570 | 78 | |||
571 | 79 | :param sentry_unit: amulet sentry/service unit pointer | ||
572 | 80 | :returns: list of strings - release codename, failure message | ||
573 | 81 | """ | ||
574 | 82 | msg = None | ||
575 | 83 | cmd = 'lsb_release -cs' | ||
576 | 84 | release, code = sentry_unit.run(cmd) | ||
577 | 85 | if code == 0: | ||
578 | 86 | self.log.debug('{} lsb_release: {}'.format( | ||
579 | 87 | sentry_unit.info['unit_name'], release)) | ||
580 | 88 | else: | ||
581 | 89 | msg = ('{} `{}` returned {} ' | ||
582 | 90 | '{}'.format(sentry_unit.info['unit_name'], | ||
583 | 91 | cmd, release, code)) | ||
584 | 92 | if release not in self.ubuntu_releases: | ||
585 | 93 | msg = ("Release ({}) not found in Ubuntu releases " | ||
586 | 94 | "({})".format(release, self.ubuntu_releases)) | ||
587 | 95 | return release, msg | ||
588 | 96 | |||
589 | 73 | def validate_services(self, commands): | 97 | def validate_services(self, commands): |
593 | 74 | """Validate services. | 98 | """Validate that lists of commands succeed on service units. Can be |
594 | 75 | 99 | used to verify system services are running on the corresponding | |
592 | 76 | Verify the specified services are running on the corresponding | ||
595 | 77 | service units. | 100 | service units. |
597 | 78 | """ | 101 | |
598 | 102 | :param commands: dict with sentry keys and arbitrary command list vals | ||
599 | 103 | :returns: None if successful, Failure string message otherwise | ||
600 | 104 | """ | ||
601 | 105 | self.log.debug('Checking status of system services...') | ||
602 | 106 | |||
603 | 107 | # /!\ DEPRECATION WARNING (beisner): | ||
604 | 108 | # New and existing tests should be rewritten to use | ||
605 | 109 | # validate_services_by_name() as it is aware of init systems. | ||
606 | 110 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | ||
607 | 111 | 'validate_services_by_name instead of validate_services ' | ||
608 | 112 | 'due to init system differences.') | ||
609 | 113 | |||
610 | 79 | for k, v in six.iteritems(commands): | 114 | for k, v in six.iteritems(commands): |
611 | 80 | for cmd in v: | 115 | for cmd in v: |
612 | 81 | output, code = k.run(cmd) | 116 | output, code = k.run(cmd) |
613 | @@ -86,6 +121,41 @@ | |||
614 | 86 | return "command `{}` returned {}".format(cmd, str(code)) | 121 | return "command `{}` returned {}".format(cmd, str(code)) |
615 | 87 | return None | 122 | return None |
616 | 88 | 123 | ||
617 | 124 | def validate_services_by_name(self, sentry_services): | ||
618 | 125 | """Validate system service status by service name, automatically | ||
619 | 126 | detecting init system based on Ubuntu release codename. | ||
620 | 127 | |||
621 | 128 | :param sentry_services: dict with sentry keys and svc list values | ||
622 | 129 | :returns: None if successful, Failure string message otherwise | ||
623 | 130 | """ | ||
624 | 131 | self.log.debug('Checking status of system services...') | ||
625 | 132 | |||
626 | 133 | # Point at which systemd became a thing | ||
627 | 134 | systemd_switch = self.ubuntu_releases.index('vivid') | ||
628 | 135 | |||
629 | 136 | for sentry_unit, services_list in six.iteritems(sentry_services): | ||
630 | 137 | # Get lsb_release codename from unit | ||
631 | 138 | release, ret = self.get_ubuntu_release_from_sentry(sentry_unit) | ||
632 | 139 | if ret: | ||
633 | 140 | return ret | ||
634 | 141 | |||
635 | 142 | for service_name in services_list: | ||
636 | 143 | if (self.ubuntu_releases.index(release) >= systemd_switch or | ||
637 | 144 | service_name == "rabbitmq-server"): | ||
638 | 145 | # init is systemd | ||
639 | 146 | cmd = 'sudo service {} status'.format(service_name) | ||
640 | 147 | elif self.ubuntu_releases.index(release) < systemd_switch: | ||
641 | 148 | # init is upstart | ||
642 | 149 | cmd = 'sudo status {}'.format(service_name) | ||
643 | 150 | |||
644 | 151 | output, code = sentry_unit.run(cmd) | ||
645 | 152 | self.log.debug('{} `{}` returned ' | ||
646 | 153 | '{}'.format(sentry_unit.info['unit_name'], | ||
647 | 154 | cmd, code)) | ||
648 | 155 | if code != 0: | ||
649 | 156 | return "command `{}` returned {}".format(cmd, str(code)) | ||
650 | 157 | return None | ||
651 | 158 | |||
652 | 89 | def _get_config(self, unit, filename): | 159 | def _get_config(self, unit, filename): |
653 | 90 | """Get a ConfigParser object for parsing a unit's config file.""" | 160 | """Get a ConfigParser object for parsing a unit's config file.""" |
654 | 91 | file_contents = unit.file_contents(filename) | 161 | file_contents = unit.file_contents(filename) |
655 | @@ -104,6 +174,9 @@ | |||
656 | 104 | Verify that the specified section of the config file contains | 174 | Verify that the specified section of the config file contains |
657 | 105 | the expected option key:value pairs. | 175 | the expected option key:value pairs. |
658 | 106 | """ | 176 | """ |
659 | 177 | self.log.debug('Validating config file data ({} in {} on {})' | ||
660 | 178 | '...'.format(section, config_file, | ||
661 | 179 | sentry_unit.info['unit_name'])) | ||
662 | 107 | config = self._get_config(sentry_unit, config_file) | 180 | config = self._get_config(sentry_unit, config_file) |
663 | 108 | 181 | ||
664 | 109 | if section != 'DEFAULT' and not config.has_section(section): | 182 | if section != 'DEFAULT' and not config.has_section(section): |
665 | @@ -321,3 +394,15 @@ | |||
666 | 321 | 394 | ||
667 | 322 | def endpoint_error(self, name, data): | 395 | def endpoint_error(self, name, data): |
668 | 323 | return 'unexpected endpoint data in {} - {}'.format(name, data) | 396 | return 'unexpected endpoint data in {} - {}'.format(name, data) |
669 | 397 | |||
670 | 398 | def get_ubuntu_releases(self): | ||
671 | 399 | """Return a list of all Ubuntu releases in order of release.""" | ||
672 | 400 | _d = distro_info.UbuntuDistroInfo() | ||
673 | 401 | _release_list = _d.all | ||
674 | 402 | self.log.debug('Ubuntu release list: {}'.format(_release_list)) | ||
675 | 403 | return _release_list | ||
676 | 404 | |||
677 | 405 | def file_to_url(self, file_rel_path): | ||
678 | 406 | """Convert a relative file path to a file URL.""" | ||
679 | 407 | _abs_path = os.path.abspath(file_rel_path) | ||
680 | 408 | return urlparse.urlparse(_abs_path, scheme='file').geturl() | ||
681 | 324 | 409 | ||
682 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
683 | --- tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-10 13:59:24 +0000 | |||
684 | +++ tests/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-19 16:34:56 +0000 | |||
685 | @@ -110,7 +110,8 @@ | |||
686 | 110 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, | 110 | (self.precise_essex, self.precise_folsom, self.precise_grizzly, |
687 | 111 | self.precise_havana, self.precise_icehouse, | 111 | self.precise_havana, self.precise_icehouse, |
688 | 112 | self.trusty_icehouse, self.trusty_juno, self.utopic_juno, | 112 | self.trusty_icehouse, self.trusty_juno, self.utopic_juno, |
690 | 113 | self.trusty_kilo, self.vivid_kilo) = range(10) | 113 | self.trusty_kilo, self.vivid_kilo, self.trusty_liberty, |
691 | 114 | self.wily_liberty) = range(12) | ||
692 | 114 | 115 | ||
693 | 115 | releases = { | 116 | releases = { |
694 | 116 | ('precise', None): self.precise_essex, | 117 | ('precise', None): self.precise_essex, |
695 | @@ -121,8 +122,10 @@ | |||
696 | 121 | ('trusty', None): self.trusty_icehouse, | 122 | ('trusty', None): self.trusty_icehouse, |
697 | 122 | ('trusty', 'cloud:trusty-juno'): self.trusty_juno, | 123 | ('trusty', 'cloud:trusty-juno'): self.trusty_juno, |
698 | 123 | ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo, | 124 | ('trusty', 'cloud:trusty-kilo'): self.trusty_kilo, |
699 | 125 | ('trusty', 'cloud:trusty-liberty'): self.trusty_liberty, | ||
700 | 124 | ('utopic', None): self.utopic_juno, | 126 | ('utopic', None): self.utopic_juno, |
702 | 125 | ('vivid', None): self.vivid_kilo} | 127 | ('vivid', None): self.vivid_kilo, |
703 | 128 | ('wily', None): self.wily_liberty} | ||
704 | 126 | return releases[(self.series, self.openstack)] | 129 | return releases[(self.series, self.openstack)] |
705 | 127 | 130 | ||
706 | 128 | def _get_openstack_release_string(self): | 131 | def _get_openstack_release_string(self): |
707 | @@ -138,6 +141,7 @@ | |||
708 | 138 | ('trusty', 'icehouse'), | 141 | ('trusty', 'icehouse'), |
709 | 139 | ('utopic', 'juno'), | 142 | ('utopic', 'juno'), |
710 | 140 | ('vivid', 'kilo'), | 143 | ('vivid', 'kilo'), |
711 | 144 | ('wily', 'liberty'), | ||
712 | 141 | ]) | 145 | ]) |
713 | 142 | if self.openstack: | 146 | if self.openstack: |
714 | 143 | os_origin = self.openstack.split(':')[1] | 147 | os_origin = self.openstack.split(':')[1] |
715 | 144 | 148 | ||
716 | === modified file 'tests/charmhelpers/contrib/openstack/amulet/utils.py' | |||
717 | --- tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-03-11 11:45:09 +0000 | |||
718 | +++ tests/charmhelpers/contrib/openstack/amulet/utils.py 2015-06-19 16:34:56 +0000 | |||
719 | @@ -16,15 +16,15 @@ | |||
720 | 16 | 16 | ||
721 | 17 | import logging | 17 | import logging |
722 | 18 | import os | 18 | import os |
723 | 19 | import six | ||
724 | 19 | import time | 20 | import time |
725 | 20 | import urllib | 21 | import urllib |
726 | 21 | 22 | ||
727 | 22 | import glanceclient.v1.client as glance_client | 23 | import glanceclient.v1.client as glance_client |
728 | 24 | import heatclient.v1.client as heat_client | ||
729 | 23 | import keystoneclient.v2_0 as keystone_client | 25 | import keystoneclient.v2_0 as keystone_client |
730 | 24 | import novaclient.v1_1.client as nova_client | 26 | import novaclient.v1_1.client as nova_client |
731 | 25 | 27 | ||
732 | 26 | import six | ||
733 | 27 | |||
734 | 28 | from charmhelpers.contrib.amulet.utils import ( | 28 | from charmhelpers.contrib.amulet.utils import ( |
735 | 29 | AmuletUtils | 29 | AmuletUtils |
736 | 30 | ) | 30 | ) |
737 | @@ -37,7 +37,7 @@ | |||
738 | 37 | """OpenStack amulet utilities. | 37 | """OpenStack amulet utilities. |
739 | 38 | 38 | ||
740 | 39 | This class inherits from AmuletUtils and has additional support | 39 | This class inherits from AmuletUtils and has additional support |
742 | 40 | that is specifically for use by OpenStack charms. | 40 | that is specifically for use by OpenStack charm tests. |
743 | 41 | """ | 41 | """ |
744 | 42 | 42 | ||
745 | 43 | def __init__(self, log_level=ERROR): | 43 | def __init__(self, log_level=ERROR): |
746 | @@ -51,6 +51,8 @@ | |||
747 | 51 | Validate actual endpoint data vs expected endpoint data. The ports | 51 | Validate actual endpoint data vs expected endpoint data. The ports |
748 | 52 | are used to find the matching endpoint. | 52 | are used to find the matching endpoint. |
749 | 53 | """ | 53 | """ |
750 | 54 | self.log.debug('Validating endpoint data...') | ||
751 | 55 | self.log.debug('actual: {}'.format(repr(endpoints))) | ||
752 | 54 | found = False | 56 | found = False |
753 | 55 | for ep in endpoints: | 57 | for ep in endpoints: |
754 | 56 | self.log.debug('endpoint: {}'.format(repr(ep))) | 58 | self.log.debug('endpoint: {}'.format(repr(ep))) |
755 | @@ -77,6 +79,7 @@ | |||
756 | 77 | Validate a list of actual service catalog endpoints vs a list of | 79 | Validate a list of actual service catalog endpoints vs a list of |
757 | 78 | expected service catalog endpoints. | 80 | expected service catalog endpoints. |
758 | 79 | """ | 81 | """ |
759 | 82 | self.log.debug('Validating service catalog endpoint data...') | ||
760 | 80 | self.log.debug('actual: {}'.format(repr(actual))) | 83 | self.log.debug('actual: {}'.format(repr(actual))) |
761 | 81 | for k, v in six.iteritems(expected): | 84 | for k, v in six.iteritems(expected): |
762 | 82 | if k in actual: | 85 | if k in actual: |
763 | @@ -93,6 +96,7 @@ | |||
764 | 93 | Validate a list of actual tenant data vs list of expected tenant | 96 | Validate a list of actual tenant data vs list of expected tenant |
765 | 94 | data. | 97 | data. |
766 | 95 | """ | 98 | """ |
767 | 99 | self.log.debug('Validating tenant data...') | ||
768 | 96 | self.log.debug('actual: {}'.format(repr(actual))) | 100 | self.log.debug('actual: {}'.format(repr(actual))) |
769 | 97 | for e in expected: | 101 | for e in expected: |
770 | 98 | found = False | 102 | found = False |
771 | @@ -114,6 +118,7 @@ | |||
772 | 114 | Validate a list of actual role data vs a list of expected role | 118 | Validate a list of actual role data vs a list of expected role |
773 | 115 | data. | 119 | data. |
774 | 116 | """ | 120 | """ |
775 | 121 | self.log.debug('Validating role data...') | ||
776 | 117 | self.log.debug('actual: {}'.format(repr(actual))) | 122 | self.log.debug('actual: {}'.format(repr(actual))) |
777 | 118 | for e in expected: | 123 | for e in expected: |
778 | 119 | found = False | 124 | found = False |
779 | @@ -134,6 +139,7 @@ | |||
780 | 134 | Validate a list of actual user data vs a list of expected user | 139 | Validate a list of actual user data vs a list of expected user |
781 | 135 | data. | 140 | data. |
782 | 136 | """ | 141 | """ |
783 | 142 | self.log.debug('Validating user data...') | ||
784 | 137 | self.log.debug('actual: {}'.format(repr(actual))) | 143 | self.log.debug('actual: {}'.format(repr(actual))) |
785 | 138 | for e in expected: | 144 | for e in expected: |
786 | 139 | found = False | 145 | found = False |
787 | @@ -155,17 +161,20 @@ | |||
788 | 155 | 161 | ||
789 | 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. |
790 | 157 | """ | 163 | """ |
791 | 164 | self.log.debug('Validating flavor data...') | ||
792 | 158 | self.log.debug('actual: {}'.format(repr(actual))) | 165 | self.log.debug('actual: {}'.format(repr(actual))) |
793 | 159 | act = [a.name for a in actual] | 166 | act = [a.name for a in actual] |
794 | 160 | return self._validate_list_data(expected, act) | 167 | return self._validate_list_data(expected, act) |
795 | 161 | 168 | ||
796 | 162 | def tenant_exists(self, keystone, tenant): | 169 | def tenant_exists(self, keystone, tenant): |
797 | 163 | """Return True if tenant exists.""" | 170 | """Return True if tenant exists.""" |
798 | 171 | self.log.debug('Checking if tenant exists ({})...'.format(tenant)) | ||
799 | 164 | return tenant in [t.name for t in keystone.tenants.list()] | 172 | return tenant in [t.name for t in keystone.tenants.list()] |
800 | 165 | 173 | ||
801 | 166 | def authenticate_keystone_admin(self, keystone_sentry, user, password, | 174 | def authenticate_keystone_admin(self, keystone_sentry, user, password, |
802 | 167 | tenant): | 175 | tenant): |
803 | 168 | """Authenticates admin user with the keystone admin endpoint.""" | 176 | """Authenticates admin user with the keystone admin endpoint.""" |
804 | 177 | self.log.debug('Authenticating keystone admin...') | ||
805 | 169 | unit = keystone_sentry | 178 | unit = keystone_sentry |
806 | 170 | service_ip = unit.relation('shared-db', | 179 | service_ip = unit.relation('shared-db', |
807 | 171 | 'mysql:shared-db')['private-address'] | 180 | 'mysql:shared-db')['private-address'] |
808 | @@ -175,6 +184,7 @@ | |||
809 | 175 | 184 | ||
810 | 176 | def authenticate_keystone_user(self, keystone, user, password, tenant): | 185 | def authenticate_keystone_user(self, keystone, user, password, tenant): |
811 | 177 | """Authenticates a regular user with the keystone public endpoint.""" | 186 | """Authenticates a regular user with the keystone public endpoint.""" |
812 | 187 | self.log.debug('Authenticating keystone user ({})...'.format(user)) | ||
813 | 178 | ep = keystone.service_catalog.url_for(service_type='identity', | 188 | ep = keystone.service_catalog.url_for(service_type='identity', |
814 | 179 | endpoint_type='publicURL') | 189 | endpoint_type='publicURL') |
815 | 180 | return keystone_client.Client(username=user, password=password, | 190 | return keystone_client.Client(username=user, password=password, |
816 | @@ -182,12 +192,21 @@ | |||
817 | 182 | 192 | ||
818 | 183 | def authenticate_glance_admin(self, keystone): | 193 | def authenticate_glance_admin(self, keystone): |
819 | 184 | """Authenticates admin user with glance.""" | 194 | """Authenticates admin user with glance.""" |
820 | 195 | self.log.debug('Authenticating glance admin...') | ||
821 | 185 | ep = keystone.service_catalog.url_for(service_type='image', | 196 | ep = keystone.service_catalog.url_for(service_type='image', |
822 | 186 | endpoint_type='adminURL') | 197 | endpoint_type='adminURL') |
823 | 187 | return glance_client.Client(ep, token=keystone.auth_token) | 198 | return glance_client.Client(ep, token=keystone.auth_token) |
824 | 188 | 199 | ||
825 | 200 | def authenticate_heat_admin(self, keystone): | ||
826 | 201 | """Authenticates the admin user with heat.""" | ||
827 | 202 | self.log.debug('Authenticating heat admin...') | ||
828 | 203 | ep = keystone.service_catalog.url_for(service_type='orchestration', | ||
829 | 204 | endpoint_type='publicURL') | ||
830 | 205 | return heat_client.Client(endpoint=ep, token=keystone.auth_token) | ||
831 | 206 | |||
832 | 189 | def authenticate_nova_user(self, keystone, user, password, tenant): | 207 | def authenticate_nova_user(self, keystone, user, password, tenant): |
833 | 190 | """Authenticates a regular user with nova-api.""" | 208 | """Authenticates a regular user with nova-api.""" |
834 | 209 | self.log.debug('Authenticating nova user ({})...'.format(user)) | ||
835 | 191 | ep = keystone.service_catalog.url_for(service_type='identity', | 210 | ep = keystone.service_catalog.url_for(service_type='identity', |
836 | 192 | endpoint_type='publicURL') | 211 | endpoint_type='publicURL') |
837 | 193 | return nova_client.Client(username=user, api_key=password, | 212 | return nova_client.Client(username=user, api_key=password, |
838 | @@ -195,6 +214,7 @@ | |||
839 | 195 | 214 | ||
840 | 196 | def create_cirros_image(self, glance, image_name): | 215 | def create_cirros_image(self, glance, image_name): |
841 | 197 | """Download the latest cirros image and upload it to glance.""" | 216 | """Download the latest cirros image and upload it to glance.""" |
842 | 217 | self.log.debug('Creating glance image ({})...'.format(image_name)) | ||
843 | 198 | http_proxy = os.getenv('AMULET_HTTP_PROXY') | 218 | http_proxy = os.getenv('AMULET_HTTP_PROXY') |
844 | 199 | self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy)) | 219 | self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy)) |
845 | 200 | if http_proxy: | 220 | if http_proxy: |
846 | @@ -235,6 +255,11 @@ | |||
847 | 235 | 255 | ||
848 | 236 | def delete_image(self, glance, image): | 256 | def delete_image(self, glance, image): |
849 | 237 | """Delete the specified image.""" | 257 | """Delete the specified image.""" |
850 | 258 | |||
851 | 259 | # /!\ DEPRECATION WARNING | ||
852 | 260 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | ||
853 | 261 | 'delete_resource instead of delete_image.') | ||
854 | 262 | self.log.debug('Deleting glance image ({})...'.format(image)) | ||
855 | 238 | num_before = len(list(glance.images.list())) | 263 | num_before = len(list(glance.images.list())) |
856 | 239 | glance.images.delete(image) | 264 | glance.images.delete(image) |
857 | 240 | 265 | ||
858 | @@ -254,6 +279,8 @@ | |||
859 | 254 | 279 | ||
860 | 255 | def create_instance(self, nova, image_name, instance_name, flavor): | 280 | def create_instance(self, nova, image_name, instance_name, flavor): |
861 | 256 | """Create the specified instance.""" | 281 | """Create the specified instance.""" |
862 | 282 | self.log.debug('Creating instance ' | ||
863 | 283 | '({}|{}|{})'.format(instance_name, image_name, flavor)) | ||
864 | 257 | image = nova.images.find(name=image_name) | 284 | image = nova.images.find(name=image_name) |
865 | 258 | flavor = nova.flavors.find(name=flavor) | 285 | flavor = nova.flavors.find(name=flavor) |
866 | 259 | instance = nova.servers.create(name=instance_name, image=image, | 286 | instance = nova.servers.create(name=instance_name, image=image, |
867 | @@ -276,6 +303,11 @@ | |||
868 | 276 | 303 | ||
869 | 277 | def delete_instance(self, nova, instance): | 304 | def delete_instance(self, nova, instance): |
870 | 278 | """Delete the specified instance.""" | 305 | """Delete the specified instance.""" |
871 | 306 | |||
872 | 307 | # /!\ DEPRECATION WARNING | ||
873 | 308 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | ||
874 | 309 | 'delete_resource instead of delete_instance.') | ||
875 | 310 | self.log.debug('Deleting instance ({})...'.format(instance)) | ||
876 | 279 | num_before = len(list(nova.servers.list())) | 311 | num_before = len(list(nova.servers.list())) |
877 | 280 | nova.servers.delete(instance) | 312 | nova.servers.delete(instance) |
878 | 281 | 313 | ||
879 | @@ -292,3 +324,90 @@ | |||
880 | 292 | return False | 324 | return False |
881 | 293 | 325 | ||
882 | 294 | return True | 326 | return True |
883 | 327 | |||
884 | 328 | def create_or_get_keypair(self, nova, keypair_name="testkey"): | ||
885 | 329 | """Create a new keypair, or return pointer if it already exists.""" | ||
886 | 330 | try: | ||
887 | 331 | _keypair = nova.keypairs.get(keypair_name) | ||
888 | 332 | self.log.debug('Keypair ({}) already exists, ' | ||
889 | 333 | 'using it.'.format(keypair_name)) | ||
890 | 334 | return _keypair | ||
891 | 335 | except: | ||
892 | 336 | self.log.debug('Keypair ({}) does not exist, ' | ||
893 | 337 | 'creating it.'.format(keypair_name)) | ||
894 | 338 | |||
895 | 339 | _keypair = nova.keypairs.create(name=keypair_name) | ||
896 | 340 | return _keypair | ||
897 | 341 | |||
898 | 342 | def delete_resource(self, resource, resource_id, | ||
899 | 343 | msg="resource", max_wait=120): | ||
900 | 344 | """Delete one openstack resource, such as one instance, keypair, | ||
901 | 345 | image, volume, stack, etc., and confirm deletion within max wait time. | ||
902 | 346 | |||
903 | 347 | :param resource: pointer to os resource type, ex:glance_client.images | ||
904 | 348 | :param resource_id: unique name or id for the openstack resource | ||
905 | 349 | :param msg: text to identify purpose in logging | ||
906 | 350 | :param max_wait: maximum wait time in seconds | ||
907 | 351 | :returns: True if successful, otherwise False | ||
908 | 352 | """ | ||
909 | 353 | num_before = len(list(resource.list())) | ||
910 | 354 | resource.delete(resource_id) | ||
911 | 355 | |||
912 | 356 | tries = 0 | ||
913 | 357 | num_after = len(list(resource.list())) | ||
914 | 358 | while num_after != (num_before - 1) and tries < (max_wait / 4): | ||
915 | 359 | self.log.debug('{} delete check: ' | ||
916 | 360 | '{} [{}:{}] {}'.format(msg, tries, | ||
917 | 361 | num_before, | ||
918 | 362 | num_after, | ||
919 | 363 | resource_id)) | ||
920 | 364 | time.sleep(4) | ||
921 | 365 | num_after = len(list(resource.list())) | ||
922 | 366 | tries += 1 | ||
923 | 367 | |||
924 | 368 | self.log.debug('{}: expected, actual count = {}, ' | ||
925 | 369 | '{}'.format(msg, num_before - 1, num_after)) | ||
926 | 370 | |||
927 | 371 | if num_after == (num_before - 1): | ||
928 | 372 | return True | ||
929 | 373 | else: | ||
930 | 374 | self.log.error('{} delete timed out'.format(msg)) | ||
931 | 375 | return False | ||
932 | 376 | |||
933 | 377 | def resource_reaches_status(self, resource, resource_id, | ||
934 | 378 | expected_stat='available', | ||
935 | 379 | msg='resource', max_wait=120): | ||
936 | 380 | """Wait for an openstack resources status to reach an | ||
937 | 381 | expected status within a specified time. Useful to confirm that | ||
938 | 382 | nova instances, cinder vols, snapshots, glance images, heat stacks | ||
939 | 383 | and other resources eventually reach the expected status. | ||
940 | 384 | |||
941 | 385 | :param resource: pointer to os resource type, ex: heat_client.stacks | ||
942 | 386 | :param resource_id: unique id for the openstack resource | ||
943 | 387 | :param expected_stat: status to expect resource to reach | ||
944 | 388 | :param msg: text to identify purpose in logging | ||
945 | 389 | :param max_wait: maximum wait time in seconds | ||
946 | 390 | :returns: True if successful, False if status is not reached | ||
947 | 391 | """ | ||
948 | 392 | |||
949 | 393 | tries = 0 | ||
950 | 394 | resource_stat = resource.get(resource_id).status | ||
951 | 395 | while resource_stat != expected_stat and tries < (max_wait / 4): | ||
952 | 396 | self.log.debug('{} status check: ' | ||
953 | 397 | '{} [{}:{}] {}'.format(msg, tries, | ||
954 | 398 | resource_stat, | ||
955 | 399 | expected_stat, | ||
956 | 400 | resource_id)) | ||
957 | 401 | time.sleep(4) | ||
958 | 402 | resource_stat = resource.get(resource_id).status | ||
959 | 403 | tries += 1 | ||
960 | 404 | |||
961 | 405 | self.log.debug('{}: expected, actual status = {}, ' | ||
962 | 406 | '{}'.format(msg, resource_stat, expected_stat)) | ||
963 | 407 | |||
964 | 408 | if resource_stat == expected_stat: | ||
965 | 409 | return True | ||
966 | 410 | else: | ||
967 | 411 | self.log.debug('{} never reached expected status: ' | ||
968 | 412 | '{}'.format(resource_id, expected_stat)) | ||
969 | 413 | return False |
charm_lint_check #5481 keystone-next for corey.bryant mp262483
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/5481/