Merge lp:~gnuoy/charm-helpers/cepg-broker into lp:charm-helpers

Proposed by Liam Young
Status: Merged
Merged at revision: 447
Proposed branch: lp:~gnuoy/charm-helpers/cepg-broker
Merge into: lp:charm-helpers
Diff against target: 812 lines (+612/-30)
5 files modified
charmhelpers/contrib/openstack/context.py (+8/-9)
charmhelpers/contrib/storage/linux/ceph.py (+224/-2)
tests/contrib/openstack/test_os_contexts.py (+36/-4)
tests/contrib/storage/test_linux_ceph.py (+337/-8)
tests/helpers.py (+7/-7)
To merge this branch: bzr merge lp:~gnuoy/charm-helpers/cepg-broker
Reviewer Review Type Date Requested Status
Edward Hope-Morley Approve
Chris Glass (community) Approve
Review via email: mp+268591@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Chris Glass (tribaal) wrote :

Hi, thanks for your proposal.

The code looks fine in general, but it would really help future readers if you could expand the docstrings a little bit to explain what the problem they are trying to solve is. I understand it now, after some effort, while the bug and context is still fresh, but I (and others) certainly won't in a few months if coming back to it is needed.

An extra bit at the beginning of the file or outside of a particular function about how the newly introduced functions are supposed to be called by whom and in what order would also help tremendously.

I know it seems verbose right now, but it will really save a lot of time if in the future somebody has to look at this code again.

Suggestions inline.

review: Needs Information
Revision history for this message
Edward Hope-Morley (hopem) wrote :

In general I think this is good but please see comments in https://code.launchpad.net/~gnuoy/charms/trusty/ceph/1453940/+merge/268614

Can you please make log lvels specific. Right now they are all default i.e. INFO but mpost should be DEBUG.

Revision history for this message
Edward Hope-Morley (hopem) wrote :

Still reviewing but in general I prefer this approach. 1 inline comment.

lp:~gnuoy/charm-helpers/cepg-broker updated
443. By Liam Young

Change how ops injection works in light of review comments

444. By Liam Young

Some tidyup

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

Looks good now after the cleanups and additions, thanks a lot. +1

review: Approve
Revision history for this message
Edward Hope-Morley (hopem) wrote :

