Merge lp:~gnuoy/charms/trusty/ceph/1453940-stable into lp:~openstack-charmers-archive/charms/trusty/ceph/trunk

Proposed by Liam Young
Status: Merged
Merged at revision: 110
Proposed branch: lp:~gnuoy/charms/trusty/ceph/1453940-stable
Merge into: lp:~openstack-charmers-archive/charms/trusty/ceph/trunk
Diff against target: 487 lines (+278/-38)
8 files modified
hooks/ceph_broker.py (+12/-2)
hooks/charmhelpers/cli/__init__.py (+1/-5)
hooks/charmhelpers/cli/commands.py (+4/-4)
hooks/charmhelpers/cli/hookenv.py (+23/-0)
hooks/charmhelpers/contrib/storage/linux/ceph.py (+224/-2)
hooks/charmhelpers/core/hookenv.py (+1/-20)
hooks/charmhelpers/core/host.py (+2/-2)
hooks/hooks.py (+11/-3)
To merge this branch: bzr merge lp:~gnuoy/charms/trusty/ceph/1453940-stable
Reviewer Review Type Date Requested Status
Chris Glass (community) Approve
Review via email: mp+271260@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 #10408 ceph for gnuoy mp271260
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/10408/

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

charm_unit_test #9558 ceph for gnuoy mp271260
    UNIT OK: passed

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

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

charm_amulet_test #6552 ceph for gnuoy mp271260
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6552/

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

charm_lint_check #10438 ceph for gnuoy mp271260
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/10438/

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

charm_unit_test #9630 ceph for gnuoy mp271260
    UNIT OK: passed

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

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

charm_amulet_test #6579 ceph for gnuoy mp271260
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/6579/

Revision history for this message
Chris Glass (tribaal) wrote :

