Merge lp:~tribaal/charms/trusty/nova-cloud-controller/update-charm-helpers-stable into lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/trunk

Proposed by Chris Glass
Status: Rejected
Rejected by: Chris Glass
Proposed branch: lp:~tribaal/charms/trusty/nova-cloud-controller/update-charm-helpers-stable
Merge into: lp:~openstack-charmers-archive/charms/trusty/nova-cloud-controller/trunk
Diff against target: 562 lines (+280/-53)
11 files modified
hooks/charmhelpers/cli/__init__.py (+1/-5)
hooks/charmhelpers/cli/commands.py (+4/-4)
hooks/charmhelpers/cli/hookenv.py (+23/-0)
hooks/charmhelpers/contrib/network/ip.py (+5/-3)
hooks/charmhelpers/contrib/openstack/context.py (+8/-9)
hooks/charmhelpers/contrib/openstack/templating.py (+2/-1)
hooks/charmhelpers/contrib/openstack/utils.py (+7/-5)
hooks/charmhelpers/contrib/storage/linux/ceph.py (+224/-2)
hooks/charmhelpers/contrib/storage/linux/utils.py (+3/-2)
hooks/charmhelpers/core/hookenv.py (+1/-20)
hooks/charmhelpers/core/host.py (+2/-2)
To merge this branch: bzr merge lp:~tribaal/charms/trusty/nova-cloud-controller/update-charm-helpers-stable
Reviewer Review Type Date Requested Status
OpenStack Charmers Pending
Review via email: mp+271771@code.launchpad.net

Description of the change

This branch includes the latest version of charm-helpers stable (from lp:~openstack-charmers/charm-helpers/stable ).

The "make sync" make target was used to produce this diff (mechanically).

To post a comment you must log in.
Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_lint_check #10401 nova-cloud-controller for tribaal mp271771
    LINT FAIL: lint-test failed
    LINT FAIL: charm-proof failed

LINT Results (max last 2 lines):
make: *** [lint] Error 100
ERROR:root:Make target returned non-zero.

Full lint test output: http://paste.ubuntu.com/12512949/
Build: http://10.245.162.77:8080/job/charm_lint_check/10401/

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_unit_test #9547 nova-cloud-controller for tribaal mp271771
    UNIT OK: passed

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

Revision history for this message
uosci-testing-bot (uosci-testing-bot) wrote :

charm_amulet_test #6544 nova-cloud-controller for tribaal mp271771
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/12513016/
Build: http://10.245.162.77:8080/job/charm_amulet_test/6544/

Unmerged revisions

169. By Chris Glass

