Merge lp:~corey.bryant/charms/trusty/cinder/sync-ch into lp:~openstack-charmers-archive/charms/trusty/cinder/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
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+265046@code.launchpad.net
To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

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/

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

Build: http://10.245.162.77:8080/job/charm_unit_test/5916/

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://10.245.162.77:8080/job/charm_amulet_test/5139/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/deployment.py'
--- hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-06-19 16:29:27 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/deployment.py 2015-07-16 20:31:27 +0000
@@ -79,9 +79,9 @@
79 services.append(this_service)79 services.append(this_service)
80 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',80 use_source = ['mysql', 'mongodb', 'rabbitmq-server', 'ceph',
81 'ceph-osd', 'ceph-radosgw']81 'ceph-osd', 'ceph-radosgw']
82 # Openstack subordinate charms do not expose an origin option as that82 # Most OpenStack subordinate charms do not expose an origin option
83 # is controlled by the principle83 # as that is controlled by the principle.
84 ignore = ['neutron-openvswitch']84 ignore = ['cinder-ceph', 'hacluster', 'neutron-openvswitch']
8585
86 if self.openstack:86 if self.openstack:
87 for svc in services:87 for svc in services:
@@ -148,3 +148,36 @@
148 return os_origin.split('%s-' % self.series)[1].split('/')[0]148 return os_origin.split('%s-' % self.series)[1].split('/')[0]
149 else:149 else:
150 return releases[self.series]150 return releases[self.series]
151
152 def get_ceph_expected_pools(self, radosgw=False):
153 """Return a list of expected ceph pools in a ceph + cinder + glance
154 test scenario, based on OpenStack release and whether ceph radosgw
155 is flagged as present or not."""
156
157 if self._get_openstack_release() >= self.trusty_kilo:
158 # Kilo or later
159 pools = [
160 'rbd',
161 'cinder',
162 'glance'
163 ]
164 else:
165 # Juno or earlier
166 pools = [
167 'data',
168 'metadata',
169 'rbd',
170 'cinder',
171 'glance'
172 ]
173
174 if radosgw:
175 pools.extend([
176 '.rgw.root',
177 '.rgw.control',
178 '.rgw',
179 '.rgw.gc',
180 '.users.uid'
181 ])
182
183 return pools
151184
=== modified file 'hooks/charmhelpers/contrib/openstack/amulet/utils.py'
--- hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-06-19 16:29:27 +0000
+++ hooks/charmhelpers/contrib/openstack/amulet/utils.py 2015-07-16 20:31:27 +0000
@@ -14,16 +14,20 @@
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1616
17import amulet
18import json
17import logging19import logging
18import os20import os
19import six21import six
20import time22import time
21import urllib23import urllib
2224
25import cinderclient.v1.client as cinder_client
23import glanceclient.v1.client as glance_client26import glanceclient.v1.client as glance_client
24import heatclient.v1.client as heat_client27import heatclient.v1.client as heat_client
25import keystoneclient.v2_0 as keystone_client28import keystoneclient.v2_0 as keystone_client
26import novaclient.v1_1.client as nova_client29import novaclient.v1_1.client as nova_client
30import swiftclient
2731
28from charmhelpers.contrib.amulet.utils import (32from charmhelpers.contrib.amulet.utils import (
29 AmuletUtils33 AmuletUtils
@@ -171,6 +175,16 @@
171 self.log.debug('Checking if tenant exists ({})...'.format(tenant))175 self.log.debug('Checking if tenant exists ({})...'.format(tenant))
172 return tenant in [t.name for t in keystone.tenants.list()]176 return tenant in [t.name for t in keystone.tenants.list()]
173177
178 def authenticate_cinder_admin(self, keystone_sentry, username,
179 password, tenant):
180 """Authenticates admin user with cinder."""
181 # NOTE(beisner): cinder python client doesn't accept tokens.
182 service_ip = \
183 keystone_sentry.relation('shared-db',
184 'mysql:shared-db')['private-address']
185 ept = "http://{}:5000/v2.0".format(service_ip.strip().decode('utf-8'))
186 return cinder_client.Client(username, password, tenant, ept)
187
174 def authenticate_keystone_admin(self, keystone_sentry, user, password,188 def authenticate_keystone_admin(self, keystone_sentry, user, password,
175 tenant):189 tenant):
176 """Authenticates admin user with the keystone admin endpoint."""190 """Authenticates admin user with the keystone admin endpoint."""
@@ -212,9 +226,29 @@
212 return nova_client.Client(username=user, api_key=password,226 return nova_client.Client(username=user, api_key=password,
213 project_id=tenant, auth_url=ep)227 project_id=tenant, auth_url=ep)
214228
229 def authenticate_swift_user(self, keystone, user, password, tenant):
230 """Authenticates a regular user with swift api."""
231 self.log.debug('Authenticating swift user ({})...'.format(user))
232 ep = keystone.service_catalog.url_for(service_type='identity',
233 endpoint_type='publicURL')
234 return swiftclient.Connection(authurl=ep,
235 user=user,
236 key=password,
237 tenant_name=tenant,
238 auth_version='2.0')
239
215 def create_cirros_image(self, glance, image_name):240 def create_cirros_image(self, glance, image_name):
216 """Download the latest cirros image and upload it to glance."""241 """Download the latest cirros image and upload it to glance,
217 self.log.debug('Creating glance image ({})...'.format(image_name))242 validate and return a resource pointer.
243
244 :param glance: pointer to authenticated glance connection
245 :param image_name: display name for new image
246 :returns: glance image pointer
247 """
248 self.log.debug('Creating glance cirros image '
249 '({})...'.format(image_name))
250
251 # Download cirros image
218 http_proxy = os.getenv('AMULET_HTTP_PROXY')252 http_proxy = os.getenv('AMULET_HTTP_PROXY')
219 self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy))253 self.log.debug('AMULET_HTTP_PROXY: {}'.format(http_proxy))
220 if http_proxy:254 if http_proxy:
@@ -223,33 +257,51 @@
223 else:257 else:
224 opener = urllib.FancyURLopener()258 opener = urllib.FancyURLopener()
225259
226 f = opener.open("http://download.cirros-cloud.net/version/released")260 f = opener.open('http://download.cirros-cloud.net/version/released')
227 version = f.read().strip()261 version = f.read().strip()
228 cirros_img = "cirros-{}-x86_64-disk.img".format(version)262 cirros_img = 'cirros-{}-x86_64-disk.img'.format(version)
229 local_path = os.path.join('tests', cirros_img)263 local_path = os.path.join('tests', cirros_img)
230264
231 if not os.path.exists(local_path):265 if not os.path.exists(local_path):
232 cirros_url = "http://{}/{}/{}".format("download.cirros-cloud.net",266 cirros_url = 'http://{}/{}/{}'.format('download.cirros-cloud.net',
233 version, cirros_img)267 version, cirros_img)
234 opener.retrieve(cirros_url, local_path)268 opener.retrieve(cirros_url, local_path)
235 f.close()269 f.close()
236270
271 # Create glance image
237 with open(local_path) as f:272 with open(local_path) as f:
238 image = glance.images.create(name=image_name, is_public=True,273 image = glance.images.create(name=image_name, is_public=True,
239 disk_format='qcow2',274 disk_format='qcow2',
240 container_format='bare', data=f)275 container_format='bare', data=f)
241 count = 1276
242 status = image.status277 # Wait for image to reach active status
243 while status != 'active' and count < 10:278 img_id = image.id
244 time.sleep(3)279 ret = self.resource_reaches_status(glance.images, img_id,
245 image = glance.images.get(image.id)280 expected_stat='active',
246 status = image.status281 msg='Image status wait')
247 self.log.debug('image status: {}'.format(status))282 if not ret:
248 count += 1283 msg = 'Glance image failed to reach expected state.'
249284 amulet.raise_status(amulet.FAIL, msg=msg)
250 if status != 'active':285
251 self.log.error('image creation timed out')286 # Re-validate new image
252 return None287 self.log.debug('Validating image attributes...')
288 val_img_name = glance.images.get(img_id).name
289 val_img_stat = glance.images.get(img_id).status
290 val_img_pub = glance.images.get(img_id).is_public
291 val_img_cfmt = glance.images.get(img_id).container_format
292 val_img_dfmt = glance.images.get(img_id).disk_format
293 msg_attr = ('Image attributes - name:{} public:{} id:{} stat:{} '
294 'container fmt:{} disk fmt:{}'.format(
295 val_img_name, val_img_pub, img_id,
296 val_img_stat, val_img_cfmt, val_img_dfmt))
297
298 if val_img_name == image_name and val_img_stat == 'active' \
299 and val_img_pub is True and val_img_cfmt == 'bare' \
300 and val_img_dfmt == 'qcow2':
301 self.log.debug(msg_attr)
302 else:
303 msg = ('Volume validation failed, {}'.format(msg_attr))
304 amulet.raise_status(amulet.FAIL, msg=msg)
253305
254 return image306 return image
255307
@@ -260,22 +312,7 @@
260 self.log.warn('/!\\ DEPRECATION WARNING: use '312 self.log.warn('/!\\ DEPRECATION WARNING: use '
261 'delete_resource instead of delete_image.')313 'delete_resource instead of delete_image.')
262 self.log.debug('Deleting glance image ({})...'.format(image))314 self.log.debug('Deleting glance image ({})...'.format(image))
263 num_before = len(list(glance.images.list()))315 return self.delete_resource(glance.images, image, msg='glance image')
264 glance.images.delete(image)
265
266 count = 1
267 num_after = len(list(glance.images.list()))
268 while num_after != (num_before - 1) and count < 10:
269 time.sleep(3)
270 num_after = len(list(glance.images.list()))
271 self.log.debug('number of images: {}'.format(num_after))
272 count += 1
273
274 if num_after != (num_before - 1):
275 self.log.error('image deletion timed out')
276 return False
277
278 return True
279316
280 def create_instance(self, nova, image_name, instance_name, flavor):317 def create_instance(self, nova, image_name, instance_name, flavor):
281 """Create the specified instance."""318 """Create the specified instance."""
@@ -308,22 +345,8 @@
308 self.log.warn('/!\\ DEPRECATION WARNING: use '345 self.log.warn('/!\\ DEPRECATION WARNING: use '
309 'delete_resource instead of delete_instance.')346 'delete_resource instead of delete_instance.')
310 self.log.debug('Deleting instance ({})...'.format(instance))347 self.log.debug('Deleting instance ({})...'.format(instance))
311 num_before = len(list(nova.servers.list()))348 return self.delete_resource(nova.servers, instance,
312 nova.servers.delete(instance)349 msg='nova instance')
313
314 count = 1
315 num_after = len(list(nova.servers.list()))
316 while num_after != (num_before - 1) and count < 10:
317 time.sleep(3)
318 num_after = len(list(nova.servers.list()))
319 self.log.debug('number of instances: {}'.format(num_after))
320 count += 1
321
322 if num_after != (num_before - 1):
323 self.log.error('instance deletion timed out')
324 return False
325
326 return True
327350
328 def create_or_get_keypair(self, nova, keypair_name="testkey"):351 def create_or_get_keypair(self, nova, keypair_name="testkey"):
329 """Create a new keypair, or return pointer if it already exists."""352 """Create a new keypair, or return pointer if it already exists."""
@@ -339,6 +362,88 @@
339 _keypair = nova.keypairs.create(name=keypair_name)362 _keypair = nova.keypairs.create(name=keypair_name)
340 return _keypair363 return _keypair
341364
365 def create_cinder_volume(self, cinder, vol_name="demo-vol", vol_size=1,
366 img_id=None, src_vol_id=None, snap_id=None):
367 """Create cinder volume, optionally from a glance image, OR
368 optionally as a clone of an existing volume, OR optionally
369 from a snapshot. Wait for the new volume status to reach
370 the expected status, validate and return a resource pointer.
371
372 :param vol_name: cinder volume display name
373 :param vol_size: size in gigabytes
374 :param img_id: optional glance image id
375 :param src_vol_id: optional source volume id to clone
376 :param snap_id: optional snapshot id to use
377 :returns: cinder volume pointer
378 """
379 # Handle parameter input and avoid impossible combinations
380 if img_id and not src_vol_id and not snap_id:
381 # Create volume from image
382 self.log.debug('Creating cinder volume from glance image...')
383 bootable = 'true'
384 elif src_vol_id and not img_id and not snap_id:
385 # Clone an existing volume
386 self.log.debug('Cloning cinder volume...')
387 bootable = cinder.volumes.get(src_vol_id).bootable
388 elif snap_id and not src_vol_id and not img_id:
389 # Create volume from snapshot
390 self.log.debug('Creating cinder volume from snapshot...')
391 snap = cinder.volume_snapshots.find(id=snap_id)
392 vol_size = snap.size
393 snap_vol_id = cinder.volume_snapshots.get(snap_id).volume_id
394 bootable = cinder.volumes.get(snap_vol_id).bootable
395 elif not img_id and not src_vol_id and not snap_id:
396 # Create volume
397 self.log.debug('Creating cinder volume...')
398 bootable = 'false'
399 else:
400 # Impossible combination of parameters
401 msg = ('Invalid method use - name:{} size:{} img_id:{} '
402 'src_vol_id:{} snap_id:{}'.format(vol_name, vol_size,
403 img_id, src_vol_id,
404 snap_id))
405 amulet.raise_status(amulet.FAIL, msg=msg)
406
407 # Create new volume
408 try:
409 vol_new = cinder.volumes.create(display_name=vol_name,
410 imageRef=img_id,
411 size=vol_size,
412 source_volid=src_vol_id,
413 snapshot_id=snap_id)
414 vol_id = vol_new.id
415 except Exception as e:
416 msg = 'Failed to create volume: {}'.format(e)
417 amulet.raise_status(amulet.FAIL, msg=msg)
418
419 # Wait for volume to reach available status
420 ret = self.resource_reaches_status(cinder.volumes, vol_id,
421 expected_stat="available",
422 msg="Volume status wait")
423 if not ret:
424 msg = 'Cinder volume failed to reach expected state.'
425 amulet.raise_status(amulet.FAIL, msg=msg)
426
427 # Re-validate new volume
428 self.log.debug('Validating volume attributes...')
429 val_vol_name = cinder.volumes.get(vol_id).display_name
430 val_vol_boot = cinder.volumes.get(vol_id).bootable
431 val_vol_stat = cinder.volumes.get(vol_id).status
432 val_vol_size = cinder.volumes.get(vol_id).size
433 msg_attr = ('Volume attributes - name:{} id:{} stat:{} boot:'
434 '{} size:{}'.format(val_vol_name, vol_id,
435 val_vol_stat, val_vol_boot,
436 val_vol_size))
437
438 if val_vol_boot == bootable and val_vol_stat == 'available' \
439 and val_vol_name == vol_name and val_vol_size == vol_size:
440 self.log.debug(msg_attr)
441 else:
442 msg = ('Volume validation failed, {}'.format(msg_attr))
443 amulet.raise_status(amulet.FAIL, msg=msg)
444
445 return vol_new
446
342 def delete_resource(self, resource, resource_id,447 def delete_resource(self, resource, resource_id,
343 msg="resource", max_wait=120):448 msg="resource", max_wait=120):
344 """Delete one openstack resource, such as one instance, keypair,449 """Delete one openstack resource, such as one instance, keypair,
@@ -350,6 +455,8 @@
350 :param max_wait: maximum wait time in seconds455 :param max_wait: maximum wait time in seconds
351 :returns: True if successful, otherwise False456 :returns: True if successful, otherwise False
352 """457 """
458 self.log.debug('Deleting OpenStack resource '
459 '{} ({})'.format(resource_id, msg))
353 num_before = len(list(resource.list()))460 num_before = len(list(resource.list()))
354 resource.delete(resource_id)461 resource.delete(resource_id)
355462
@@ -411,3 +518,87 @@
411 self.log.debug('{} never reached expected status: '518 self.log.debug('{} never reached expected status: '
412 '{}'.format(resource_id, expected_stat))519 '{}'.format(resource_id, expected_stat))
413 return False520 return False
521
522 def get_ceph_osd_id_cmd(self, index):
523 """Produce a shell command that will return a ceph-osd id."""
524 return ("`initctl list | grep 'ceph-osd ' | "
525 "awk 'NR=={} {{ print $2 }}' | "
526 "grep -o '[0-9]*'`".format(index + 1))
527
528 def get_ceph_pools(self, sentry_unit):
529 """Return a dict of ceph pools from a single ceph unit, with
530 pool name as keys, pool id as vals."""
531 pools = {}
532 cmd = 'sudo ceph osd lspools'
533 output, code = sentry_unit.run(cmd)
534 if code != 0:
535 msg = ('{} `{}` returned {} '
536 '{}'.format(sentry_unit.info['unit_name'],
537 cmd, code, output))
538 amulet.raise_status(amulet.FAIL, msg=msg)
539
540 # Example output: 0 data,1 metadata,2 rbd,3 cinder,4 glance,
541 for pool in str(output).split(','):
542 pool_id_name = pool.split(' ')
543 if len(pool_id_name) == 2:
544 pool_id = pool_id_name[0]
545 pool_name = pool_id_name[1]
546 pools[pool_name] = int(pool_id)
547
548 self.log.debug('Pools on {}: {}'.format(sentry_unit.info['unit_name'],
549 pools))
550 return pools
551
552 def get_ceph_df(self, sentry_unit):
553 """Return dict of ceph df json output, including ceph pool state.
554
555 :param sentry_unit: Pointer to amulet sentry instance (juju unit)
556 :returns: Dict of ceph df output
557 """
558 cmd = 'sudo ceph df --format=json'
559 output, code = sentry_unit.run(cmd)
560 if code != 0:
561 msg = ('{} `{}` returned {} '
562 '{}'.format(sentry_unit.info['unit_name'],
563 cmd, code, output))
564 amulet.raise_status(amulet.FAIL, msg=msg)
565 return json.loads(output)
566
567 def get_ceph_pool_sample(self, sentry_unit, pool_id=0):
568 """Take a sample of attributes of a ceph pool, returning ceph
569 pool name, object count and disk space used for the specified
570 pool ID number.
571
572 :param sentry_unit: Pointer to amulet sentry instance (juju unit)
573 :param pool_id: Ceph pool ID
574 :returns: List of pool name, object count, kb disk space used
575 """
576 df = self.get_ceph_df(sentry_unit)
577 pool_name = df['pools'][pool_id]['name']
578 obj_count = df['pools'][pool_id]['stats']['objects']
579 kb_used = df['pools'][pool_id]['stats']['kb_used']
580 self.log.debug('Ceph {} pool (ID {}): {} objects, '
581 '{} kb used'.format(pool_name, pool_id,
582 obj_count, kb_used))
583 return pool_name, obj_count, kb_used
584
585 def validate_ceph_pool_samples(self, samples, sample_type="resource pool"):
586 """Validate ceph pool samples taken over time, such as pool
587 object counts or pool kb used, before adding, after adding, and
588 after deleting items which affect those pool attributes. The
589 2nd element is expected to be greater than the 1st; 3rd is expected
590 to be less than the 2nd.
591
592 :param samples: List containing 3 data samples
593 :param sample_type: String for logging and usage context
594 :returns: None if successful, Failure message otherwise
595 """
596 original, created, deleted = range(3)
597 if samples[created] <= samples[original] or \
598 samples[deleted] >= samples[created]:
599 return ('Ceph {} samples ({}) '
600 'unexpected.'.format(sample_type, samples))
601 else:
602 self.log.debug('Ceph {} samples (OK): '
603 '{}'.format(sample_type, samples))
604 return None
414605
=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
--- hooks/charmhelpers/contrib/openstack/context.py 2015-06-19 16:29:27 +0000
+++ hooks/charmhelpers/contrib/openstack/context.py 2015-07-16 20:31:27 +0000
@@ -122,21 +122,24 @@
122 of specifying multiple key value pairs within the same string. For122 of specifying multiple key value pairs within the same string. For
123 example, a string in the format of 'key1=value1, key2=value2' will123 example, a string in the format of 'key1=value1, key2=value2' will
124 return a dict of:124 return a dict of:
125 {'key1': 'value1',125
126 'key2': 'value2'}.126 {'key1': 'value1',
127 'key2': 'value2'}.
127128
128 2. A string in the above format, but supporting a comma-delimited list129 2. A string in the above format, but supporting a comma-delimited list
129 of values for the same key. For example, a string in the format of130 of values for the same key. For example, a string in the format of
130 'key1=value1, key2=value3,value4,value5' will return a dict of:131 'key1=value1, key2=value3,value4,value5' will return a dict of:
131 {'key1', 'value1',132
132 'key2', 'value2,value3,value4'}133 {'key1', 'value1',
134 'key2', 'value2,value3,value4'}
133135
134 3. A string containing a colon character (:) prior to an equal136 3. A string containing a colon character (:) prior to an equal
135 character (=) will be treated as yaml and parsed as such. This can be137 character (=) will be treated as yaml and parsed as such. This can be
136 used to specify more complex key value pairs. For example,138 used to specify more complex key value pairs. For example,
137 a string in the format of 'key1: subkey1=value1, subkey2=value2' will139 a string in the format of 'key1: subkey1=value1, subkey2=value2' will
138 return a dict of:140 return a dict of:
139 {'key1', 'subkey1=value1, subkey2=value2'}141
142 {'key1', 'subkey1=value1, subkey2=value2'}
140143
141 The provided config_flags string may be a list of comma-separated values144 The provided config_flags string may be a list of comma-separated values
142 which themselves may be comma-separated list of values.145 which themselves may be comma-separated list of values.
@@ -891,8 +894,6 @@
891 return ctxt894 return ctxt
892895
893 def __call__(self):896 def __call__(self):
894 self._ensure_packages()
895
896 if self.network_manager not in ['quantum', 'neutron']:897 if self.network_manager not in ['quantum', 'neutron']:
897 return {}898 return {}
898899
899900
=== modified file 'hooks/charmhelpers/contrib/openstack/templates/ceph.conf'
--- hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2014-04-10 15:46:24 +0000
+++ hooks/charmhelpers/contrib/openstack/templates/ceph.conf 2015-07-16 20:31:27 +0000
@@ -5,11 +5,11 @@
5###############################################################################5###############################################################################
6[global]6[global]
7{% if auth -%}7{% if auth -%}
8 auth_supported = {{ auth }}8auth_supported = {{ auth }}
9 keyring = /etc/ceph/$cluster.$name.keyring9keyring = /etc/ceph/$cluster.$name.keyring
10 mon host = {{ mon_hosts }}10mon host = {{ mon_hosts }}
11{% endif -%}11{% endif -%}
12 log to syslog = {{ use_syslog }}12log to syslog = {{ use_syslog }}
13 err to syslog = {{ use_syslog }}13err to syslog = {{ use_syslog }}
14 clog to syslog = {{ use_syslog }}14clog to syslog = {{ use_syslog }}
1515
1616
=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
--- hooks/charmhelpers/contrib/openstack/utils.py 2015-06-23 23:53:39 +0000
+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-07-16 20:31:27 +0000
@@ -522,6 +522,7 @@
522 Clone/install all specified OpenStack repositories.522 Clone/install all specified OpenStack repositories.
523523
524 The expected format of projects_yaml is:524 The expected format of projects_yaml is:
525
525 repositories:526 repositories:
526 - {name: keystone,527 - {name: keystone,
527 repository: 'git://git.openstack.org/openstack/keystone.git',528 repository: 'git://git.openstack.org/openstack/keystone.git',
@@ -529,11 +530,13 @@
529 - {name: requirements,530 - {name: requirements,
530 repository: 'git://git.openstack.org/openstack/requirements.git',531 repository: 'git://git.openstack.org/openstack/requirements.git',
531 branch: 'stable/icehouse'}532 branch: 'stable/icehouse'}
533
532 directory: /mnt/openstack-git534 directory: /mnt/openstack-git
533 http_proxy: squid-proxy-url535 http_proxy: squid-proxy-url
534 https_proxy: squid-proxy-url536 https_proxy: squid-proxy-url
535537
536 The directory, http_proxy, and https_proxy keys are optional.538 The directory, http_proxy, and https_proxy keys are optional.
539
537 """540 """
538 global requirements_dir541 global requirements_dir
539 parent_dir = '/mnt/openstack-git'542 parent_dir = '/mnt/openstack-git'
@@ -555,10 +558,11 @@
555558
556 pip_create_virtualenv(os.path.join(parent_dir, 'venv'))559 pip_create_virtualenv(os.path.join(parent_dir, 'venv'))
557560
558 # Upgrade setuptools from default virtualenv version. The default version561 # Upgrade setuptools and pip from default virtualenv versions. The default
559 # in trusty breaks update.py in global requirements master branch.562 # versions in trusty break master OpenStack branch deployments.
560 pip_install('setuptools', upgrade=True, proxy=http_proxy,563 for p in ['pip', 'setuptools']:
561 venv=os.path.join(parent_dir, 'venv'))564 pip_install(p, upgrade=True, proxy=http_proxy,
565 venv=os.path.join(parent_dir, 'venv'))
562566
563 for p in projects['repositories']:567 for p in projects['repositories']:
564 repo = p['repository']568 repo = p['repository']
565569
=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-01-26 09:47:37 +0000
+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-07-16 20:31:27 +0000
@@ -60,12 +60,12 @@
60KEYFILE = '/etc/ceph/ceph.client.{}.key'60KEYFILE = '/etc/ceph/ceph.client.{}.key'
6161
62CEPH_CONF = """[global]62CEPH_CONF = """[global]
63 auth supported = {auth}63auth supported = {auth}
64 keyring = {keyring}64keyring = {keyring}
65 mon host = {mon_hosts}65mon host = {mon_hosts}
66 log to syslog = {use_syslog}66log to syslog = {use_syslog}
67 err to syslog = {use_syslog}67err to syslog = {use_syslog}
68 clog to syslog = {use_syslog}68clog to syslog = {use_syslog}
69"""69"""
7070
7171
7272
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2015-06-29 13:24:23 +0000
+++ hooks/charmhelpers/core/hookenv.py 2015-07-16 20:31:27 +0000
@@ -761,6 +761,7 @@
761761
762 This is useful for modules and classes to perform initialization762 This is useful for modules and classes to perform initialization
763 and inject behavior. In particular:763 and inject behavior. In particular:
764
764 - Run common code before all of your hooks, such as logging765 - Run common code before all of your hooks, such as logging
765 the hook name or interesting relation data.766 the hook name or interesting relation data.
766 - Defer object or module initialization that requires a hook767 - Defer object or module initialization that requires a hook
767768
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-06-19 16:29:27 +0000
+++ hooks/charmhelpers/core/host.py 2015-07-16 20:31:27 +0000
@@ -63,6 +63,36 @@
63 return service_result63 return service_result
6464
6565
66def service_pause(service_name, init_dir=None):
67 """Pause a system service.
68
69 Stop it, and prevent it from starting again at boot."""
70 if init_dir is None:
71 init_dir = "/etc/init"
72 stopped = service_stop(service_name)
73 # XXX: Support systemd too
74 override_path = os.path.join(
75 init_dir, '{}.conf.override'.format(service_name))
76 with open(override_path, 'w') as fh:
77 fh.write("manual\n")
78 return stopped
79
80
81def service_resume(service_name, init_dir=None):
82 """Resume a system service.
83
84 Reenable starting again at boot. Start the service"""
85 # XXX: Support systemd too
86 if init_dir is None:
87 init_dir = "/etc/init"
88 override_path = os.path.join(
89 init_dir, '{}.conf.override'.format(service_name))
90 if os.path.exists(override_path):
91 os.unlink(override_path)
92 started = service_start(service_name)
93 return started
94
95
66def service(action, service_name):96def service(action, service_name):
67 """Control a system service"""97 """Control a system service"""
68 cmd = ['service', service_name, action]98 cmd = ['service', service_name, action]
@@ -140,11 +170,7 @@
140170
141def add_user_to_group(username, group):171def add_user_to_group(username, group):
142 """Add a user to a group"""172 """Add a user to a group"""
143 cmd = [173 cmd = ['gpasswd', '-a', username, group]
144 'gpasswd', '-a',
145 username,
146 group
147 ]
148 log("Adding user {} to group {}".format(username, group))174 log("Adding user {} to group {}".format(username, group))
149 subprocess.check_call(cmd)175 subprocess.check_call(cmd)
150176
151177
=== modified file 'hooks/charmhelpers/core/services/helpers.py'
--- hooks/charmhelpers/core/services/helpers.py 2015-05-11 07:27:02 +0000
+++ hooks/charmhelpers/core/services/helpers.py 2015-07-16 20:31:27 +0000
@@ -239,12 +239,12 @@
239 action.239 action.
240240
241 :param str source: The template source file, relative to241 :param str source: The template source file, relative to
242 `$CHARM_DIR/templates`242 `$CHARM_DIR/templates`
243
244 :param str target: The target to write the rendered template to243 :param str target: The target to write the rendered template to
245 :param str owner: The owner of the rendered file244 :param str owner: The owner of the rendered file
246 :param str group: The group of the rendered file245 :param str group: The group of the rendered file
247 :param int perms: The permissions of the rendered file246 :param int perms: The permissions of the rendered file
247
248 """248 """
249 def __init__(self, source, target,249 def __init__(self, source, target,
250 owner='root', group='root', perms=0o444):250 owner='root', group='root', perms=0o444):
251251
=== modified file 'hooks/charmhelpers/fetch/__init__.py'
--- hooks/charmhelpers/fetch/__init__.py 2015-06-10 21:37:05 +0000
+++ hooks/charmhelpers/fetch/__init__.py 2015-07-16 20:31:27 +0000
@@ -215,19 +215,27 @@
215 _run_apt_command(cmd, fatal)215 _run_apt_command(cmd, fatal)
216216
217217
218def apt_mark(packages, mark, fatal=False):
219 """Flag one or more packages using apt-mark"""
220 cmd = ['apt-mark', mark]
221 if isinstance(packages, six.string_types):
222 cmd.append(packages)
223 else:
224 cmd.extend(packages)
225 log("Holding {}".format(packages))
226
227 if fatal:
228 subprocess.check_call(cmd, universal_newlines=True)
229 else:
230 subprocess.call(cmd, universal_newlines=True)
231
232
218def apt_hold(packages, fatal=False):233def apt_hold(packages, fatal=False):
219 """Hold one or more packages"""234 return apt_mark(packages, 'hold', fatal=fatal)
220 cmd = ['apt-mark', 'hold']235
221 if isinstance(packages, six.string_types):236
222 cmd.append(packages)237def apt_unhold(packages, fatal=False):
223 else:238 return apt_mark(packages, 'unhold', fatal=fatal)
224 cmd.extend(packages)
225 log("Holding {}".format(packages))
226
227 if fatal:
228 subprocess.check_call(cmd)
229 else:
230 subprocess.call(cmd)
231239
232240
233def add_source(source, key=None):241def add_source(source, key=None):
@@ -370,8 +378,9 @@
370 for handler in handlers:378 for handler in handlers:
371 try:379 try:
372 installed_to = handler.install(source, *args, **kwargs)380 installed_to = handler.install(source, *args, **kwargs)
373 except UnhandledSource:381 except UnhandledSource as e:
374 pass382 log('Install source attempt unsuccessful: {}'.format(e),
383 level='WARNING')
375 if not installed_to:384 if not installed_to:
376 raise UnhandledSource("No handler found for source {}".format(source))385 raise UnhandledSource("No handler found for source {}".format(source))
377 return installed_to386 return installed_to
378387
=== modified file 'hooks/charmhelpers/fetch/archiveurl.py'
--- hooks/charmhelpers/fetch/archiveurl.py 2015-03-13 13:00:03 +0000
+++ hooks/charmhelpers/fetch/archiveurl.py 2015-07-16 20:31:27 +0000
@@ -77,6 +77,8 @@
77 def can_handle(self, source):77 def can_handle(self, source):
78 url_parts = self.parse_url(source)78 url_parts = self.parse_url(source)
79 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):79 if url_parts.scheme not in ('http', 'https', 'ftp', 'file'):
80 # XXX: Why is this returning a boolean and a string? It's
81 # doomed to fail since "bool(can_handle('foo://'))" will be True.
80 return "Wrong source type"82 return "Wrong source type"
81 if get_archive_handler(self.base_url(source)):83 if get_archive_handler(self.base_url(source)):
82 return True84 return True
@@ -155,7 +157,11 @@
155 else:157 else:
156 algorithms = hashlib.algorithms_available158 algorithms = hashlib.algorithms_available
157 if key in algorithms:159 if key in algorithms:
158 check_hash(dld_file, value, key)160 if len(value) != 1:
161 raise TypeError(
162 "Expected 1 hash value, not %d" % len(value))
163 expected = value[0]
164 check_hash(dld_file, expected, key)
159 if checksum:165 if checksum:
160 check_hash(dld_file, checksum, hash_type)166 check_hash(dld_file, checksum, hash_type)
161 return extract(dld_file, dest)167 return extract(dld_file, dest)
162168
=== modified file 'hooks/charmhelpers/fetch/giturl.py'
--- hooks/charmhelpers/fetch/giturl.py 2015-05-27 13:01:09 +0000
+++ hooks/charmhelpers/fetch/giturl.py 2015-07-16 20:31:27 +0000
@@ -67,7 +67,7 @@
67 try:67 try:
68 self.clone(source, dest_dir, branch, depth)68 self.clone(source, dest_dir, branch, depth)
69 except GitCommandError as e:69 except GitCommandError as e:
70 raise UnhandledSource(e.message)70 raise UnhandledSource(e)
71 except OSError as e:71 except OSError as e:
72 raise UnhandledSource(e.strerror)72 raise UnhandledSource(e.strerror)
73 return dest_dir73 return dest_dir

Subscribers

People subscribed via source and target branches