Merge lp:~corey.bryant/charms/trusty/cinder/sync-ch into lp:~openstack-charmers-archive/charms/trusty/cinder/next
- Trusty Tahr (14.04)
- sync-ch
- Merge into next
Proposed by
Corey Bryant
Status: | Merged |
---|---|
Merged at revision: | 106 |
Proposed branch: | lp:~corey.bryant/charms/trusty/cinder/sync-ch |
Merge into: | lp:~openstack-charmers-archive/charms/trusty/cinder/next |
Diff against target: |
740 lines (+370/-99) 12 files modified
hooks/charmhelpers/contrib/openstack/amulet/deployment.py (+36/-3) hooks/charmhelpers/contrib/openstack/amulet/utils.py (+240/-49) hooks/charmhelpers/contrib/openstack/context.py (+8/-7) hooks/charmhelpers/contrib/openstack/templates/ceph.conf (+6/-6) hooks/charmhelpers/contrib/openstack/utils.py (+9/-5) hooks/charmhelpers/contrib/storage/linux/ceph.py (+6/-6) hooks/charmhelpers/core/hookenv.py (+1/-0) hooks/charmhelpers/core/host.py (+31/-5) hooks/charmhelpers/core/services/helpers.py (+2/-2) hooks/charmhelpers/fetch/__init__.py (+23/-14) hooks/charmhelpers/fetch/archiveurl.py (+7/-1) hooks/charmhelpers/fetch/giturl.py (+1/-1) |
To merge this branch: | bzr merge lp:~corey.bryant/charms/trusty/cinder/sync-ch |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
OpenStack Charmers | Pending | ||
Review via email: mp+265046@code.launchpad.net |
Commit message
Description of the change
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 #5916 cinder-next for corey.bryant mp265046
UNIT OK: passed
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote : | # |
charm_amulet_test #5139 cinder-next for corey.bryant mp265046
AMULET OK: passed
Build: http://
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py' | |||
2 | --- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-19 16:29:27 +0000 | |||
3 | +++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-07-16 20:31:27 +0000 | |||
4 | @@ -79,9 +79,9 @@ | |||
5 | 79 | services.append(this_service) | 79 | services.append(this_service) |
6 | 80 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', | 80 | use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph', |
7 | 81 | 'ceph-osd', 'ceph-radosgw'] | 81 | 'ceph-osd', 'ceph-radosgw'] |
11 | 82 | # Openstack subordinate charms do not expose an origin option as that | 82 | # Most OpenStack subordinate charms do not expose an origin option |
12 | 83 | # is controlled by the principle | 83 | # as that is controlled by the principle. |
13 | 84 | ignore = ['neutron-openvswitch'] | 84 | ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch'] |
14 | 85 | 85 | ||
15 | 86 | if self.openstack: | 86 | if self.openstack: |
16 | 87 | for svc in services: | 87 | for svc in services: |
17 | @@ -148,3 +148,36 @@ | |||
18 | 148 | return os_origin.split('%s-' % self.series)[1].split('/')[0] | 148 | return os_origin.split('%s-' % self.series)[1].split('/')[0] |
19 | 149 | else: | 149 | else: |
20 | 150 | return releases[self.series] | 150 | return releases[self.series] |
21 | 151 | |||
22 | 152 | def get_ceph_expected_pools(self, radosgw=False): | ||
23 | 153 | """Return a list of expected ceph pools in a ceph + cinder + glance | ||
24 | 154 | test scenario, based on OpenStack release and whether ceph radosgw | ||
25 | 155 | is flagged as present or not.""" | ||
26 | 156 | |||
27 | 157 | if self._get_openstack_release() >= self.trusty_kilo: | ||
28 | 158 | # Kilo or later | ||
29 | 159 | pools = [ | ||
30 | 160 | 'rbd', | ||
31 | 161 | 'cinder', | ||
32 | 162 | 'glance' | ||
33 | 163 | ] | ||
34 | 164 | else: | ||
35 | 165 | # Juno or earlier | ||
36 | 166 | pools = [ | ||
37 | 167 | 'data', | ||
38 | 168 | 'metadata', | ||
39 | 169 | 'rbd', | ||
40 | 170 | 'cinder', | ||
41 | 171 | 'glance' | ||
42 | 172 | ] | ||
43 | 173 | |||
44 | 174 | if radosgw: | ||
45 | 175 | pools.extend([ | ||
46 | 176 | '.rgw.root', | ||
47 | 177 | '.rgw.control', | ||
48 | 178 | '.rgw', | ||
49 | 179 | '.rgw.gc', | ||
50 | 180 | '.users.uid' | ||
51 | 181 | ]) | ||
52 | 182 | |||
53 | 183 | return pools | ||
54 | 151 | 184 | ||
55 | === modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py' | |||
56 | --- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-06-19 16:29:27 +0000 | |||
57 | +++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:31:27 +0000 | |||
58 | @@ -14,16 +14,20 @@ | |||
59 | 14 | # You should have received a copy of the GNU Lesser General Public License | 14 | # You should have received a copy of the GNU Lesser General Public License |
60 | 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/>. |
61 | 16 | 16 | ||
62 | 17 | import amulet | ||
63 | 18 | import json | ||
64 | 17 | import logging | 19 | import logging |
65 | 18 | import os | 20 | import os |
66 | 19 | import six | 21 | import six |
67 | 20 | import time | 22 | import time |
68 | 21 | import urllib | 23 | import urllib |
69 | 22 | 24 | ||
70 | 25 | import cinderclient.v1.client as cinder_client | ||
71 | 23 | import glanceclient.v1.client as glance_client | 26 | import glanceclient.v1.client as glance_client |
72 | 24 | import heatclient.v1.client as heat_client | 27 | import heatclient.v1.client as heat_client |
73 | 25 | import keystoneclient.v2_0 as keystone_client | 28 | import keystoneclient.v2_0 as keystone_client |
74 | 26 | import novaclient.v1_1.client as nova_client | 29 | import novaclient.v1_1.client as nova_client |
75 | 30 | import swiftclient | ||
76 | 27 | 31 | ||
77 | 28 | from charmhelpers.contrib.amulet.utils import ( | 32 | from charmhelpers.contrib.amulet.utils import ( |
78 | 29 | AmuletUtils | 33 | AmuletUtils |
79 | @@ -171,6 +175,16 @@ | |||
80 | 171 | self.log.debug('Checking if tenant exists ({})...'.format(tenant)) | 175 | self.log.debug('Checking if tenant exists ({})...'.format(tenant)) |
81 | 172 | return tenant in [t.name for t in keystone.tenants.list()] | 176 | return tenant in [t.name for t in keystone.tenants.list()] |
82 | 173 | 177 | ||
83 | 178 | def authenticate_cinder_admin(self, keystone_sentry, username, | ||
84 | 179 | password, tenant): | ||
85 | 180 | """Authenticates admin user with cinder.""" | ||
86 | 181 | # NOTE(beisner): cinder python client doesn't accept tokens. | ||
87 | 182 | service_ip = \ | ||
88 | 183 | keystone_sentry.relation('shared-db', | ||
89 | 184 | 'mysql:shared-db')['private-address'] | ||
90 | 185 | ept = "http://{}:5000/v2.0".format(service_ip.strip().decode('utf-8')) | ||
91 | 186 | return cinder_client.Client(username, password, tenant, ept) | ||
92 | 187 | |||
93 | 174 | def authenticate_keystone_admin(self, keystone_sentry, user, password, | 188 | def authenticate_keystone_admin(self, keystone_sentry, user, password, |
94 | 175 | tenant): | 189 | tenant): |
95 | 176 | """Authenticates admin user with the keystone admin endpoint.""" | 190 | """Authenticates admin user with the keystone admin endpoint.""" |
96 | @@ -212,9 +226,29 @@ | |||
97 | 212 | return nova_client.Client(username=user, api_key=password, | 226 | return nova_client.Client(username=user, api_key=password, |
98 | 213 | project_id=tenant, auth_url=ep) | 227 | project_id=tenant, auth_url=ep) |
99 | 214 | 228 | ||
100 | 229 | def authenticate_swift_user(self, keystone, user, password, tenant): | ||
101 | 230 | """Authenticates a regular user with swift api.""" | ||
102 | 231 | self.log.debug('Authenticating swift user ({})...'.format(user)) | ||
103 | 232 | ep = keystone.service_catalog.url_for(service_type='identity', | ||
104 | 233 | endpoint_type='publicURL') | ||
105 | 234 | return swiftclient.Connection(authurl=ep, | ||
106 | 235 | user=user, | ||
107 | 236 | key=password, | ||
108 | 237 | tenant_name=tenant, | ||
109 | 238 | auth_version='2.0') | ||
110 | 239 | |||
111 | 215 | def create_cirros_image(self, glance, image_name): | 240 | def create_cirros_image(self, glance, image_name): |
114 | 216 | """Download the latest cirros image and upload it to glance.""" | 241 | """Download the latest cirros image and upload it to glance, |
115 | 217 | self.log.debug('Creating glance image ({})...'.format(image_name)) | 242 | validate and return a resource pointer. |
116 | 243 | |||
117 | 244 | :param glance: pointer to authenticated glance connection | ||
118 | 245 | :param image_name: display name for new image | ||
119 | 246 | :returns: glance image pointer | ||
120 | 247 | """ | ||
121 | 248 | self.log.debug('Creating glance cirros image ' | ||
122 | 249 | '({})...'.format(image_name)) | ||
123 | 250 | |||
124 | 251 | # Download cirros image | ||
125 | 218 | http_proxy = os.getenv('AMULET_HTTP_PROXY') | 252 | http_proxy = os.getenv('AMULET_HTTP_PROXY') |
126 | 219 | self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy)) | 253 | self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy)) |
127 | 220 | if http_proxy: | 254 | if http_proxy: |
128 | @@ -223,33 +257,51 @@ | |||
129 | 223 | else: | 257 | else: |
130 | 224 | opener = urllib.FancyURLopener() | 258 | opener = urllib.FancyURLopener() |
131 | 225 | 259 | ||
133 | 226 | f = opener.open("http://download.cirros-cloud.net/version/released") | 260 | f = opener.open('http://download.cirros-cloud.net/version/released') |
134 | 227 | version = f.read().strip() | 261 | version = f.read().strip() |
136 | 228 | cirros_img = "cirros-{}-x86_64-disk.img".format(version) | 262 | cirros_img = 'cirros-{}-x86_64-disk.img'.format(version) |
137 | 229 | local_path = os.path.join('tests', cirros_img) | 263 | local_path = os.path.join('tests', cirros_img) |
138 | 230 | 264 | ||
139 | 231 | if not os.path.exists(local_path): | 265 | if not os.path.exists(local_path): |
141 | 232 | cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net", | 266 | cirros_url = 'http://{}/{}/{}'.format('download.cirros-cloud.net', |
142 | 233 | version, cirros_img) | 267 | version, cirros_img) |
143 | 234 | opener.retrieve(cirros_url, local_path) | 268 | opener.retrieve(cirros_url, local_path) |
144 | 235 | f.close() | 269 | f.close() |
145 | 236 | 270 | ||
146 | 271 | # Create glance image | ||
147 | 237 | with open(local_path) as f: | 272 | with open(local_path) as f: |
148 | 238 | image = glance.images.create(name=image_name, is_public=True, | 273 | image = glance.images.create(name=image_name, is_public=True, |
149 | 239 | disk_format='qcow2', | 274 | disk_format='qcow2', |
150 | 240 | container_format='bare', data=f) | 275 | container_format='bare', data=f) |
163 | 241 | count = 1 | 276 | |
164 | 242 | status = image.status | 277 | # Wait for image to reach active status |
165 | 243 | while status != 'active' and count < 10: | 278 | img_id = image.id |
166 | 244 | time.sleep(3) | 279 | ret = self.resource_reaches_status(glance.images, img_id, |
167 | 245 | image = glance.images.get(image.id) | 280 | expected_stat='active', |
168 | 246 | status = image.status | 281 | msg='Image status wait') |
169 | 247 | self.log.debug('image status: {}'.format(status)) | 282 | if not ret: |
170 | 248 | count += 1 | 283 | msg = 'Glance image failed to reach expected state.' |
171 | 249 | 284 | amulet.raise_status(amulet.FAIL, msg=msg) | |
172 | 250 | if status != 'active': | 285 | |
173 | 251 | self.log.error('image creation timed out') | 286 | # Re-validate new image |
174 | 252 | return None | 287 | self.log.debug('Validating image attributes...') |
175 | 288 | val_img_name = glance.images.get(img_id).name | ||
176 | 289 | val_img_stat = glance.images.get(img_id).status | ||
177 | 290 | val_img_pub = glance.images.get(img_id).is_public | ||
178 | 291 | val_img_cfmt = glance.images.get(img_id).container_format | ||
179 | 292 | val_img_dfmt = glance.images.get(img_id).disk_format | ||
180 | 293 | msg_attr = ('Image attributes - name:{} public:{} id:{} stat:{} ' | ||
181 | 294 | 'container fmt:{} disk fmt:{}'.format( | ||
182 | 295 | val_img_name, val_img_pub, img_id, | ||
183 | 296 | val_img_stat, val_img_cfmt, val_img_dfmt)) | ||
184 | 297 | |||
185 | 298 | if val_img_name == image_name and val_img_stat == 'active' \ | ||
186 | 299 | and val_img_pub is True and val_img_cfmt == 'bare' \ | ||
187 | 300 | and val_img_dfmt == 'qcow2': | ||
188 | 301 | self.log.debug(msg_attr) | ||
189 | 302 | else: | ||
190 | 303 | msg = ('Volume validation failed, {}'.format(msg_attr)) | ||
191 | 304 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
192 | 253 | 305 | ||
193 | 254 | return image | 306 | return image |
194 | 255 | 307 | ||
195 | @@ -260,22 +312,7 @@ | |||
196 | 260 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | 312 | self.log.warn('/!\\ DEPRECATION WARNING: use ' |
197 | 261 | 'delete_resource instead of delete_image.') | 313 | 'delete_resource instead of delete_image.') |
198 | 262 | self.log.debug('Deleting glance image ({})...'.format(image)) | 314 | self.log.debug('Deleting glance image ({})...'.format(image)) |
215 | 263 | num_before = len(list(glance.images.list())) | 315 | return self.delete_resource(glance.images, image, msg='glance image') |
200 | 264 | glance.images.delete(image) | ||
201 | 265 | |||
202 | 266 | count = 1 | ||
203 | 267 | num_after = len(list(glance.images.list())) | ||
204 | 268 | while num_after != (num_before - 1) and count < 10: | ||
205 | 269 | time.sleep(3) | ||
206 | 270 | num_after = len(list(glance.images.list())) | ||
207 | 271 | self.log.debug('number of images: {}'.format(num_after)) | ||
208 | 272 | count += 1 | ||
209 | 273 | |||
210 | 274 | if num_after != (num_before - 1): | ||
211 | 275 | self.log.error('image deletion timed out') | ||
212 | 276 | return False | ||
213 | 277 | |||
214 | 278 | return True | ||
216 | 279 | 316 | ||
217 | 280 | def create_instance(self, nova, image_name, instance_name, flavor): | 317 | def create_instance(self, nova, image_name, instance_name, flavor): |
218 | 281 | """Create the specified instance.""" | 318 | """Create the specified instance.""" |
219 | @@ -308,22 +345,8 @@ | |||
220 | 308 | self.log.warn('/!\\ DEPRECATION WARNING: use ' | 345 | self.log.warn('/!\\ DEPRECATION WARNING: use ' |
221 | 309 | 'delete_resource instead of delete_instance.') | 346 | 'delete_resource instead of delete_instance.') |
222 | 310 | self.log.debug('Deleting instance ({})...'.format(instance)) | 347 | self.log.debug('Deleting instance ({})...'.format(instance)) |
239 | 311 | num_before = len(list(nova.servers.list())) | 348 | return self.delete_resource(nova.servers, instance, |
240 | 312 | nova.servers.delete(instance) | 349 | msg='nova instance') |
225 | 313 | |||
226 | 314 | count = 1 | ||
227 | 315 | num_after = len(list(nova.servers.list())) | ||
228 | 316 | while num_after != (num_before - 1) and count < 10: | ||
229 | 317 | time.sleep(3) | ||
230 | 318 | num_after = len(list(nova.servers.list())) | ||
231 | 319 | self.log.debug('number of instances: {}'.format(num_after)) | ||
232 | 320 | count += 1 | ||
233 | 321 | |||
234 | 322 | if num_after != (num_before - 1): | ||
235 | 323 | self.log.error('instance deletion timed out') | ||
236 | 324 | return False | ||
237 | 325 | |||
238 | 326 | return True | ||
241 | 327 | 350 | ||
242 | 328 | def create_or_get_keypair(self, nova, keypair_name="testkey"): | 351 | def create_or_get_keypair(self, nova, keypair_name="testkey"): |
243 | 329 | """Create a new keypair, or return pointer if it already exists.""" | 352 | """Create a new keypair, or return pointer if it already exists.""" |
244 | @@ -339,6 +362,88 @@ | |||
245 | 339 | _keypair = nova.keypairs.create(name=keypair_name) | 362 | _keypair = nova.keypairs.create(name=keypair_name) |
246 | 340 | return _keypair | 363 | return _keypair |
247 | 341 | 364 | ||
248 | 365 | def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1, | ||
249 | 366 | img_id=None, src_vol_id=None, snap_id=None): | ||
250 | 367 | """Create cinder volume, optionally from a glance image, OR | ||
251 | 368 | optionally as a clone of an existing volume, OR optionally | ||
252 | 369 | from a snapshot. Wait for the new volume status to reach | ||
253 | 370 | the expected status, validate and return a resource pointer. | ||
254 | 371 | |||
255 | 372 | :param vol_name: cinder volume display name | ||
256 | 373 | :param vol_size: size in gigabytes | ||
257 | 374 | :param img_id: optional glance image id | ||
258 | 375 | :param src_vol_id: optional source volume id to clone | ||
259 | 376 | :param snap_id: optional snapshot id to use | ||
260 | 377 | :returns: cinder volume pointer | ||
261 | 378 | """ | ||
262 | 379 | # Handle parameter input and avoid impossible combinations | ||
263 | 380 | if img_id and not src_vol_id and not snap_id: | ||
264 | 381 | # Create volume from image | ||
265 | 382 | self.log.debug('Creating cinder volume from glance image...') | ||
266 | 383 | bootable = 'true' | ||
267 | 384 | elif src_vol_id and not img_id and not snap_id: | ||
268 | 385 | # Clone an existing volume | ||
269 | 386 | self.log.debug('Cloning cinder volume...') | ||
270 | 387 | bootable = cinder.volumes.get(src_vol_id).bootable | ||
271 | 388 | elif snap_id and not src_vol_id and not img_id: | ||
272 | 389 | # Create volume from snapshot | ||
273 | 390 | self.log.debug('Creating cinder volume from snapshot...') | ||
274 | 391 | snap = cinder.volume_snapshots.find(id=snap_id) | ||
275 | 392 | vol_size = snap.size | ||
276 | 393 | snap_vol_id = cinder.volume_snapshots.get(snap_id).volume_id | ||
277 | 394 | bootable = cinder.volumes.get(snap_vol_id).bootable | ||
278 | 395 | elif not img_id and not src_vol_id and not snap_id: | ||
279 | 396 | # Create volume | ||
280 | 397 | self.log.debug('Creating cinder volume...') | ||
281 | 398 | bootable = 'false' | ||
282 | 399 | else: | ||
283 | 400 | # Impossible combination of parameters | ||
284 | 401 | msg = ('Invalid method use - name:{} size:{} img_id:{} ' | ||
285 | 402 | 'src_vol_id:{} snap_id:{}'.format(vol_name, vol_size, | ||
286 | 403 | img_id, src_vol_id, | ||
287 | 404 | snap_id)) | ||
288 | 405 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
289 | 406 | |||
290 | 407 | # Create new volume | ||
291 | 408 | try: | ||
292 | 409 | vol_new = cinder.volumes.create(display_name=vol_name, | ||
293 | 410 | imageRef=img_id, | ||
294 | 411 | size=vol_size, | ||
295 | 412 | source_volid=src_vol_id, | ||
296 | 413 | snapshot_id=snap_id) | ||
297 | 414 | vol_id = vol_new.id | ||
298 | 415 | except Exception as e: | ||
299 | 416 | msg = 'Failed to create volume: {}'.format(e) | ||
300 | 417 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
301 | 418 | |||
302 | 419 | # Wait for volume to reach available status | ||
303 | 420 | ret = self.resource_reaches_status(cinder.volumes, vol_id, | ||
304 | 421 | expected_stat="available", | ||
305 | 422 | msg="Volume status wait") | ||
306 | 423 | if not ret: | ||
307 | 424 | msg = 'Cinder volume failed to reach expected state.' | ||
308 | 425 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
309 | 426 | |||
310 | 427 | # Re-validate new volume | ||
311 | 428 | self.log.debug('Validating volume attributes...') | ||
312 | 429 | val_vol_name = cinder.volumes.get(vol_id).display_name | ||
313 | 430 | val_vol_boot = cinder.volumes.get(vol_id).bootable | ||
314 | 431 | val_vol_stat = cinder.volumes.get(vol_id).status | ||
315 | 432 | val_vol_size = cinder.volumes.get(vol_id).size | ||
316 | 433 | msg_attr = ('Volume attributes - name:{} id:{} stat:{} boot:' | ||
317 | 434 | '{} size:{}'.format(val_vol_name, vol_id, | ||
318 | 435 | val_vol_stat, val_vol_boot, | ||
319 | 436 | val_vol_size)) | ||
320 | 437 | |||
321 | 438 | if val_vol_boot == bootable and val_vol_stat == 'available' \ | ||
322 | 439 | and val_vol_name == vol_name and val_vol_size == vol_size: | ||
323 | 440 | self.log.debug(msg_attr) | ||
324 | 441 | else: | ||
325 | 442 | msg = ('Volume validation failed, {}'.format(msg_attr)) | ||
326 | 443 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
327 | 444 | |||
328 | 445 | return vol_new | ||
329 | 446 | |||
330 | 342 | def delete_resource(self, resource, resource_id, | 447 | def delete_resource(self, resource, resource_id, |
331 | 343 | msg="resource", max_wait=120): | 448 | msg="resource", max_wait=120): |
332 | 344 | """Delete one openstack resource, such as one instance, keypair, | 449 | """Delete one openstack resource, such as one instance, keypair, |
333 | @@ -350,6 +455,8 @@ | |||
334 | 350 | :param max_wait: maximum wait time in seconds | 455 | :param max_wait: maximum wait time in seconds |
335 | 351 | :returns: True if successful, otherwise False | 456 | :returns: True if successful, otherwise False |
336 | 352 | """ | 457 | """ |
337 | 458 | self.log.debug('Deleting OpenStack resource ' | ||
338 | 459 | '{} ({})'.format(resource_id, msg)) | ||
339 | 353 | num_before = len(list(resource.list())) | 460 | num_before = len(list(resource.list())) |
340 | 354 | resource.delete(resource_id) | 461 | resource.delete(resource_id) |
341 | 355 | 462 | ||
342 | @@ -411,3 +518,87 @@ | |||
343 | 411 | self.log.debug('{} never reached expected status: ' | 518 | self.log.debug('{} never reached expected status: ' |
344 | 412 | '{}'.format(resource_id, expected_stat)) | 519 | '{}'.format(resource_id, expected_stat)) |
345 | 413 | return False | 520 | return False |
346 | 521 | |||
347 | 522 | def get_ceph_osd_id_cmd(self, index): | ||
348 | 523 | """Produce a shell command that will return a ceph-osd id.""" | ||
349 | 524 | return ("`initctl list | grep 'ceph-osd ' | " | ||
350 | 525 | "awk 'NR=={} {{ print $2 }}' | " | ||
351 | 526 | "grep -o '[0-9]*'`".format(index + 1)) | ||
352 | 527 | |||
353 | 528 | def get_ceph_pools(self, sentry_unit): | ||
354 | 529 | """Return a dict of ceph pools from a single ceph unit, with | ||
355 | 530 | pool name as keys, pool id as vals.""" | ||
356 | 531 | pools = {} | ||
357 | 532 | cmd = 'sudo ceph osd lspools' | ||
358 | 533 | output, code = sentry_unit.run(cmd) | ||
359 | 534 | if code != 0: | ||
360 | 535 | msg = ('{} `{}` returned {} ' | ||
361 | 536 | '{}'.format(sentry_unit.info['unit_name'], | ||
362 | 537 | cmd, code, output)) | ||
363 | 538 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
364 | 539 | |||
365 | 540 | # Example output: 0 data,1 metadata,2 rbd,3 cinder,4 glance, | ||
366 | 541 | for pool in str(output).split(','): | ||
367 | 542 | pool_id_name = pool.split(' ') | ||
368 | 543 | if len(pool_id_name) == 2: | ||
369 | 544 | pool_id = pool_id_name[0] | ||
370 | 545 | pool_name = pool_id_name[1] | ||
371 | 546 | pools[pool_name] = int(pool_id) | ||
372 | 547 | |||
373 | 548 | self.log.debug('Pools on {}: {}'.format(sentry_unit.info['unit_name'], | ||
374 | 549 | pools)) | ||
375 | 550 | return pools | ||
376 | 551 | |||
377 | 552 | def get_ceph_df(self, sentry_unit): | ||
378 | 553 | """Return dict of ceph df json output, including ceph pool state. | ||
379 | 554 | |||
380 | 555 | :param sentry_unit: Pointer to amulet sentry instance (juju unit) | ||
381 | 556 | :returns: Dict of ceph df output | ||
382 | 557 | """ | ||
383 | 558 | cmd = 'sudo ceph df --format=json' | ||
384 | 559 | output, code = sentry_unit.run(cmd) | ||
385 | 560 | if code != 0: | ||
386 | 561 | msg = ('{} `{}` returned {} ' | ||
387 | 562 | '{}'.format(sentry_unit.info['unit_name'], | ||
388 | 563 | cmd, code, output)) | ||
389 | 564 | amulet.raise_status(amulet.FAIL, msg=msg) | ||
390 | 565 | return json.loads(output) | ||
391 | 566 | |||
392 | 567 | def get_ceph_pool_sample(self, sentry_unit, pool_id=0): | ||
393 | 568 | """Take a sample of attributes of a ceph pool, returning ceph | ||
394 | 569 | pool name, object count and disk space used for the specified | ||
395 | 570 | pool ID number. | ||
396 | 571 | |||
397 | 572 | :param sentry_unit: Pointer to amulet sentry instance (juju unit) | ||
398 | 573 | :param pool_id: Ceph pool ID | ||
399 | 574 | :returns: List of pool name, object count, kb disk space used | ||
400 | 575 | """ | ||
401 | 576 | df = self.get_ceph_df(sentry_unit) | ||
402 | 577 | pool_name = df['pools'][pool_id]['name'] | ||
403 | 578 | obj_count = df['pools'][pool_id]['stats']['objects'] | ||
404 | 579 | kb_used = df['pools'][pool_id]['stats']['kb_used'] | ||
405 | 580 | self.log.debug('Ceph {} pool (ID {}): {} objects, ' | ||
406 | 581 | '{} kb used'.format(pool_name, pool_id, | ||
407 | 582 | obj_count, kb_used)) | ||
408 | 583 | return pool_name, obj_count, kb_used | ||
409 | 584 | |||
410 | 585 | def validate_ceph_pool_samples(self, samples, sample_type="resource pool"): | ||
411 | 586 | """Validate ceph pool samples taken over time, such as pool | ||
412 | 587 | object counts or pool kb used, before adding, after adding, and | ||
413 | 588 | after deleting items which affect those pool attributes. The | ||
414 | 589 | 2nd element is expected to be greater than the 1st; 3rd is expected | ||
415 | 590 | to be less than the 2nd. | ||
416 | 591 | |||
417 | 592 | :param samples: List containing 3 data samples | ||
418 | 593 | :param sample_type: String for logging and usage context | ||
419 | 594 | :returns: None if successful, Failure message otherwise | ||
420 | 595 | """ | ||
421 | 596 | original, created, deleted = range(3) | ||
422 | 597 | if samples[created] <= samples[original] or \ | ||
423 | 598 | samples[deleted] >= samples[created]: | ||
424 | 599 | return ('Ceph {} samples ({}) ' | ||
425 | 600 | 'unexpected.'.format(sample_type, samples)) | ||
426 | 601 | else: | ||
427 | 602 | self.log.debug('Ceph {} samples (OK): ' | ||
428 | 603 | '{}'.format(sample_type, samples)) | ||
429 | 604 | return None | ||
430 | 414 | 605 | ||
431 | === modified file 'hooks/charmhelpers/contrib/openstack/context.py' | |||
432 | --- hooks/charmhelpers/contrib/openstack/context.py 2015-06-19 16:29:27 +0000 | |||
433 | +++ hooks/charmhelpers/contrib/openstack/context.py 2015-07-16 20:31:27 +0000 | |||
434 | @@ -122,21 +122,24 @@ | |||
435 | 122 | of specifying multiple key value pairs within the same string. For | 122 | of specifying multiple key value pairs within the same string. For |
436 | 123 | example, a string in the format of 'key1=value1, key2=value2' will | 123 | example, a string in the format of 'key1=value1, key2=value2' will |
437 | 124 | return a dict of: | 124 | return a dict of: |
440 | 125 | {'key1': 'value1', | 125 | |
441 | 126 | 'key2': 'value2'}. | 126 | {'key1': 'value1', |
442 | 127 | 'key2': 'value2'}. | ||
443 | 127 | 128 | ||
444 | 128 | 2. A string in the above format, but supporting a comma-delimited list | 129 | 2. A string in the above format, but supporting a comma-delimited list |
445 | 129 | of values for the same key. For example, a string in the format of | 130 | of values for the same key. For example, a string in the format of |
446 | 130 | 'key1=value1, key2=value3,value4,value5' will return a dict of: | 131 | 'key1=value1, key2=value3,value4,value5' will return a dict of: |
449 | 131 | {'key1', 'value1', | 132 | |
450 | 132 | 'key2', 'value2,value3,value4'} | 133 | {'key1', 'value1', |
451 | 134 | 'key2', 'value2,value3,value4'} | ||
452 | 133 | 135 | ||
453 | 134 | 3. A string containing a colon character (:) prior to an equal | 136 | 3. A string containing a colon character (:) prior to an equal |
454 | 135 | character (=) will be treated as yaml and parsed as such. This can be | 137 | character (=) will be treated as yaml and parsed as such. This can be |
455 | 136 | used to specify more complex key value pairs. For example, | 138 | used to specify more complex key value pairs. For example, |
456 | 137 | a string in the format of 'key1: subkey1=value1, subkey2=value2' will | 139 | a string in the format of 'key1: subkey1=value1, subkey2=value2' will |
457 | 138 | return a dict of: | 140 | return a dict of: |
459 | 139 | {'key1', 'subkey1=value1, subkey2=value2'} | 141 | |
460 | 142 | {'key1', 'subkey1=value1, subkey2=value2'} | ||
461 | 140 | 143 | ||
462 | 141 | The provided config_flags string may be a list of comma-separated values | 144 | The provided config_flags string may be a list of comma-separated values |
463 | 142 | which themselves may be comma-separated list of values. | 145 | which themselves may be comma-separated list of values. |
464 | @@ -891,8 +894,6 @@ | |||
465 | 891 | return ctxt | 894 | return ctxt |
466 | 892 | 895 | ||
467 | 893 | def __call__(self): | 896 | def __call__(self): |
468 | 894 | self._ensure_packages() | ||
469 | 895 | |||
470 | 896 | if self.network_manager not in ['quantum', 'neutron']: | 897 | if self.network_manager not in ['quantum', 'neutron']: |
471 | 897 | return {} | 898 | return {} |
472 | 898 | 899 | ||
473 | 899 | 900 | ||
474 | === modified file 'hooks/charmhelpers/contrib/openstack/templates/ceph.conf' | |||
475 | --- hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2014-04-10 15:46:24 +0000 | |||
476 | +++ hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2015-07-16 20:31:27 +0000 | |||
477 | @@ -5,11 +5,11 @@ | |||
478 | 5 | ############################################################################### | 5 | ############################################################################### |
479 | 6 | [global] | 6 | [global] |
480 | 7 | {% if auth -%} | 7 | {% if auth -%} |
484 | 8 | auth_supported = {{ auth }} | 8 | auth_supported = {{ auth }} |
485 | 9 | keyring = /etc/ceph/$cluster.$name.keyring | 9 | keyring = /etc/ceph/$cluster.$name.keyring |
486 | 10 | mon host = {{ mon_hosts }} | 10 | mon host = {{ mon_hosts }} |
487 | 11 | {% endif -%} | 11 | {% endif -%} |
491 | 12 | log to syslog = {{ use_syslog }} | 12 | log to syslog = {{ use_syslog }} |
492 | 13 | err to syslog = {{ use_syslog }} | 13 | err to syslog = {{ use_syslog }} |
493 | 14 | clog to syslog = {{ use_syslog }} | 14 | clog to syslog = {{ use_syslog }} |
494 | 15 | 15 | ||
495 | 16 | 16 | ||
496 | === modified file 'hooks/charmhelpers/contrib/openstack/utils.py' | |||
497 | --- hooks/charmhelpers/contrib/openstack/utils.py 2015-06-23 23:53:39 +0000 | |||
498 | +++ hooks/charmhelpers/contrib/openstack/utils.py 2015-07-16 20:31:27 +0000 | |||
499 | @@ -522,6 +522,7 @@ | |||
500 | 522 | Clone/install all specified OpenStack repositories. | 522 | Clone/install all specified OpenStack repositories. |
501 | 523 | 523 | ||
502 | 524 | The expected format of projects_yaml is: | 524 | The expected format of projects_yaml is: |
503 | 525 | |||
504 | 525 | repositories: | 526 | repositories: |
505 | 526 | - {name: keystone, | 527 | - {name: keystone, |
506 | 527 | repository: 'git://git.openstack.org/openstack/keystone.git', | 528 | repository: 'git://git.openstack.org/openstack/keystone.git', |
507 | @@ -529,11 +530,13 @@ | |||
508 | 529 | - {name: requirements, | 530 | - {name: requirements, |
509 | 530 | repository: 'git://git.openstack.org/openstack/requirements.git', | 531 | repository: 'git://git.openstack.org/openstack/requirements.git', |
510 | 531 | branch: 'stable/icehouse'} | 532 | branch: 'stable/icehouse'} |
511 | 533 | |||
512 | 532 | directory: /mnt/openstack-git | 534 | directory: /mnt/openstack-git |
513 | 533 | http_proxy: squid-proxy-url | 535 | http_proxy: squid-proxy-url |
514 | 534 | https_proxy: squid-proxy-url | 536 | https_proxy: squid-proxy-url |
515 | 535 | 537 | ||
517 | 536 | The directory, http_proxy, and https_proxy keys are optional. | 538 | The directory, http_proxy, and https_proxy keys are optional. |
518 | 539 | |||
519 | 537 | """ | 540 | """ |
520 | 538 | global requirements_dir | 541 | global requirements_dir |
521 | 539 | parent_dir = '/mnt/openstack-git' | 542 | parent_dir = '/mnt/openstack-git' |
522 | @@ -555,10 +558,11 @@ | |||
523 | 555 | 558 | ||
524 | 556 | pip_create_virtualenv(os.path.join(parent_dir, 'venv')) | 559 | pip_create_virtualenv(os.path.join(parent_dir, 'venv')) |
525 | 557 | 560 | ||
530 | 558 | # Upgrade setuptools from default virtualenv version. The default version | 561 | # Upgrade setuptools and pip from default virtualenv versions. The default |
531 | 559 | # in trusty breaks update.py in global requirements master branch. | 562 | # versions in trusty break master OpenStack branch deployments. |
532 | 560 | pip_install('setuptools', upgrade=True, proxy=http_proxy, | 563 | for p in ['pip', 'setuptools']: |
533 | 561 | venv=os.path.join(parent_dir, 'venv')) | 564 | pip_install(p, upgrade=True, proxy=http_proxy, |
534 | 565 | venv=os.path.join(parent_dir, 'venv')) | ||
535 | 562 | 566 | ||
536 | 563 | for p in projects['repositories']: | 567 | for p in projects['repositories']: |
537 | 564 | repo = p['repository'] | 568 | repo = p['repository'] |
538 | 565 | 569 | ||
539 | === modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py' | |||
540 | --- hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-01-26 09:47:37 +0000 | |||
541 | +++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-07-16 20:31:27 +0000 | |||
542 | @@ -60,12 +60,12 @@ | |||
543 | 60 | KEYFILE = '/etc/ceph/ceph.client.{}.key' | 60 | KEYFILE = '/etc/ceph/ceph.client.{}.key' |
544 | 61 | 61 | ||
545 | 62 | CEPH_CONF = """[global] | 62 | CEPH_CONF = """[global] |
552 | 63 | auth supported = {auth} | 63 | auth supported = {auth} |
553 | 64 | keyring = {keyring} | 64 | keyring = {keyring} |
554 | 65 | mon host = {mon_hosts} | 65 | mon host = {mon_hosts} |
555 | 66 | log to syslog = {use_syslog} | 66 | log to syslog = {use_syslog} |
556 | 67 | err to syslog = {use_syslog} | 67 | err to syslog = {use_syslog} |
557 | 68 | clog to syslog = {use_syslog} | 68 | clog to syslog = {use_syslog} |
558 | 69 | """ | 69 | """ |
559 | 70 | 70 | ||
560 | 71 | 71 | ||
561 | 72 | 72 | ||
562 | === modified file 'hooks/charmhelpers/core/hookenv.py' | |||
563 | --- hooks/charmhelpers/core/hookenv.py 2015-06-29 13:24:23 +0000 | |||
564 | +++ hooks/charmhelpers/core/hookenv.py 2015-07-16 20:31:27 +0000 | |||
565 | @@ -761,6 +761,7 @@ | |||
566 | 761 | 761 | ||
567 | 762 | This is useful for modules and classes to perform initialization | 762 | This is useful for modules and classes to perform initialization |
568 | 763 | and inject behavior. In particular: | 763 | and inject behavior. In particular: |
569 | 764 | |||
570 | 764 | - Run common code before all of your hooks, such as logging | 765 | - Run common code before all of your hooks, such as logging |
571 | 765 | the hook name or interesting relation data. | 766 | the hook name or interesting relation data. |
572 | 766 | - Defer object or module initialization that requires a hook | 767 | - Defer object or module initialization that requires a hook |
573 | 767 | 768 | ||
574 | === modified file 'hooks/charmhelpers/core/host.py' | |||
575 | --- hooks/charmhelpers/core/host.py 2015-06-19 16:29:27 +0000 | |||
576 | +++ hooks/charmhelpers/core/host.py 2015-07-16 20:31:27 +0000 | |||
577 | @@ -63,6 +63,36 @@ | |||
578 | 63 | return service_result | 63 | return service_result |
579 | 64 | 64 | ||
580 | 65 | 65 | ||
581 | 66 | def service_pause(service_name, init_dir=None): | ||
582 | 67 | """Pause a system service. | ||
583 | 68 | |||
584 | 69 | Stop it, and prevent it from starting again at boot.""" | ||
585 | 70 | if init_dir is None: | ||
586 | 71 | init_dir = "/etc/init" | ||
587 | 72 | stopped = service_stop(service_name) | ||
588 | 73 | # XXX: Support systemd too | ||
589 | 74 | override_path = os.path.join( | ||
590 | 75 | init_dir, '{}.conf.override'.format(service_name)) | ||
591 | 76 | with open(override_path, 'w') as fh: | ||
592 | 77 | fh.write("manual\n") | ||
593 | 78 | return stopped | ||
594 | 79 | |||
595 | 80 | |||
596 | 81 | def service_resume(service_name, init_dir=None): | ||
597 | 82 | """Resume a system service. | ||
598 | 83 | |||
599 | 84 | Reenable starting again at boot. Start the service""" | ||
600 | 85 | # XXX: Support systemd too | ||
601 | 86 | if init_dir is None: | ||
602 | 87 | init_dir = "/etc/init" | ||
603 | 88 | override_path = os.path.join( | ||
604 | 89 | init_dir, '{}.conf.override'.format(service_name)) | ||
605 | 90 | if os.path.exists(override_path): | ||
606 | 91 | os.unlink(override_path) | ||
607 | 92 | started = service_start(service_name) | ||
608 | 93 | return started | ||
609 | 94 | |||
610 | 95 | |||
611 | 66 | def service(action, service_name): | 96 | def service(action, service_name): |
612 | 67 | """Control a system service""" | 97 | """Control a system service""" |
613 | 68 | cmd = ['service', service_name, action] | 98 | cmd = ['service', service_name, action] |
614 | @@ -140,11 +170,7 @@ | |||
615 | 140 | 170 | ||
616 | 141 | def add_user_to_group(username, group): | 171 | def add_user_to_group(username, group): |
617 | 142 | """Add a user to a group""" | 172 | """Add a user to a group""" |
623 | 143 | cmd = [ | 173 | cmd = ['gpasswd', '-a', username, group] |
619 | 144 | 'gpasswd', '-a', | ||
620 | 145 | username, | ||
621 | 146 | group | ||
622 | 147 | ] | ||
624 | 148 | log("Adding user {} to group {}".format(username, group)) | 174 | log("Adding user {} to group {}".format(username, group)) |
625 | 149 | subprocess.check_call(cmd) | 175 | subprocess.check_call(cmd) |
626 | 150 | 176 | ||
627 | 151 | 177 | ||
628 | === modified file 'hooks/charmhelpers/core/services/helpers.py' | |||
629 | --- hooks/charmhelpers/core/services/helpers.py 2015-05-11 07:27:02 +0000 | |||
630 | +++ hooks/charmhelpers/core/services/helpers.py 2015-07-16 20:31:27 +0000 | |||
631 | @@ -239,12 +239,12 @@ | |||
632 | 239 | action. | 239 | action. |
633 | 240 | 240 | ||
634 | 241 | :param str source: The template source file, relative to | 241 | :param str source: The template source file, relative to |
637 | 242 | `$CHARM_DIR/templates` | 242 | `$CHARM_DIR/templates` |
636 | 243 | |||
638 | 244 | :param str target: The target to write the rendered template to | 243 | :param str target: The target to write the rendered template to |
639 | 245 | :param str owner: The owner of the rendered file | 244 | :param str owner: The owner of the rendered file |
640 | 246 | :param str group: The group of the rendered file | 245 | :param str group: The group of the rendered file |
641 | 247 | :param int perms: The permissions of the rendered file | 246 | :param int perms: The permissions of the rendered file |
642 | 247 | |||
643 | 248 | """ | 248 | """ |
644 | 249 | def __init__(self, source, target, | 249 | def __init__(self, source, target, |
645 | 250 | owner='root', group='root', perms=0o444): | 250 | owner='root', group='root', perms=0o444): |
646 | 251 | 251 | ||
647 | === modified file 'hooks/charmhelpers/fetch/__init__.py' | |||
648 | --- hooks/charmhelpers/fetch/__init__.py 2015-06-10 21:37:05 +0000 | |||
649 | +++ hooks/charmhelpers/fetch/__init__.py 2015-07-16 20:31:27 +0000 | |||
650 | @@ -215,19 +215,27 @@ | |||
651 | 215 | _run_apt_command(cmd, fatal) | 215 | _run_apt_command(cmd, fatal) |
652 | 216 | 216 | ||
653 | 217 | 217 | ||
654 | 218 | def apt_mark(packages, mark, fatal=False): | ||
655 | 219 | """Flag one or more packages using apt-mark""" | ||
656 | 220 | cmd = ['apt-mark', mark] | ||
657 | 221 | if isinstance(packages, six.string_types): | ||
658 | 222 | cmd.append(packages) | ||
659 | 223 | else: | ||
660 | 224 | cmd.extend(packages) | ||
661 | 225 | log("Holding {}".format(packages)) | ||
662 | 226 | |||
663 | 227 | if fatal: | ||
664 | 228 | subprocess.check_call(cmd, universal_newlines=True) | ||
665 | 229 | else: | ||
666 | 230 | subprocess.call(cmd, universal_newlines=True) | ||
667 | 231 | |||
668 | 232 | |||
669 | 218 | def apt_hold(packages, fatal=False): | 233 | def apt_hold(packages, fatal=False): |
682 | 219 | """Hold one or more packages""" | 234 | return apt_mark(packages, 'hold', fatal=fatal) |
683 | 220 | cmd = ['apt-mark', 'hold'] | 235 | |
684 | 221 | if isinstance(packages, six.string_types): | 236 | |
685 | 222 | cmd.append(packages) | 237 | def apt_unhold(packages, fatal=False): |
686 | 223 | else: | 238 | return apt_mark(packages, 'unhold', fatal=fatal) |
675 | 224 | cmd.extend(packages) | ||
676 | 225 | log("Holding {}".format(packages)) | ||
677 | 226 | |||
678 | 227 | if fatal: | ||
679 | 228 | subprocess.check_call(cmd) | ||
680 | 229 | else: | ||
681 | 230 | subprocess.call(cmd) | ||
687 | 231 | 239 | ||
688 | 232 | 240 | ||
689 | 233 | def add_source(source, key=None): | 241 | def add_source(source, key=None): |
690 | @@ -370,8 +378,9 @@ | |||
691 | 370 | for handler in handlers: | 378 | for handler in handlers: |
692 | 371 | try: | 379 | try: |
693 | 372 | installed_to = handler.install(source, *args, **kwargs) | 380 | installed_to = handler.install(source, *args, **kwargs) |
696 | 373 | except UnhandledSource: | 381 | except UnhandledSource as e: |
697 | 374 | pass | 382 | log('Install source attempt unsuccessful: {}'.format(e), |
698 | 383 | level='WARNING') | ||
699 | 375 | if not installed_to: | 384 | if not installed_to: |
700 | 376 | raise UnhandledSource("No handler found for source {}".format(source)) | 385 | raise UnhandledSource("No handler found for source {}".format(source)) |
701 | 377 | return installed_to | 386 | return installed_to |
702 | 378 | 387 | ||
703 | === modified file 'hooks/charmhelpers/fetch/archiveurl.py' | |||
704 | --- hooks/charmhelpers/fetch/archiveurl.py 2015-03-13 13:00:03 +0000 | |||
705 | +++ hooks/charmhelpers/fetch/archiveurl.py 2015-07-16 20:31:27 +0000 | |||
706 | @@ -77,6 +77,8 @@ | |||
707 | 77 | def can_handle(self, source): | 77 | def can_handle(self, source): |
708 | 78 | url_parts = self.parse_url(source) | 78 | url_parts = self.parse_url(source) |
709 | 79 | if url_parts.scheme not in ('http', 'https', 'ftp', 'file'): | 79 | if url_parts.scheme not in ('http', 'https', 'ftp', 'file'): |
710 | 80 | # XXX: Why is this returning a boolean and a string? It's | ||
711 | 81 | # doomed to fail since "bool(can_handle('foo://'))" will be True. | ||
712 | 80 | return "Wrong source type" | 82 | return "Wrong source type" |
713 | 81 | if get_archive_handler(self.base_url(source)): | 83 | if get_archive_handler(self.base_url(source)): |
714 | 82 | return True | 84 | return True |
715 | @@ -155,7 +157,11 @@ | |||
716 | 155 | else: | 157 | else: |
717 | 156 | algorithms = hashlib.algorithms_available | 158 | algorithms = hashlib.algorithms_available |
718 | 157 | if key in algorithms: | 159 | if key in algorithms: |
720 | 158 | check_hash(dld_file, value, key) | 160 | if len(value) != 1: |
721 | 161 | raise TypeError( | ||
722 | 162 | "Expected 1 hash value, not %d" % len(value)) | ||
723 | 163 | expected = value[0] | ||
724 | 164 | check_hash(dld_file, expected, key) | ||
725 | 159 | if checksum: | 165 | if checksum: |
726 | 160 | check_hash(dld_file, checksum, hash_type) | 166 | check_hash(dld_file, checksum, hash_type) |
727 | 161 | return extract(dld_file, dest) | 167 | return extract(dld_file, dest) |
728 | 162 | 168 | ||
729 | === modified file 'hooks/charmhelpers/fetch/giturl.py' | |||
730 | --- hooks/charmhelpers/fetch/giturl.py 2015-05-27 13:01:09 +0000 | |||
731 | +++ hooks/charmhelpers/fetch/giturl.py 2015-07-16 20:31:27 +0000 | |||
732 | @@ -67,7 +67,7 @@ | |||
733 | 67 | try: | 67 | try: |
734 | 68 | self.clone(source, dest_dir, branch, depth) | 68 | self.clone(source, dest_dir, branch, depth) |
735 | 69 | except GitCommandError as e: | 69 | except GitCommandError as e: |
737 | 70 | raise UnhandledSource(e.message) | 70 | raise UnhandledSource(e) |
738 | 71 | except OSError as e: | 71 | except OSError as e: |
739 | 72 | raise UnhandledSource(e.strerror) | 72 | raise UnhandledSource(e.strerror) |
740 | 73 | return dest_dir | 73 | return dest_dir |
charm_lint_check #6284 cinder-next for corey.bryant mp265046
LINT OK: passed
Build: http:// 10.245. 162.77: 8080/job/ charm_lint_ check/6284/