Update stable charm-helpers (using "make sync").

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/charmhelpers/cli/__init__.py'
2--- hooks/charmhelpers/cli/__init__.py 2015-08-10 16:36:50 +0000
3+++ hooks/charmhelpers/cli/__init__.py 2015-09-21 07:13:28 +0000
4@@ -152,15 +152,11 @@
5 arguments = self.argument_parser.parse_args()
6 argspec = inspect.getargspec(arguments.func)
7 vargs = []
8- kwargs = {}
9 for arg in argspec.args:
10 vargs.append(getattr(arguments, arg))
11 if argspec.varargs:
12 vargs.extend(getattr(arguments, argspec.varargs))
13- if argspec.keywords:
14- for kwarg in argspec.keywords.items():
15- kwargs[kwarg] = getattr(arguments, kwarg)
16- output = arguments.func(*vargs, **kwargs)
17+ output = arguments.func(*vargs)
18 if getattr(arguments.func, '_cli_test_command', False):
19 self.exit_code = 0 if output else 1
20 output = ''
21
22=== modified file 'hooks/charmhelpers/cli/commands.py'
23--- hooks/charmhelpers/cli/commands.py 2015-08-10 16:36:50 +0000
24+++ hooks/charmhelpers/cli/commands.py 2015-09-21 07:13:28 +0000
25@@ -26,7 +26,7 @@
26 """
27 Import the sub-modules which have decorated subcommands to register with chlp.
28 """
29-import host # noqa
30-import benchmark # noqa
31-import unitdata # noqa
32-from charmhelpers.core import hookenv # noqa
33+from . import host # noqa
34+from . import benchmark # noqa
35+from . import unitdata # noqa
36+from . import hookenv # noqa
37
38=== added file 'hooks/charmhelpers/cli/hookenv.py'
39--- hooks/charmhelpers/cli/hookenv.py 1970-01-01 00:00:00 +0000
40+++ hooks/charmhelpers/cli/hookenv.py 2015-09-21 07:13:28 +0000
41@@ -0,0 +1,23 @@
42+# Copyright 2014-2015 Canonical Limited.
43+#
44+# This file is part of charm-helpers.
45+#
46+# charm-helpers is free software: you can redistribute it and/or modify
47+# it under the terms of the GNU Lesser General Public License version 3 as
48+# published by the Free Software Foundation.
49+#
50+# charm-helpers is distributed in the hope that it will be useful,
51+# but WITHOUT ANY WARRANTY; without even the implied warranty of
52+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53+# GNU Lesser General Public License for more details.
54+#
55+# You should have received a copy of the GNU Lesser General Public License
56+# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
57+
58+from . import cmdline
59+from charmhelpers.core import hookenv
60+
61+
62+cmdline.subcommand('relation-id')(hookenv.relation_id._wrapped)
63+cmdline.subcommand('service-name')(hookenv.service_name)
64+cmdline.subcommand('remote-service-name')(hookenv.remote_service_name._wrapped)
65
66=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
67--- hooks/charmhelpers/contrib/network/ip.py 2015-03-31 14:56:11 +0000
68+++ hooks/charmhelpers/contrib/network/ip.py 2015-09-21 07:13:28 +0000
69@@ -23,7 +23,7 @@
70 from functools import partial
71
72 from charmhelpers.core.hookenv import unit_get
73-from charmhelpers.fetch import apt_install
74+from charmhelpers.fetch import apt_install, apt_update
75 from charmhelpers.core.hookenv import (
76 log,
77 WARNING,
78@@ -32,13 +32,15 @@
79 try:
80 import netifaces
81 except ImportError:
82- apt_install('python-netifaces')
83+ apt_update(fatal=True)
84+ apt_install('python-netifaces', fatal=True)
85 import netifaces
86
87 try:
88 import netaddr
89 except ImportError:
90- apt_install('python-netaddr')
91+ apt_update(fatal=True)
92+ apt_install('python-netaddr', fatal=True)
93 import netaddr
94
95
96
97=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
98--- hooks/charmhelpers/contrib/openstack/context.py 2015-08-10 16:36:50 +0000
99+++ hooks/charmhelpers/contrib/openstack/context.py 2015-09-21 07:13:28 +0000
100@@ -483,13 +483,15 @@
101
102 log('Generating template context for ceph', level=DEBUG)
103 mon_hosts = []
104- auth = None
105- key = None
106- use_syslog = str(config('use-syslog')).lower()
107+ ctxt = {
108+ 'use_syslog': str(config('use-syslog')).lower()
109+ }
110 for rid in relation_ids('ceph'):
111 for unit in related_units(rid):
112- auth = relation_get('auth', rid=rid, unit=unit)
113- key = relation_get('key', rid=rid, unit=unit)
114+ if not ctxt.get('auth'):
115+ ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)
116+ if not ctxt.get('key'):
117+ ctxt['key'] = relation_get('key', rid=rid, unit=unit)
118 ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
119 unit=unit)
120 unit_priv_addr = relation_get('private-address', rid=rid,
121@@ -498,10 +500,7 @@
122 ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
123 mon_hosts.append(ceph_addr)
124
125- ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
126- 'auth': auth,
127- 'key': key,
128- 'use_syslog': use_syslog}
129+ ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))
130
131 if not os.path.isdir('/etc/ceph'):
132 os.mkdir('/etc/ceph')
133
134=== modified file 'hooks/charmhelpers/contrib/openstack/templating.py'
135--- hooks/charmhelpers/contrib/openstack/templating.py 2015-08-10 16:36:50 +0000
136+++ hooks/charmhelpers/contrib/openstack/templating.py 2015-09-21 07:13:28 +0000
137@@ -18,7 +18,7 @@
138
139 import six
140
141-from charmhelpers.fetch import apt_install
142+from charmhelpers.fetch import apt_install, apt_update
143 from charmhelpers.core.hookenv import (
144 log,
145 ERROR,
146@@ -29,6 +29,7 @@
147 try:
148 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
149 except ImportError:
150+ apt_update(fatal=True)
151 apt_install('python-jinja2', fatal=True)
152 from jinja2 import FileSystemLoader, ChoiceLoader, Environment, exceptions
153
154
155=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
156--- hooks/charmhelpers/contrib/openstack/utils.py 2015-08-10 16:36:50 +0000
157+++ hooks/charmhelpers/contrib/openstack/utils.py 2015-09-21 07:13:28 +0000
158@@ -1,5 +1,3 @@
159-#!/usr/bin/python
160-
161 # Copyright 2014-2015 Canonical Limited.
162 #
163 # This file is part of charm-helpers.
164@@ -167,9 +165,9 @@
165 error_out(e)
166
167
168-def get_os_version_codename(codename):
169+def get_os_version_codename(codename, version_map=OPENSTACK_CODENAMES):
170 '''Determine OpenStack version number from codename.'''
171- for k, v in six.iteritems(OPENSTACK_CODENAMES):
172+ for k, v in six.iteritems(version_map):
173 if v == codename:
174 return k
175 e = 'Could not derive OpenStack version for '\
176@@ -392,7 +390,11 @@
177 import apt_pkg as apt
178 src = config('openstack-origin')
179 cur_vers = get_os_version_package(package)
180- available_vers = get_os_version_install_source(src)
181+ if "swift" in package:
182+ codename = get_os_codename_install_source(src)
183+ available_vers = get_os_version_codename(codename, SWIFT_CODENAMES)
184+ else:
185+ available_vers = get_os_version_install_source(src)
186 apt.init()
187 return apt.version_compare(available_vers, cur_vers) == 1
188
189
190=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
191--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-08-10 16:36:50 +0000
192+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-09-21 07:13:28 +0000
193@@ -28,6 +28,7 @@
194 import shutil
195 import json
196 import time
197+import uuid
198
199 from subprocess import (
200 check_call,
201@@ -35,8 +36,10 @@
202 CalledProcessError,
203 )
204 from charmhelpers.core.hookenv import (
205+ local_unit,
206 relation_get,
207 relation_ids,
208+ relation_set,
209 related_units,
210 log,
211 DEBUG,
212@@ -411,17 +414,52 @@
213
214 The API is versioned and defaults to version 1.
215 """
216- def __init__(self, api_version=1):
217+ def __init__(self, api_version=1, request_id=None):
218 self.api_version = api_version
219+ if request_id:
220+ self.request_id = request_id
221+ else:
222+ self.request_id = str(uuid.uuid1())
223 self.ops = []
224
225 def add_op_create_pool(self, name, replica_count=3):
226 self.ops.append({'op': 'create-pool', 'name': name,
227 'replicas': replica_count})
228
229+ def set_ops(self, ops):
230+ """Set request ops to provided value.
231+
232+ Useful for injecting ops that come from a previous request
233+ to allow comparisons to ensure validity.
234+ """
235+ self.ops = ops
236+
237 @property
238 def request(self):
239- return json.dumps({'api-version': self.api_version, 'ops': self.ops})
240+ return json.dumps({'api-version': self.api_version, 'ops': self.ops,
241+ 'request-id': self.request_id})
242+
243+ def _ops_equal(self, other):
244+ if len(self.ops) == len(other.ops):
245+ for req_no in range(0, len(self.ops)):
246+ for key in ['replicas', 'name', 'op']:
247+ if self.ops[req_no][key] != other.ops[req_no][key]:
248+ return False
249+ else:
250+ return False
251+ return True
252+
253+ def __eq__(self, other):
254+ if not isinstance(other, self.__class__):
255+ return False
256+ if self.api_version == other.api_version and \
257+ self._ops_equal(other):
258+ return True
259+ else:
260+ return False
261+
262+ def __ne__(self, other):
263+ return not self.__eq__(other)
264
265
266 class CephBrokerRsp(object):
267@@ -431,14 +469,198 @@
268
269 The API is versioned and defaults to version 1.
270 """
271+
272 def __init__(self, encoded_rsp):
273 self.api_version = None
274 self.rsp = json.loads(encoded_rsp)
275
276 @property
277+ def request_id(self):
278+ return self.rsp.get('request-id')
279+
280+ @property
281 def exit_code(self):
282 return self.rsp.get('exit-code')
283
284 @property
285 def exit_msg(self):
286 return self.rsp.get('stderr')
287+
288+
289+# Ceph Broker Conversation:
290+# If a charm needs an action to be taken by ceph it can create a CephBrokerRq
291+# and send that request to ceph via the ceph relation. The CephBrokerRq has a
292+# unique id so that the client can identity which CephBrokerRsp is associated
293+# with the request. Ceph will also respond to each client unit individually
294+# creating a response key per client unit eg glance/0 will get a CephBrokerRsp
295+# via key broker-rsp-glance-0
296+#
297+# To use this the charm can just do something like:
298+#
299+# from charmhelpers.contrib.storage.linux.ceph import (
300+# send_request_if_needed,
301+# is_request_complete,
302+# CephBrokerRq,
303+# )
304+#
305+# @hooks.hook('ceph-relation-changed')
306+# def ceph_changed():
307+# rq = CephBrokerRq()
308+# rq.add_op_create_pool(name='poolname', replica_count=3)
309+#
310+# if is_request_complete(rq):
311+# <Request complete actions>
312+# else:
313+# send_request_if_needed(get_ceph_request())
314+#
315+# CephBrokerRq and CephBrokerRsp are serialized into JSON. Below is an example
316+# of glance having sent a request to ceph which ceph has successfully processed
317+# 'ceph:8': {
318+# 'ceph/0': {
319+# 'auth': 'cephx',
320+# 'broker-rsp-glance-0': '{"request-id": "0bc7dc54", "exit-code": 0}',
321+# 'broker_rsp': '{"request-id": "0da543b8", "exit-code": 0}',
322+# 'ceph-public-address': '10.5.44.103',
323+# 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
324+# 'private-address': '10.5.44.103',
325+# },
326+# 'glance/0': {
327+# 'broker_req': ('{"api-version": 1, "request-id": "0bc7dc54", '
328+# '"ops": [{"replicas": 3, "name": "glance", '
329+# '"op": "create-pool"}]}'),
330+# 'private-address': '10.5.44.109',
331+# },
332+# }
333+
334+def get_previous_request(rid):
335+ """Return the last ceph broker request sent on a given relation
336+
337+ @param rid: Relation id to query for request
338+ """
339+ request = None
340+ broker_req = relation_get(attribute='broker_req', rid=rid,
341+ unit=local_unit())
342+ if broker_req:
343+ request_data = json.loads(broker_req)
344+ request = CephBrokerRq(api_version=request_data['api-version'],
345+ request_id=request_data['request-id'])
346+ request.set_ops(request_data['ops'])
347+
348+ return request
349+
350+
351+def get_request_states(request):
352+ """Return a dict of requests per relation id with their corresponding
353+ completion state.
354+
355+ This allows a charm, which has a request for ceph, to see whether there is
356+ an equivalent request already being processed and if so what state that
357+ request is in.
358+
359+ @param request: A CephBrokerRq object
360+ """
361+ complete = []
362+ requests = {}
363+ for rid in relation_ids('ceph'):
364+ complete = False
365+ previous_request = get_previous_request(rid)
366+ if request == previous_request:
367+ sent = True
368+ complete = is_request_complete_for_rid(previous_request, rid)
369+ else:
370+ sent = False
371+ complete = False
372+
373+ requests[rid] = {
374+ 'sent': sent,
375+ 'complete': complete,
376+ }
377+
378+ return requests
379+
380+
381+def is_request_sent(request):
382+ """Check to see if a functionally equivalent request has already been sent
383+
384+ Returns True if a similair request has been sent
385+
386+ @param request: A CephBrokerRq object
387+ """
388+ states = get_request_states(request)
389+ for rid in states.keys():
390+ if not states[rid]['sent']:
391+ return False
392+
393+ return True
394+
395+
396+def is_request_complete(request):
397+ """Check to see if a functionally equivalent request has already been
398+ completed
399+
400+ Returns True if a similair request has been completed
401+
402+ @param request: A CephBrokerRq object
403+ """
404+ states = get_request_states(request)
405+ for rid in states.keys():
406+ if not states[rid]['complete']:
407+ return False
408+
409+ return True
410+
411+
412+def is_request_complete_for_rid(request, rid):
413+ """Check if a given request has been completed on the given relation
414+
415+ @param request: A CephBrokerRq object
416+ @param rid: Relation ID
417+ """
418+ broker_key = get_broker_rsp_key()
419+ for unit in related_units(rid):
420+ rdata = relation_get(rid=rid, unit=unit)
421+ if rdata.get(broker_key):
422+ rsp = CephBrokerRsp(rdata.get(broker_key))
423+ if rsp.request_id == request.request_id:
424+ if not rsp.exit_code:
425+ return True
426+ else:
427+ # The remote unit sent no reply targeted at this unit so either the
428+ # remote ceph cluster does not support unit targeted replies or it
429+ # has not processed our request yet.
430+ if rdata.get('broker_rsp'):
431+ request_data = json.loads(rdata['broker_rsp'])
432+ if request_data.get('request-id'):
433+ log('Ignoring legacy broker_rsp without unit key as remote '
434+ 'service supports unit specific replies', level=DEBUG)
435+ else:
436+ log('Using legacy broker_rsp as remote service does not '
437+ 'supports unit specific replies', level=DEBUG)
438+ rsp = CephBrokerRsp(rdata['broker_rsp'])
439+ if not rsp.exit_code:
440+ return True
441+
442+ return False
443+
444+
445+def get_broker_rsp_key():
446+ """Return broker response key for this unit
447+
448+ This is the key that ceph is going to use to pass request status
449+ information back to this unit
450+ """
451+ return 'broker-rsp-' + local_unit().replace('/', '-')
452+
453+
454+def send_request_if_needed(request):
455+ """Send broker request if an equivalent request has not already been sent
456+
457+ @param request: A CephBrokerRq object
458+ """
459+ if is_request_sent(request):
460+ log('Request already sent but not complete, not sending new request',
461+ level=DEBUG)
462+ else:
463+ for rid in relation_ids('ceph'):
464+ log('Sending request {}'.format(request.request_id), level=DEBUG)
465+ relation_set(relation_id=rid, broker_req=request.request)
466
467=== modified file 'hooks/charmhelpers/contrib/storage/linux/utils.py'
468--- hooks/charmhelpers/contrib/storage/linux/utils.py 2015-08-10 16:36:50 +0000
469+++ hooks/charmhelpers/contrib/storage/linux/utils.py 2015-09-21 07:13:28 +0000
470@@ -43,9 +43,10 @@
471
472 :param block_device: str: Full path of block device to clean.
473 '''
474+ # https://github.com/ceph/ceph/commit/fdd7f8d83afa25c4e09aaedd90ab93f3b64a677b
475 # sometimes sgdisk exits non-zero; this is OK, dd will clean up
476- call(['sgdisk', '--zap-all', '--mbrtogpt',
477- '--clear', block_device])
478+ call(['sgdisk', '--zap-all', '--', block_device])
479+ call(['sgdisk', '--clear', '--mbrtogpt', '--', block_device])
480 dev_end = check_output(['blockdev', '--getsz',
481 block_device]).decode('UTF-8')
482 gpt_end = int(dev_end.split()[0]) - 100
483
484=== modified file 'hooks/charmhelpers/core/hookenv.py'
485--- hooks/charmhelpers/core/hookenv.py 2015-08-10 16:36:50 +0000
486+++ hooks/charmhelpers/core/hookenv.py 2015-09-21 07:13:28 +0000
487@@ -34,23 +34,6 @@
488 import tempfile
489 from subprocess import CalledProcessError
490
491-try:
492- from charmhelpers.cli import cmdline
493-except ImportError as e:
494- # due to the anti-pattern of partially synching charmhelpers directly
495- # into charms, it's possible that charmhelpers.cli is not available;
496- # if that's the case, they don't really care about using the cli anyway,
497- # so mock it out
498- if str(e) == 'No module named cli':
499- class cmdline(object):
500- @classmethod
501- def subcommand(cls, *args, **kwargs):
502- def _wrap(func):
503- return func
504- return _wrap
505- else:
506- raise
507-
508 import six
509 if not six.PY3:
510 from UserDict import UserDict
511@@ -91,6 +74,7 @@
512 res = func(*args, **kwargs)
513 cache[key] = res
514 return res
515+ wrapper._wrapped = func
516 return wrapper
517
518
519@@ -190,7 +174,6 @@
520 return os.environ.get('JUJU_RELATION', None)
521
522
523-@cmdline.subcommand()
524 @cached
525 def relation_id(relation_name=None, service_or_unit=None):
526 """The relation ID for the current or a specified relation"""
527@@ -216,13 +199,11 @@
528 return os.environ.get('JUJU_REMOTE_UNIT', None)
529
530
531-@cmdline.subcommand()
532 def service_name():
533 """The name service group this unit belongs to"""
534 return local_unit().split('/')[0]
535
536
537-@cmdline.subcommand()
538 @cached
539 def remote_service_name(relid=None):
540 """The remote service name for a given relation-id (or the current relation)"""
541
542=== modified file 'hooks/charmhelpers/core/host.py'
543--- hooks/charmhelpers/core/host.py 2015-08-10 16:36:50 +0000
544+++ hooks/charmhelpers/core/host.py 2015-09-21 07:13:28 +0000
545@@ -72,7 +72,7 @@
546 stopped = service_stop(service_name)
547 # XXX: Support systemd too
548 override_path = os.path.join(
549- init_dir, '{}.conf.override'.format(service_name))
550+ init_dir, '{}.override'.format(service_name))
551 with open(override_path, 'w') as fh:
552 fh.write("manual\n")
553 return stopped
554@@ -86,7 +86,7 @@
555 if init_dir is None:
556 init_dir = "/etc/init"
557 override_path = os.path.join(
558- init_dir, '{}.conf.override'.format(service_name))
559+ init_dir, '{}.override'.format(service_name))
560 if os.path.exists(override_path):
561 os.unlink(override_path)
562 started = service_start(service_name)

Subscribers

People subscribed via source and target branches