LGTM +1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/openstack/context.py'
2--- charmhelpers/contrib/openstack/context.py 2015-09-02 14:09:43 +0000
3+++ charmhelpers/contrib/openstack/context.py 2015-09-10 09:26:02 +0000
4@@ -485,13 +485,15 @@
5
6 log('Generating template context for ceph', level=DEBUG)
7 mon_hosts = []
8- auth = None
9- key = None
10- use_syslog = str(config('use-syslog')).lower()
11+ ctxt = {
12+ 'use_syslog': str(config('use-syslog')).lower()
13+ }
14 for rid in relation_ids('ceph'):
15 for unit in related_units(rid):
16- auth = relation_get('auth', rid=rid, unit=unit)
17- key = relation_get('key', rid=rid, unit=unit)
18+ if not ctxt.get('auth'):
19+ ctxt['auth'] = relation_get('auth', rid=rid, unit=unit)
20+ if not ctxt.get('key'):
21+ ctxt['key'] = relation_get('key', rid=rid, unit=unit)
22 ceph_pub_addr = relation_get('ceph-public-address', rid=rid,
23 unit=unit)
24 unit_priv_addr = relation_get('private-address', rid=rid,
25@@ -500,10 +502,7 @@
26 ceph_addr = format_ipv6_addr(ceph_addr) or ceph_addr
27 mon_hosts.append(ceph_addr)
28
29- ctxt = {'mon_hosts': ' '.join(sorted(mon_hosts)),
30- 'auth': auth,
31- 'key': key,
32- 'use_syslog': use_syslog}
33+ ctxt['mon_hosts'] = ' '.join(sorted(mon_hosts))
34
35 if not os.path.isdir('/etc/ceph'):
36 os.mkdir('/etc/ceph')
37
38=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
39--- charmhelpers/contrib/storage/linux/ceph.py 2015-09-03 14:22:08 +0000
40+++ charmhelpers/contrib/storage/linux/ceph.py 2015-09-10 09:26:02 +0000
41@@ -28,6 +28,7 @@
42 import shutil
43 import json
44 import time
45+import uuid
46
47 from subprocess import (
48 check_call,
49@@ -35,8 +36,10 @@
50 CalledProcessError,
51 )
52 from charmhelpers.core.hookenv import (
53+ local_unit,
54 relation_get,
55 relation_ids,
56+ relation_set,
57 related_units,
58 log,
59 DEBUG,
60@@ -402,17 +405,52 @@
61
62 The API is versioned and defaults to version 1.
63 """
64- def __init__(self, api_version=1):
65+ def __init__(self, api_version=1, request_id=None):
66 self.api_version = api_version
67+ if request_id:
68+ self.request_id = request_id
69+ else:
70+ self.request_id = str(uuid.uuid1())
71 self.ops = []
72
73 def add_op_create_pool(self, name, replica_count=3):
74 self.ops.append({'op': 'create-pool', 'name': name,
75 'replicas': replica_count})
76
77+ def set_ops(self, ops):
78+ """Set request ops to provided value.
79+
80+ Useful for injecting ops that come from a previous request
81+ to allow comparisons to ensure validity.
82+ """
83+ self.ops = ops
84+
85 @property
86 def request(self):
87- return json.dumps({'api-version': self.api_version, 'ops': self.ops})
88+ return json.dumps({'api-version': self.api_version, 'ops': self.ops,
89+ 'request-id': self.request_id})
90+
91+ def _ops_equal(self, other):
92+ if len(self.ops) == len(other.ops):
93+ for req_no in range(0, len(self.ops)):
94+ for key in ['replicas', 'name', 'op']:
95+ if self.ops[req_no][key] != other.ops[req_no][key]:
96+ return False
97+ else:
98+ return False
99+ return True
100+
101+ def __eq__(self, other):
102+ if not isinstance(other, self.__class__):
103+ return False
104+ if self.api_version == other.api_version and \
105+ self._ops_equal(other):
106+ return True
107+ else:
108+ return False
109+
110+ def __ne__(self, other):
111+ return not self.__eq__(other)
112
113
114 class CephBrokerRsp(object):
115@@ -422,14 +460,198 @@
116
117 The API is versioned and defaults to version 1.
118 """
119+
120 def __init__(self, encoded_rsp):
121 self.api_version = None
122 self.rsp = json.loads(encoded_rsp)
123
124 @property
125+ def request_id(self):
126+ return self.rsp.get('request-id')
127+
128+ @property
129 def exit_code(self):
130 return self.rsp.get('exit-code')
131
132 @property
133 def exit_msg(self):
134 return self.rsp.get('stderr')
135+
136+
137+# Ceph Broker Conversation:
138+# If a charm needs an action to be taken by ceph it can create a CephBrokerRq
139+# and send that request to ceph via the ceph relation. The CephBrokerRq has a
140+# unique id so that the client can identity which CephBrokerRsp is associated
141+# with the request. Ceph will also respond to each client unit individually
142+# creating a response key per client unit eg glance/0 will get a CephBrokerRsp
143+# via key broker-rsp-glance-0
144+#
145+# To use this the charm can just do something like:
146+#
147+# from charmhelpers.contrib.storage.linux.ceph import (
148+# send_request_if_needed,
149+# is_request_complete,
150+# CephBrokerRq,
151+# )
152+#
153+# @hooks.hook('ceph-relation-changed')
154+# def ceph_changed():
155+# rq = CephBrokerRq()
156+# rq.add_op_create_pool(name='poolname', replica_count=3)
157+#
158+# if is_request_complete(rq):
159+# <Request complete actions>
160+# else:
161+# send_request_if_needed(get_ceph_request())
162+#
163+# CephBrokerRq and CephBrokerRsp are serialized into JSON. Below is an example
164+# of glance having sent a request to ceph which ceph has successfully processed
165+# 'ceph:8': {
166+# 'ceph/0': {
167+# 'auth': 'cephx',
168+# 'broker-rsp-glance-0': '{"request-id": "0bc7dc54", "exit-code": 0}',
169+# 'broker_rsp': '{"request-id": "0da543b8", "exit-code": 0}',
170+# 'ceph-public-address': '10.5.44.103',
171+# 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
172+# 'private-address': '10.5.44.103',
173+# },
174+# 'glance/0': {
175+# 'broker_req': ('{"api-version": 1, "request-id": "0bc7dc54", '
176+# '"ops": [{"replicas": 3, "name": "glance", '
177+# '"op": "create-pool"}]}'),
178+# 'private-address': '10.5.44.109',
179+# },
180+# }
181+
182+def get_previous_request(rid):
183+ """Return the last ceph broker request sent on a given relation
184+
185+ @param rid: Relation id to query for request
186+ """
187+ request = None
188+ broker_req = relation_get(attribute='broker_req', rid=rid,
189+ unit=local_unit())
190+ if broker_req:
191+ request_data = json.loads(broker_req)
192+ request = CephBrokerRq(api_version=request_data['api-version'],
193+ request_id=request_data['request-id'])
194+ request.set_ops(request_data['ops'])
195+
196+ return request
197+
198+
199+def get_request_states(request):
200+ """Return a dict of requests per relation id with their corresponding
201+ completion state.
202+
203+ This allows a charm, which has a request for ceph, to see whether there is
204+ an equivalent request already being processed and if so what state that
205+ request is in.
206+
207+ @param request: A CephBrokerRq object
208+ """
209+ complete = []
210+ requests = {}
211+ for rid in relation_ids('ceph'):
212+ complete = False
213+ previous_request = get_previous_request(rid)
214+ if request == previous_request:
215+ sent = True
216+ complete = is_request_complete_for_rid(previous_request, rid)
217+ else:
218+ sent = False
219+ complete = False
220+
221+ requests[rid] = {
222+ 'sent': sent,
223+ 'complete': complete,
224+ }
225+
226+ return requests
227+
228+
229+def is_request_sent(request):
230+ """Check to see if a functionally equivalent request has already been sent
231+
232+ Returns True if a similair request has been sent
233+
234+ @param request: A CephBrokerRq object
235+ """
236+ states = get_request_states(request)
237+ for rid in states.keys():
238+ if not states[rid]['sent']:
239+ return False
240+
241+ return True
242+
243+
244+def is_request_complete(request):
245+ """Check to see if a functionally equivalent request has already been
246+ completed
247+
248+ Returns True if a similair request has been completed
249+
250+ @param request: A CephBrokerRq object
251+ """
252+ states = get_request_states(request)
253+ for rid in states.keys():
254+ if not states[rid]['complete']:
255+ return False
256+
257+ return True
258+
259+
260+def is_request_complete_for_rid(request, rid):
261+ """Check if a given request has been completed on the given relation
262+
263+ @param request: A CephBrokerRq object
264+ @param rid: Relation ID
265+ """
266+ broker_key = get_broker_rsp_key()
267+ for unit in related_units(rid):
268+ rdata = relation_get(rid=rid, unit=unit)
269+ if rdata.get(broker_key):
270+ rsp = CephBrokerRsp(rdata.get(broker_key))
271+ if rsp.request_id == request.request_id:
272+ if not rsp.exit_code:
273+ return True
274+ else:
275+ # The remote unit sent no reply targeted at this unit so either the
276+ # remote ceph cluster does not support unit targeted replies or it
277+ # has not processed our request yet.
278+ if rdata.get('broker_rsp'):
279+ request_data = json.loads(rdata['broker_rsp'])
280+ if request_data.get('request-id'):
281+ log('Ignoring legacy broker_rsp without unit key as remote '
282+ 'service supports unit specific replies', level=DEBUG)
283+ else:
284+ log('Using legacy broker_rsp as remote service does not '
285+ 'supports unit specific replies', level=DEBUG)
286+ rsp = CephBrokerRsp(rdata['broker_rsp'])
287+ if not rsp.exit_code:
288+ return True
289+
290+ return False
291+
292+
293+def get_broker_rsp_key():
294+ """Return broker response key for this unit
295+
296+ This is the key that ceph is going to use to pass request status
297+ information back to this unit
298+ """
299+ return 'broker-rsp-' + local_unit().replace('/', '-')
300+
301+
302+def send_request_if_needed(request):
303+ """Send broker request if an equivalent request has not already been sent
304+
305+ @param request: A CephBrokerRq object
306+ """
307+ if is_request_sent(request):
308+ log('Request already sent but not complete, not sending new request',
309+ level=DEBUG)
310+ else:
311+ for rid in relation_ids('ceph'):
312+ log('Sending request {}'.format(request.request_id), level=DEBUG)
313+ relation_set(relation_id=rid, broker_req=request.request)
314
315=== modified file 'tests/contrib/openstack/test_os_contexts.py'
316--- tests/contrib/openstack/test_os_contexts.py 2015-09-02 14:09:43 +0000
317+++ tests/contrib/openstack/test_os_contexts.py 2015-09-10 09:26:02 +0000
318@@ -74,7 +74,7 @@
319
320 def relation_ids(self, relation):
321 rids = []
322- for rid in self.relation_data.keys():
323+ for rid in sorted(self.relation_data.keys()):
324 if relation + ':' in rid:
325 rids.append(rid)
326 return rids
327@@ -82,7 +82,7 @@
328 def relation_units(self, relation_id):
329 if relation_id not in self.relation_data:
330 return None
331- return self.relation_data[relation_id].keys()
332+ return sorted(self.relation_data[relation_id].keys())
333
334 SHARED_DB_RELATION = {
335 'db_host': 'dbserver.local',
336@@ -1029,7 +1029,7 @@
337 def test_ceph_context_with_data(self, ensure_packages, mkdir, isdir,
338 config):
339 '''Test ceph context with all relation data'''
340- config.return_value = True
341+ config.side_effect = fake_config({'use-syslog': 'True'})
342 isdir.return_value = False
343 relation = FakeRelation(relation_data=CEPH_RELATION)
344 self.relation_get.side_effect = relation.get
345@@ -1051,7 +1051,7 @@
346 @patch.object(context, 'ensure_packages')
347 def test_ceph_context_with_missing_data(self, ensure_packages, mkdir):
348 '''Test ceph context with missing relation data'''
349- relation = copy(CEPH_RELATION)
350+ relation = deepcopy(CEPH_RELATION)
351 for k, v in six.iteritems(relation):
352 for u in six.iterkeys(v):
353 del relation[k][u]['auth']
354@@ -1068,6 +1068,38 @@
355 @patch('os.path.isdir')
356 @patch('os.mkdir')
357 @patch.object(context, 'ensure_packages')
358+ def test_ceph_context_partial_missing_data(self, ensure_packages, mkdir,
359+ isdir, config):
360+ '''Test ceph context last unit missing data
361+
362+ Tests a fix to a previously bug which meant only the config from
363+ last unit was returned so if a valid value was supplied from an
364+ earlier unit it would be ignored'''
365+ config.side_effect = fake_config({'use-syslog': 'True'})
366+ relation = deepcopy(CEPH_RELATION)
367+ for k, v in six.iteritems(relation):
368+ last_unit = sorted(six.iterkeys(v))[-1]
369+ unit_data = relation[k][last_unit]
370+ del unit_data['auth']
371+ relation[k][last_unit] = unit_data
372+ relation = FakeRelation(relation_data=relation)
373+ self.relation_get.side_effect = relation.get
374+ self.relation_ids.side_effect = relation.relation_ids
375+ self.related_units.side_effect = relation.relation_units
376+ ceph = context.CephContext()
377+ result = ceph()
378+ expected = {
379+ 'mon_hosts': 'ceph_node1 ceph_node2',
380+ 'auth': 'foo',
381+ 'key': 'bar',
382+ 'use_syslog': 'true'
383+ }
384+ self.assertEquals(result, expected)
385+
386+ @patch.object(context, 'config')
387+ @patch('os.path.isdir')
388+ @patch('os.mkdir')
389+ @patch.object(context, 'ensure_packages')
390 def test_ceph_context_with_public_addr(
391 self, ensure_packages, mkdir, isdir, config):
392 '''Test ceph context in host with multiple networks with all
393
394=== modified file 'tests/contrib/storage/test_linux_ceph.py'
395--- tests/contrib/storage/test_linux_ceph.py 2015-09-03 14:22:08 +0000
396+++ tests/contrib/storage/test_linux_ceph.py 2015-09-10 09:26:02 +0000
397@@ -5,10 +5,11 @@
398 from threading import Timer
399 from testtools import TestCase
400 import json
401+import copy
402
403 import charmhelpers.contrib.storage.linux.ceph as ceph_utils
404 from subprocess import CalledProcessError
405-from tests.helpers import patch_open
406+from tests.helpers import patch_open, FakeRelation
407 import nose.plugins.attrib
408 import os
409 import time
410@@ -31,6 +32,46 @@
411 baz
412 """
413
414+CEPH_CLIENT_RELATION = {
415+ 'ceph:8': {
416+ 'ceph/0': {
417+ 'auth': 'cephx',
418+ 'broker-rsp-glance-0': '{"request-id": "0bc7dc54", "exit-code": 0}',
419+ 'broker-rsp-glance-1': '{"request-id": "0880e22a", "exit-code": 0}',
420+ 'broker-rsp-glance-2': '{"request-id": "0da543b8", "exit-code": 0}',
421+ 'broker_rsp': '{"request-id": "0da543b8", "exit-code": 0}',
422+ 'ceph-public-address': '10.5.44.103',
423+ 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
424+ 'private-address': '10.5.44.103',
425+ },
426+ 'ceph/1': {
427+ 'auth': 'cephx',
428+ 'ceph-public-address': '10.5.44.104',
429+ 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
430+ 'private-address': '10.5.44.104',
431+ },
432+ 'ceph/2': {
433+ 'auth': 'cephx',
434+ 'ceph-public-address': '10.5.44.105',
435+ 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
436+ 'private-address': '10.5.44.105',
437+ },
438+ 'glance/0': {
439+ 'broker_req': '{"api-version": 1, "request-id": "0bc7dc54", "ops": [{"replicas": 3, "name": "glance", "op": "create-pool"}]}',
440+ 'private-address': '10.5.44.109',
441+ },
442+ }
443+}
444+
445+CEPH_CLIENT_RELATION_LEGACY = copy.deepcopy(CEPH_CLIENT_RELATION)
446+CEPH_CLIENT_RELATION_LEGACY['ceph:8']['ceph/0'] = {
447+ 'auth': 'cephx',
448+ 'broker_rsp': '{"exit-code": 0}',
449+ 'ceph-public-address': '10.5.44.103',
450+ 'key': 'AQCLDttVuHXINhAAvI144CB09dYchhHyTUY9BQ==',
451+ 'private-address': '10.5.44.103',
452+}
453+
454
455 class CephUtilsTests(TestCase):
456 def setUp(self):
457@@ -38,6 +79,10 @@
458 [self._patch(m) for m in [
459 'check_call',
460 'check_output',
461+ 'relation_get',
462+ 'related_units',
463+ 'relation_ids',
464+ 'relation_set',
465 'log',
466 ]]
467
468@@ -554,19 +599,303 @@
469 b'ceph version 0.67.4 (ad85b8bfafea6232d64cb7ba76a8b6e8252fa0c7)'
470 self.assertEquals(ceph_utils.ceph_version(), '0.67.4')
471
472- def test_ceph_broker_rq_class(self):
473+ @patch.object(ceph_utils, 'uuid')
474+ def test_ceph_broker_rq_class(self, uuid):
475+ uuid.uuid1.return_value = 'uuid'
476 rq = ceph_utils.CephBrokerRq()
477 rq.add_op_create_pool('pool1', replica_count=1)
478 rq.add_op_create_pool('pool2')
479- expected = json.dumps({'api-version': 1,
480- 'ops': [{'op': 'create-pool', 'name': 'pool1',
481- 'replicas': 1},
482- {'op': 'create-pool', 'name': 'pool2',
483- 'replicas': 3}]})
484- self.assertEqual(rq.request, expected)
485+ expected = {
486+ 'api-version': 1,
487+ 'request-id': 'uuid',
488+ 'ops': [{'op': 'create-pool', 'name': 'pool1', 'replicas': 1},
489+ {'op': 'create-pool', 'name': 'pool2', 'replicas': 3}]
490+ }
491+ request_dict = json.loads(rq.request)
492+ for key in ['api-version', 'request-id']:
493+ self.assertEqual(request_dict[key], expected[key])
494+ for key in ['op', 'name', 'replicas']:
495+ self.assertEqual(request_dict['ops'][0][key], expected['ops'][0][key])
496+ self.assertEqual(request_dict['ops'][1][key], expected['ops'][1][key])
497
498 def test_ceph_broker_rsp_class(self):
499 rsp = ceph_utils.CephBrokerRsp(json.dumps({'exit-code': 0,
500 'stderr': "Success"}))
501 self.assertEqual(rsp.exit_code, 0)
502 self.assertEqual(rsp.exit_msg, "Success")
503+ self.assertEqual(rsp.request_id, None)
504+
505+ def test_ceph_broker_rsp_class_rqid(self):
506+ rsp = ceph_utils.CephBrokerRsp(json.dumps({'exit-code': 0,
507+ 'stderr': "Success",
508+ 'request-id': 'reqid1'}))
509+ self.assertEqual(rsp.exit_code, 0)
510+ self.assertEqual(rsp.exit_msg, 'Success')
511+ self.assertEqual(rsp.request_id, 'reqid1')
512+
513+ def setup_client_relation(self, relation):
514+ relation = FakeRelation(relation)
515+ self.relation_get.side_effect = relation.get
516+ self.relation_ids.side_effect = relation.relation_ids
517+ self.related_units.side_effect = relation.related_units
518+
519+# @patch.object(ceph_utils, 'uuid')
520+# @patch.object(ceph_utils, 'local_unit')
521+# def test_get_request_states(self, mlocal_unit, muuid):
522+# muuid.uuid1.return_value = '0bc7dc54'
523+ @patch.object(ceph_utils, 'local_unit')
524+ def test_get_request_states(self, mlocal_unit):
525+ mlocal_unit.return_value = 'glance/0'
526+ self.setup_client_relation(CEPH_CLIENT_RELATION)
527+ rq = ceph_utils.CephBrokerRq()
528+ rq.add_op_create_pool(name='glance', replica_count=3)
529+ expect = {'ceph:8': {'complete': True, 'sent': True}}
530+ self.assertEqual(ceph_utils.get_request_states(rq), expect)
531+
532+ @patch.object(ceph_utils, 'local_unit')
533+ def test_get_request_states_newrq(self, mlocal_unit):
534+ mlocal_unit.return_value = 'glance/0'
535+ self.setup_client_relation(CEPH_CLIENT_RELATION)
536+ rq = ceph_utils.CephBrokerRq()
537+ rq.add_op_create_pool(name='glance', replica_count=4)
538+ expect = {'ceph:8': {'complete': False, 'sent': False}}
539+ self.assertEqual(ceph_utils.get_request_states(rq), expect)
540+
541+ @patch.object(ceph_utils, 'local_unit')
542+ def test_get_request_states_pendingrq(self, mlocal_unit):
543+ mlocal_unit.return_value = 'glance/0'
544+ rel = copy.deepcopy(CEPH_CLIENT_RELATION)
545+ del rel['ceph:8']['ceph/0']['broker-rsp-glance-0']
546+ self.setup_client_relation(rel)
547+ rq = ceph_utils.CephBrokerRq()
548+ rq.add_op_create_pool(name='glance', replica_count=3)
549+ expect = {'ceph:8': {'complete': False, 'sent': True}}
550+ self.assertEqual(ceph_utils.get_request_states(rq), expect)
551+
552+ @patch.object(ceph_utils, 'local_unit')
553+ def test_get_request_states_failedrq(self, mlocal_unit):
554+ mlocal_unit.return_value = 'glance/0'
555+ rel = copy.deepcopy(CEPH_CLIENT_RELATION)
556+ rel['ceph:8']['ceph/0']['broker-rsp-glance-0'] = '{"request-id": "0bc7dc54", "exit-code": 1}'
557+ self.setup_client_relation(rel)
558+ rq = ceph_utils.CephBrokerRq()
559+ rq.add_op_create_pool(name='glance', replica_count=3)
560+ expect = {'ceph:8': {'complete': False, 'sent': True}}
561+ self.assertEqual(ceph_utils.get_request_states(rq), expect)
562+
563+ @patch.object(ceph_utils, 'local_unit')
564+ def test_is_request_sent(self, mlocal_unit):
565+ mlocal_unit.return_value = 'glance/0'
566+ self.setup_client_relation(CEPH_CLIENT_RELATION)
567+ rq = ceph_utils.CephBrokerRq()
568+ rq.add_op_create_pool(name='glance', replica_count=3)
569+ self.assertTrue(ceph_utils.is_request_sent(rq))
570+
571+ @patch.object(ceph_utils, 'local_unit')
572+ def test_is_request_sent_newrq(self, mlocal_unit):
573+ mlocal_unit.return_value = 'glance/0'
574+ self.setup_client_relation(CEPH_CLIENT_RELATION)
575+ rq = ceph_utils.CephBrokerRq()
576+ rq.add_op_create_pool(name='glance', replica_count=4)
577+ self.assertFalse(ceph_utils.is_request_sent(rq))
578+
579+ @patch.object(ceph_utils, 'local_unit')
580+ def test_is_request_sent_pending(self, mlocal_unit):
581+ mlocal_unit.return_value = 'glance/0'
582+ rel = copy.deepcopy(CEPH_CLIENT_RELATION)
583+ del rel['ceph:8']['ceph/0']['broker-rsp-glance-0']
584+ self.setup_client_relation(rel)
585+ rq = ceph_utils.CephBrokerRq()
586+ rq.add_op_create_pool(name='glance', replica_count=3)
587+ self.assertTrue(ceph_utils.is_request_sent(rq))
588+
589+ @patch.object(ceph_utils, 'local_unit')
590+ def test_is_request_sent_legacy(self, mlocal_unit):
591+ mlocal_unit.return_value = 'glance/0'
592+ self.setup_client_relation(CEPH_CLIENT_RELATION_LEGACY)
593+ rq = ceph_utils.CephBrokerRq()
594+ rq.add_op_create_pool(name='glance', replica_count=3)
595+ self.assertTrue(ceph_utils.is_request_sent(rq))
596+
597+ @patch.object(ceph_utils, 'local_unit')
598+ def test_is_request_sent_legacy_newrq(self, mlocal_unit):
599+ mlocal_unit.return_value = 'glance/0'
600+ self.setup_client_relation(CEPH_CLIENT_RELATION_LEGACY)
601+ rq = ceph_utils.CephBrokerRq()
602+ rq.add_op_create_pool(name='glance', replica_count=4)
603+ self.assertFalse(ceph_utils.is_request_sent(rq))
604+
605+ @patch.object(ceph_utils, 'local_unit')
606+ def test_is_request_sent_legacy_pending(self, mlocal_unit):
607+ mlocal_unit.return_value = 'glance/0'
608+ rel = copy.deepcopy(CEPH_CLIENT_RELATION_LEGACY)
609+ del rel['ceph:8']['ceph/0']['broker_rsp']
610+ rq = ceph_utils.CephBrokerRq()
611+ rq.add_op_create_pool(name='glance', replica_count=3)
612+ self.assertTrue(ceph_utils.is_request_sent(rq))
613+
614+ @patch.object(ceph_utils, 'uuid')
615+ @patch.object(ceph_utils, 'local_unit')
616+ def test_is_request_complete(self, mlocal_unit, muuid):
617+ muuid.uuid1.return_value = '0bc7dc54'
618+ mlocal_unit.return_value = 'glance/0'
619+ self.setup_client_relation(CEPH_CLIENT_RELATION)
620+ rq = ceph_utils.CephBrokerRq()
621+ rq.add_op_create_pool(name='glance', replica_count=3)
622+ self.assertTrue(ceph_utils.is_request_complete(rq))
623+
624+ @patch.object(ceph_utils, 'local_unit')
625+ def test_is_request_complete_newrq(self, mlocal_unit):
626+ mlocal_unit.return_value = 'glance/0'
627+ self.setup_client_relation(CEPH_CLIENT_RELATION)
628+ rq = ceph_utils.CephBrokerRq()
629+ rq.add_op_create_pool(name='glance', replica_count=4)
630+ self.assertFalse(ceph_utils.is_request_complete(rq))
631+
632+ @patch.object(ceph_utils, 'local_unit')
633+ def test_is_request_complete_pending(self, mlocal_unit):
634+ mlocal_unit.return_value = 'glance/0'
635+ rel = copy.deepcopy(CEPH_CLIENT_RELATION)
636+ del rel['ceph:8']['ceph/0']['broker-rsp-glance-0']
637+ self.setup_client_relation(rel)
638+ rq = ceph_utils.CephBrokerRq()
639+ rq.add_op_create_pool(name='glance', replica_count=3)
640+ self.assertFalse(ceph_utils.is_request_complete(rq))
641+
642+ @patch.object(ceph_utils, 'local_unit')
643+ def test_is_request_complete_legacy(self, mlocal_unit):
644+ mlocal_unit.return_value = 'glance/0'
645+ self.setup_client_relation(CEPH_CLIENT_RELATION_LEGACY)
646+ rq = ceph_utils.CephBrokerRq()
647+ rq.add_op_create_pool(name='glance', replica_count=3)
648+ self.assertTrue(ceph_utils.is_request_complete(rq))
649+
650+ @patch.object(ceph_utils, 'local_unit')
651+ def test_is_request_complete_legacy_newrq(self, mlocal_unit):
652+ mlocal_unit.return_value = 'glance/0'
653+ self.setup_client_relation(CEPH_CLIENT_RELATION_LEGACY)
654+ rq = ceph_utils.CephBrokerRq()
655+ rq.add_op_create_pool(name='glance', replica_count=4)
656+ self.assertFalse(ceph_utils.is_request_complete(rq))
657+
658+ @patch.object(ceph_utils, 'local_unit')
659+ def test_is_request_complete_legacy_pending(self, mlocal_unit):
660+ mlocal_unit.return_value = 'glance/0'
661+ rel = copy.deepcopy(CEPH_CLIENT_RELATION_LEGACY)
662+ del rel['ceph:8']['ceph/0']['broker_rsp']
663+ self.setup_client_relation(rel)
664+ rq = ceph_utils.CephBrokerRq()
665+ rq.add_op_create_pool(name='glance', replica_count=3)
666+ self.assertFalse(ceph_utils.is_request_complete(rq))
667+
668+ def test_equivalent_broker_requests(self):
669+ rq1 = ceph_utils.CephBrokerRq()
670+ rq1.add_op_create_pool(name='glance', replica_count=4)
671+ rq2 = ceph_utils.CephBrokerRq()
672+ rq2.add_op_create_pool(name='glance', replica_count=4)
673+ self.assertTrue(rq1 == rq2)
674+
675+ def test_equivalent_broker_requests_diff1(self):
676+ rq1 = ceph_utils.CephBrokerRq()
677+ rq1.add_op_create_pool(name='glance', replica_count=3)
678+ rq2 = ceph_utils.CephBrokerRq()
679+ rq2.add_op_create_pool(name='glance', replica_count=4)
680+ self.assertFalse(rq1 == rq2)
681+
682+ def test_equivalent_broker_requests_diff2(self):
683+ rq1 = ceph_utils.CephBrokerRq()
684+ rq1.add_op_create_pool(name='glance', replica_count=3)
685+ rq2 = ceph_utils.CephBrokerRq()
686+ rq2.add_op_create_pool(name='cinder', replica_count=3)
687+ self.assertFalse(rq1 == rq2)
688+
689+ def test_equivalent_broker_requests_diff3(self):
690+ rq1 = ceph_utils.CephBrokerRq()
691+ rq1.add_op_create_pool(name='glance', replica_count=3)
692+ rq2 = ceph_utils.CephBrokerRq(api_version=2)
693+ rq2.add_op_create_pool(name='glance', replica_count=3)
694+ self.assertFalse(rq1 == rq2)
695+
696+ @patch.object(ceph_utils, 'uuid')
697+ @patch.object(ceph_utils, 'local_unit')
698+ def test_is_request_complete_for_rid(self, mlocal_unit, muuid):
699+ muuid.uuid1.return_value = '0bc7dc54'
700+ req = ceph_utils.CephBrokerRq()
701+ req.add_op_create_pool(name='glance', replica_count=3)
702+ mlocal_unit.return_value = 'glance/0'
703+ self.setup_client_relation(CEPH_CLIENT_RELATION)
704+ self.assertTrue(ceph_utils.is_request_complete_for_rid(req, 'ceph:8'))
705+
706+ @patch.object(ceph_utils, 'uuid')
707+ @patch.object(ceph_utils, 'local_unit')
708+ def test_is_request_complete_for_rid_newrq(self, mlocal_unit, muuid):
709+ muuid.uuid1.return_value = 'a44c0fa6'
710+ req = ceph_utils.CephBrokerRq()
711+ req.add_op_create_pool(name='glance', replica_count=4)
712+ mlocal_unit.return_value = 'glance/0'
713+ self.setup_client_relation(CEPH_CLIENT_RELATION)
714+ self.assertFalse(ceph_utils.is_request_complete_for_rid(req, 'ceph:8'))
715+
716+ @patch.object(ceph_utils, 'uuid')
717+ @patch.object(ceph_utils, 'local_unit')
718+ def test_is_request_complete_for_rid_failed(self, mlocal_unit, muuid):
719+ muuid.uuid1.return_value = '0bc7dc54'
720+ req = ceph_utils.CephBrokerRq()
721+ req.add_op_create_pool(name='glance', replica_count=4)
722+ mlocal_unit.return_value = 'glance/0'
723+ rel = copy.deepcopy(CEPH_CLIENT_RELATION)
724+ rel['ceph:8']['ceph/0']['broker-rsp-glance-0'] = '{"request-id": "0bc7dc54", "exit-code": 1}'
725+ self.setup_client_relation(rel)
726+ self.assertFalse(ceph_utils.is_request_complete_for_rid(req, 'ceph:8'))
727+
728+ @patch.object(ceph_utils, 'uuid')
729+ @patch.object(ceph_utils, 'local_unit')
730+ def test_is_request_complete_for_rid_pending(self, mlocal_unit, muuid):
731+ muuid.uuid1.return_value = '0bc7dc54'
732+ req = ceph_utils.CephBrokerRq()
733+ req.add_op_create_pool(name='glance', replica_count=4)
734+ mlocal_unit.return_value = 'glance/0'
735+ rel = copy.deepcopy(CEPH_CLIENT_RELATION)
736+ del rel['ceph:8']['ceph/0']['broker-rsp-glance-0']
737+ self.setup_client_relation(rel)
738+ self.assertFalse(ceph_utils.is_request_complete_for_rid(req, 'ceph:8'))
739+
740+ @patch.object(ceph_utils, 'uuid')
741+ @patch.object(ceph_utils, 'local_unit')
742+ def test_is_request_complete_for_rid_legacy(self, mlocal_unit, muuid):
743+ muuid.uuid1.return_value = '0bc7dc54'
744+ req = ceph_utils.CephBrokerRq()
745+ req.add_op_create_pool(name='glance', replica_count=3)
746+ mlocal_unit.return_value = 'glance/0'
747+ self.setup_client_relation(CEPH_CLIENT_RELATION_LEGACY)
748+ self.assertTrue(ceph_utils.is_request_complete_for_rid(req, 'ceph:8'))
749+
750+ @patch.object(ceph_utils, 'local_unit')
751+ def test_get_broker_rsp_key(self, mlocal_unit):
752+ mlocal_unit.return_value = 'glance/0'
753+ self.assertEqual(ceph_utils.get_broker_rsp_key(), 'broker-rsp-glance-0')
754+
755+ @patch.object(ceph_utils, 'local_unit')
756+ def test_send_request_if_needed(self, mlocal_unit):
757+ mlocal_unit.return_value = 'glance/0'
758+ self.setup_client_relation(CEPH_CLIENT_RELATION)
759+ rq = ceph_utils.CephBrokerRq()
760+ rq.add_op_create_pool(name='glance', replica_count=3)
761+ ceph_utils.send_request_if_needed(rq)
762+ self.relation_set.assert_has_calls([])
763+
764+ @patch.object(ceph_utils, 'uuid')
765+ @patch.object(ceph_utils, 'local_unit')
766+ def test_send_request_if_needed_newrq(self, mlocal_unit, muuid):
767+ muuid.uuid1.return_value = 'de67511e'
768+ mlocal_unit.return_value = 'glance/0'
769+ self.setup_client_relation(CEPH_CLIENT_RELATION)
770+ rq = ceph_utils.CephBrokerRq()
771+ rq.add_op_create_pool(name='glance', replica_count=4)
772+ ceph_utils.send_request_if_needed(rq)
773+ actual = json.loads(self.relation_set.call_args_list[0][1]['broker_req'])
774+ self.assertEqual(actual['api-version'], 1)
775+ self.assertEqual(actual['request-id'], 'de67511e')
776+ self.assertEqual(actual['ops'][0]['replicas'], 4)
777+ self.assertEqual(actual['ops'][0]['op'], 'create-pool')
778+ self.assertEqual(actual['ops'][0]['name'], 'glance')
779
780=== modified file 'tests/helpers.py'
781--- tests/helpers.py 2014-11-25 15:07:02 +0000
782+++ tests/helpers.py 2015-09-10 09:26:02 +0000
783@@ -74,12 +74,12 @@
784 def __init__(self, relation_data):
785 self.relation_data = relation_data
786
787- def get(self, attr=None, unit=None, rid=None):
788+ def get(self, attribute=None, unit=None, rid=None):
789 if not rid or rid == 'foo:0':
790- if attr is None:
791+ if attribute is None:
792 return self.relation_data
793- elif attr in self.relation_data:
794- return self.relation_data[attr]
795+ elif attribute in self.relation_data:
796+ return self.relation_data[attribute]
797 return None
798 else:
799 if rid not in self.relation_data:
800@@ -88,9 +88,9 @@
801 relation = self.relation_data[rid][unit]
802 except KeyError:
803 return None
804- if attr in relation:
805- return relation[attr]
806- return None
807+ if attribute and attribute in relation:
808+ return relation[attribute]
809+ return relation
810
811 def relation_ids(self, relation=None):
812 return self.relation_data.keys()

Subscribers

People subscribed via source and target branches