Looks good! +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'hooks/ceph_broker.py'
--- hooks/ceph_broker.py 2014-11-19 21:33:12 +0000
+++ hooks/ceph_broker.py 2015-09-16 09:02:26 +0000
@@ -31,10 +31,16 @@
31 This is a versioned api. API version must be supplied by the client making31 This is a versioned api. API version must be supplied by the client making
32 the request.32 the request.
33 """33 """
34 request_id = reqs.get('request-id')
34 try:35 try:
35 version = reqs.get('api-version')36 version = reqs.get('api-version')
36 if version == 1:37 if version == 1:
37 return process_requests_v1(reqs['ops'])38 log('Processing request {}'.format(request_id), level=DEBUG)
39 resp = process_requests_v1(reqs['ops'])
40 if request_id:
41 resp['request-id'] = request_id
42
43 return resp
3844
39 except Exception as exc:45 except Exception as exc:
40 log(str(exc), level=ERROR)46 log(str(exc), level=ERROR)
@@ -44,7 +50,11 @@
44 return {'exit-code': 1, 'stderr': msg}50 return {'exit-code': 1, 'stderr': msg}
4551
46 msg = ("Missing or invalid api version (%s)" % (version))52 msg = ("Missing or invalid api version (%s)" % (version))
47 return {'exit-code': 1, 'stderr': msg}53 resp = {'exit-code': 1, 'stderr': msg}
54 if request_id:
55 resp['request-id'] = request_id
56
57 return resp
4858
4959
50def process_requests_v1(reqs):60def process_requests_v1(reqs):
5161
=== modified file 'hooks/charmhelpers/cli/__init__.py'
--- hooks/charmhelpers/cli/__init__.py 2015-08-10 16:32:52 +0000
+++ hooks/charmhelpers/cli/__init__.py 2015-09-16 09:02:26 +0000
@@ -152,15 +152,11 @@
152 arguments = self.argument_parser.parse_args()152 arguments = self.argument_parser.parse_args()
153 argspec = inspect.getargspec(arguments.func)153 argspec = inspect.getargspec(arguments.func)
154 vargs = []154 vargs = []
155 kwargs = {}
156 for arg in argspec.args:155 for arg in argspec.args:
157 vargs.append(getattr(arguments, arg))156 vargs.append(getattr(arguments, arg))
158 if argspec.varargs:157 if argspec.varargs:
159 vargs.extend(getattr(arguments, argspec.varargs))158 vargs.extend(getattr(arguments, argspec.varargs))
160 if argspec.keywords:159 output = arguments.func(*vargs)
161 for kwarg in argspec.keywords.items():
162 kwargs[kwarg] = getattr(arguments, kwarg)
163 output = arguments.func(*vargs, **kwargs)
164 if getattr(arguments.func, '_cli_test_command', False):160 if getattr(arguments.func, '_cli_test_command', False):
165 self.exit_code = 0 if output else 1161 self.exit_code = 0 if output else 1
166 output = ''162 output = ''
167163
=== modified file 'hooks/charmhelpers/cli/commands.py'
--- hooks/charmhelpers/cli/commands.py 2015-08-10 16:32:52 +0000
+++ hooks/charmhelpers/cli/commands.py 2015-09-16 09:02:26 +0000
@@ -26,7 +26,7 @@
26"""26"""
27Import the sub-modules which have decorated subcommands to register with chlp.27Import the sub-modules which have decorated subcommands to register with chlp.
28"""28"""
29import host # noqa29from . import host # noqa
30import benchmark # noqa30from . import benchmark # noqa
31import unitdata # noqa31from . import unitdata # noqa
32from charmhelpers.core import hookenv # noqa32from . import hookenv # noqa
3333
=== added file 'hooks/charmhelpers/cli/hookenv.py'
--- hooks/charmhelpers/cli/hookenv.py 1970-01-01 00:00:00 +0000
+++ hooks/charmhelpers/cli/hookenv.py 2015-09-16 09:02:26 +0000
@@ -0,0 +1,23 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# This file is part of charm-helpers.
4#
5# charm-helpers is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# charm-helpers is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Lesser General Public License for more details.
13#
14# 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/>.
16
17from . import cmdline
18from charmhelpers.core import hookenv
19
20
21cmdline.subcommand('relation-id')(hookenv.relation_id._wrapped)
22cmdline.subcommand('service-name')(hookenv.service_name)
23cmdline.subcommand('remote-service-name')(hookenv.remote_service_name._wrapped)
024
=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-08-10 16:32:52 +0000
+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2015-09-16 09:02:26 +0000
@@ -28,6 +28,7 @@
28import shutil28import shutil
29import json29import json
30import time30import time
31import uuid
3132
32from subprocess import (33from subprocess import (
33 check_call,34 check_call,
@@ -35,8 +36,10 @@
35 CalledProcessError,36 CalledProcessError,
36)37)
37from charmhelpers.core.hookenv import (38from charmhelpers.core.hookenv import (
39 local_unit,
38 relation_get,40 relation_get,
39 relation_ids,41 relation_ids,
42 relation_set,
40 related_units,43 related_units,
41 log,44 log,
42 DEBUG,45 DEBUG,
@@ -411,17 +414,52 @@
411414
412 The API is versioned and defaults to version 1.415 The API is versioned and defaults to version 1.
413 """416 """
414 def __init__(self, api_version=1):417 def __init__(self, api_version=1, request_id=None):
415 self.api_version = api_version418 self.api_version = api_version
419 if request_id:
420 self.request_id = request_id
421 else:
422 self.request_id = str(uuid.uuid1())
416 self.ops = []423 self.ops = []
417424
418 def add_op_create_pool(self, name, replica_count=3):425 def add_op_create_pool(self, name, replica_count=3):
419 self.ops.append({'op': 'create-pool', 'name': name,426 self.ops.append({'op': 'create-pool', 'name': name,
420 'replicas': replica_count})427 'replicas': replica_count})
421428
429 def set_ops(self, ops):
430 """Set request ops to provided value.
431
432 Useful for injecting ops that come from a previous request
433 to allow comparisons to ensure validity.
434 """
435 self.ops = ops
436
422 @property437 @property
423 def request(self):438 def request(self):
424 return json.dumps({'api-version': self.api_version, 'ops': self.ops})439 return json.dumps({'api-version': self.api_version, 'ops': self.ops,
440 'request-id': self.request_id})
441
442 def _ops_equal(self, other):
443 if len(self.ops) == len(other.ops):
444 for req_no in range(0, len(self.ops)):
445 for key in ['replicas', 'name', 'op']:
446 if self.ops[req_no][key] != other.ops[req_no][key]:
447 return False
448 else:
449 return False
450 return True
451
452 def __eq__(self, other):
453 if not isinstance(other, self.__class__):
454 return False
455 if self.api_version == other.api_version and \
456 self._ops_equal(other):
457 return True
458 else:
459 return False
460
461 def __ne__(self, other):
462 return not self.__eq__(other)
425463
426464
427class CephBrokerRsp(object):465class CephBrokerRsp(object):
@@ -431,14 +469,198 @@
431469
432 The API is versioned and defaults to version 1.470 The API is versioned and defaults to version 1.
433 """471 """
472
434 def __init__(self, encoded_rsp):473 def __init__(self, encoded_rsp):
435 self.api_version = None474 self.api_version = None
436 self.rsp = json.loads(encoded_rsp)475 self.rsp = json.loads(encoded_rsp)
437476
438 @property477 @property
478 def request_id(self):
479 return self.rsp.get('request-id')
480
481 @property
439 def exit_code(self):482 def exit_code(self):
440 return self.rsp.get('exit-code')483 return self.rsp.get('exit-code')
441484
442 @property485 @property
443 def exit_msg(self):486 def exit_msg(self):
444 return self.rsp.get('stderr')487 return self.rsp.get('stderr')
488
489
490# Ceph Broker Conversation:
491# If a charm needs an action to be taken by ceph it can create a CephBrokerRq
492# and send that request to ceph via the ceph relation. The CephBrokerRq has a
493# unique id so that the client can identity which CephBrokerRsp is associated
494# with the request. Ceph will also respond to each client unit individually
495# creating a response key per client unit eg glance/0 will get a CephBrokerRsp
496# via key broker-rsp-glance-0
497#
498# To use this the charm can just do something like:
499#
500# from charmhelpers.contrib.storage.linux.ceph import (
501# send_request_if_needed,
502# is_request_complete,
503# CephBrokerRq,
504# )
505#
506# @hooks.hook('ceph-relation-changed')
507# def ceph_changed():
508# rq = CephBrokerRq()
509# rq.add_op_create_pool(name='poolname', replica_count=3)
510#
511# if is_request_complete(rq):
512# <Request complete actions>
513# else:
514# send_request_if_needed(get_ceph_request())
515#
516# CephBrokerRq and CephBrokerRsp are serialized into JSON. Below is an example
517# of glance having sent a request to ceph which ceph has successfully processed
518# 'ceph:8': {
519# 'ceph/0': {
520# 'auth': 'cephx',
521# 'broker-rsp-glance-0': '{"request-id": "0bc7dc54", "exit-code": 0}',
522# 'broker_rsp': '{"request-id": "0da543b8", "exit-code": 0}',
523# 'ceph-public-address': '10.5.44.103',
524# 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
525# 'private-address': '10.5.44.103',
526# },
527# 'glance/0': {
528# 'broker_req': ('{"api-version": 1, "request-id": "0bc7dc54", '
529# '"ops": [{"replicas": 3, "name": "glance", '
530# '"op": "create-pool"}]}'),
531# 'private-address': '10.5.44.109',
532# },
533# }
534
535def get_previous_request(rid):
536 """Return the last ceph broker request sent on a given relation
537
538 @param rid: Relation id to query for request
539 """
540 request = None
541 broker_req = relation_get(attribute='broker_req', rid=rid,
542 unit=local_unit())
543 if broker_req:
544 request_data = json.loads(broker_req)
545 request = CephBrokerRq(api_version=request_data['api-version'],
546 request_id=request_data['request-id'])
547 request.set_ops(request_data['ops'])
548
549 return request
550
551
552def get_request_states(request):
553 """Return a dict of requests per relation id with their corresponding
554 completion state.
555
556 This allows a charm, which has a request for ceph, to see whether there is
557 an equivalent request already being processed and if so what state that
558 request is in.
559
560 @param request: A CephBrokerRq object
561 """
562 complete = []
563 requests = {}
564 for rid in relation_ids('ceph'):
565 complete = False
566 previous_request = get_previous_request(rid)
567 if request == previous_request:
568 sent = True
569 complete = is_request_complete_for_rid(previous_request, rid)
570 else:
571 sent = False
572 complete = False
573
574 requests[rid] = {
575 'sent': sent,
576 'complete': complete,
577 }
578
579 return requests
580
581
582def is_request_sent(request):
583 """Check to see if a functionally equivalent request has already been sent
584
585 Returns True if a similair request has been sent
586
587 @param request: A CephBrokerRq object
588 """
589 states = get_request_states(request)
590 for rid in states.keys():
591 if not states[rid]['sent']:
592 return False
593
594 return True
595
596
597def is_request_complete(request):
598 """Check to see if a functionally equivalent request has already been
599 completed
600
601 Returns True if a similair request has been completed
602
603 @param request: A CephBrokerRq object
604 """
605 states = get_request_states(request)
606 for rid in states.keys():
607 if not states[rid]['complete']:
608 return False
609
610 return True
611
612
613def is_request_complete_for_rid(request, rid):
614 """Check if a given request has been completed on the given relation
615
616 @param request: A CephBrokerRq object
617 @param rid: Relation ID
618 """
619 broker_key = get_broker_rsp_key()
620 for unit in related_units(rid):
621 rdata = relation_get(rid=rid, unit=unit)
622 if rdata.get(broker_key):
623 rsp = CephBrokerRsp(rdata.get(broker_key))
624 if rsp.request_id == request.request_id:
625 if not rsp.exit_code:
626 return True
627 else:
628 # The remote unit sent no reply targeted at this unit so either the
629 # remote ceph cluster does not support unit targeted replies or it
630 # has not processed our request yet.
631 if rdata.get('broker_rsp'):
632 request_data = json.loads(rdata['broker_rsp'])
633 if request_data.get('request-id'):
634 log('Ignoring legacy broker_rsp without unit key as remote '
635 'service supports unit specific replies', level=DEBUG)
636 else:
637 log('Using legacy broker_rsp as remote service does not '
638 'supports unit specific replies', level=DEBUG)
639 rsp = CephBrokerRsp(rdata['broker_rsp'])
640 if not rsp.exit_code:
641 return True
642
643 return False
644
645
646def get_broker_rsp_key():
647 """Return broker response key for this unit
648
649 This is the key that ceph is going to use to pass request status
650 information back to this unit
651 """
652 return 'broker-rsp-' + local_unit().replace('/', '-')
653
654
655def send_request_if_needed(request):
656 """Send broker request if an equivalent request has not already been sent
657
658 @param request: A CephBrokerRq object
659 """
660 if is_request_sent(request):
661 log('Request already sent but not complete, not sending new request',
662 level=DEBUG)
663 else:
664 for rid in relation_ids('ceph'):
665 log('Sending request {}'.format(request.request_id), level=DEBUG)
666 relation_set(relation_id=rid, broker_req=request.request)
445667
=== modified file 'hooks/charmhelpers/core/hookenv.py'
--- hooks/charmhelpers/core/hookenv.py 2015-08-10 16:32:52 +0000
+++ hooks/charmhelpers/core/hookenv.py 2015-09-16 09:02:26 +0000
@@ -34,23 +34,6 @@
34import tempfile34import tempfile
35from subprocess import CalledProcessError35from subprocess import CalledProcessError
3636
37try:
38 from charmhelpers.cli import cmdline
39except ImportError as e:
40 # due to the anti-pattern of partially synching charmhelpers directly
41 # into charms, it's possible that charmhelpers.cli is not available;
42 # if that's the case, they don't really care about using the cli anyway,
43 # so mock it out
44 if str(e) == 'No module named cli':
45 class cmdline(object):
46 @classmethod
47 def subcommand(cls, *args, **kwargs):
48 def _wrap(func):
49 return func
50 return _wrap
51 else:
52 raise
53
54import six37import six
55if not six.PY3:38if not six.PY3:
56 from UserDict import UserDict39 from UserDict import UserDict
@@ -91,6 +74,7 @@
91 res = func(*args, **kwargs)74 res = func(*args, **kwargs)
92 cache[key] = res75 cache[key] = res
93 return res76 return res
77 wrapper._wrapped = func
94 return wrapper78 return wrapper
9579
9680
@@ -190,7 +174,6 @@
190 return os.environ.get('JUJU_RELATION', None)174 return os.environ.get('JUJU_RELATION', None)
191175
192176
193@cmdline.subcommand()
194@cached177@cached
195def relation_id(relation_name=None, service_or_unit=None):178def relation_id(relation_name=None, service_or_unit=None):
196 """The relation ID for the current or a specified relation"""179 """The relation ID for the current or a specified relation"""
@@ -216,13 +199,11 @@
216 return os.environ.get('JUJU_REMOTE_UNIT', None)199 return os.environ.get('JUJU_REMOTE_UNIT', None)
217200
218201
219@cmdline.subcommand()
220def service_name():202def service_name():
221 """The name service group this unit belongs to"""203 """The name service group this unit belongs to"""
222 return local_unit().split('/')[0]204 return local_unit().split('/')[0]
223205
224206
225@cmdline.subcommand()
226@cached207@cached
227def remote_service_name(relid=None):208def remote_service_name(relid=None):
228 """The remote service name for a given relation-id (or the current relation)"""209 """The remote service name for a given relation-id (or the current relation)"""
229210
=== modified file 'hooks/charmhelpers/core/host.py'
--- hooks/charmhelpers/core/host.py 2015-08-10 16:32:52 +0000
+++ hooks/charmhelpers/core/host.py 2015-09-16 09:02:26 +0000
@@ -72,7 +72,7 @@
72 stopped = service_stop(service_name)72 stopped = service_stop(service_name)
73 # XXX: Support systemd too73 # XXX: Support systemd too
74 override_path = os.path.join(74 override_path = os.path.join(
75 init_dir, '{}.conf.override'.format(service_name))75 init_dir, '{}.override'.format(service_name))
76 with open(override_path, 'w') as fh:76 with open(override_path, 'w') as fh:
77 fh.write("manual\n")77 fh.write("manual\n")
78 return stopped78 return stopped
@@ -86,7 +86,7 @@
86 if init_dir is None:86 if init_dir is None:
87 init_dir = "/etc/init"87 init_dir = "/etc/init"
88 override_path = os.path.join(88 override_path = os.path.join(
89 init_dir, '{}.conf.override'.format(service_name))89 init_dir, '{}.override'.format(service_name))
90 if os.path.exists(override_path):90 if os.path.exists(override_path):
91 os.unlink(override_path)91 os.unlink(override_path)
92 started = service_start(service_name)92 started = service_start(service_name)
9393
=== modified file 'hooks/hooks.py'
--- hooks/hooks.py 2015-03-23 17:40:42 +0000
+++ hooks/hooks.py 2015-09-16 09:02:26 +0000
@@ -144,8 +144,8 @@
144 umount(e_mountpoint)144 umount(e_mountpoint)
145145
146 osd_journal = config('osd-journal')146 osd_journal = config('osd-journal')
147 if (osd_journal and not os.path.exists(JOURNAL_ZAPPED)147 if (osd_journal and not os.path.exists(JOURNAL_ZAPPED) and
148 and os.path.exists(osd_journal)):148 os.path.exists(osd_journal)):
149 ceph.zap_disk(osd_journal)149 ceph.zap_disk(osd_journal)
150 with open(JOURNAL_ZAPPED, 'w') as zapped:150 with open(JOURNAL_ZAPPED, 'w') as zapped:
151 zapped.write('DONE')151 zapped.write('DONE')
@@ -319,7 +319,15 @@
319 log("Not leader - ignoring broker request", level=DEBUG)319 log("Not leader - ignoring broker request", level=DEBUG)
320 else:320 else:
321 rsp = process_requests(settings['broker_req'])321 rsp = process_requests(settings['broker_req'])
322 relation_set(relation_settings={'broker_rsp': rsp})322 unit_id = remote_unit().replace('/', '-')
323 unit_response_key = 'broker-rsp-' + unit_id
324 # broker_rsp is being left for backward compatibility,
325 # unit_response_key superscedes it
326 data = {
327 'broker_rsp': rsp,
328 unit_response_key: rsp,
329 }
330 relation_set(relation_settings=data)
323 else:331 else:
324 log('mon cluster not in quorum', level=DEBUG)332 log('mon cluster not in quorum', level=DEBUG)
325333

Subscribers

People subscribed via source and target branches