Merge lp:~gnuoy/charm-helpers/stable-1453940 into lp:~openstack-charmers/charm-helpers/stable-1604

Proposed by Liam Young
Status: Merged
Merged at revision: 371
Proposed branch: lp:~gnuoy/charm-helpers/stable-1453940
Merge into: lp:~openstack-charmers/charm-helpers/stable-1604
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/stable-1453940
Reviewer Review Type Date Requested Status
Chris Glass (community) Approve
Review via email: mp+271258@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Chris Glass (tribaal) wrote :

Looks good. Same diff as for trunk. +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-08-10 16:39:41 +0000
3+++ charmhelpers/contrib/openstack/context.py 2015-09-16 09:01:05 +0000
4@@ -483,13 +483,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@@ -498,10 +500,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-08-10 16:39:41 +0000
40+++ charmhelpers/contrib/storage/linux/ceph.py 2015-09-16 09:01:05 +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@@ -411,17 +414,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@@ -431,14 +469,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-08-10 16:39:41 +0000
317+++ tests/contrib/openstack/test_os_contexts.py 2015-09-16 09:01:05 +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-08-10 16:39:41 +0000
396+++ tests/contrib/storage/test_linux_ceph.py 2015-09-16 09:01:05 +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@@ -563,19 +608,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-16 09:01:05 +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