Merge lp:~openstack-charmers/charm-helpers/active-active into lp:charm-helpers

Proposed by James Page
Status: Merged
Merged at revision: 132
Proposed branch: lp:~openstack-charmers/charm-helpers/active-active
Merge into: lp:charm-helpers
Diff against target: 272 lines (+180/-23)
6 files modified
charmhelpers/contrib/openstack/context.py (+9/-6)
charmhelpers/contrib/openstack/utils.py (+13/-13)
charmhelpers/contrib/peerstorage/__init__.py (+83/-0)
tests/contrib/openstack/test_openstack_utils.py (+6/-3)
tests/contrib/openstack/test_os_contexts.py (+0/-1)
tests/contrib/peerstorage/test_peerstorage.py (+69/-0)
To merge this branch: bzr merge lp:~openstack-charmers/charm-helpers/active-active
Reviewer Review Type Date Requested Status
Marco Ceppi Approve
Review via email: mp+211285@code.launchpad.net

Description of the change

Tweaks to AMQP context for HA changes

New peerstorage helper which is useful for storing and replicating passwords etc.. across peers in a cluster.

Fix to get_hostname to make fqdn flag work properly.

To post a comment you must log in.
Revision history for this message
Marco Ceppi (marcoceppi) wrote :

LGTM

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 2014-02-27 15:50:39 +0000
3+++ charmhelpers/contrib/openstack/context.py 2014-03-17 11:41:06 +0000
4@@ -199,6 +199,7 @@
5
6 ctxt = {}
7 for rid in relation_ids('amqp'):
8+ ha_vip_only = False
9 for unit in related_units(rid):
10 if relation_get('clustered', rid=rid, unit=unit):
11 ctxt['clustered'] = True
12@@ -213,16 +214,18 @@
13 unit=unit),
14 'rabbitmq_virtual_host': vhost,
15 })
16+ if relation_get('ha_queues', rid=rid, unit=unit) is not None:
17+ ctxt['rabbitmq_ha_queues'] = True
18+
19+ ha_vip_only = relation_get('ha-vip-only',
20+ rid=rid, unit=unit) is not None
21+
22 if context_complete(ctxt):
23 # Sufficient information found = break out!
24 break
25 # Used for active/active rabbitmq >= grizzly
26- if ('clustered' not in ctxt or relation_get('ha-vip-only') == 'True') and \
27- len(related_units(rid)) > 1:
28- if relation_get('ha_queues'):
29- ctxt['rabbitmq_ha_queues'] = relation_get('ha_queues')
30- else:
31- ctxt['rabbitmq_ha_queues'] = False
32+ if ('clustered' not in ctxt or ha_vip_only) \
33+ and len(related_units(rid)) > 1:
34 rabbitmq_hosts = []
35 for unit in related_units(rid):
36 rabbitmq_hosts.append(relation_get('private-address',
37
38=== modified file 'charmhelpers/contrib/openstack/utils.py'
39--- charmhelpers/contrib/openstack/utils.py 2014-01-14 12:14:15 +0000
40+++ charmhelpers/contrib/openstack/utils.py 2014-03-17 11:41:06 +0000
41@@ -420,19 +420,19 @@
42 Resolves hostname for given IP, or returns the input
43 if it is already a hostname.
44 """
45- if not is_ip(address):
46- return address
47-
48- try:
49- import dns.reversename
50- except ImportError:
51- apt_install('python-dnspython')
52- import dns.reversename
53-
54- rev = dns.reversename.from_address(address)
55- result = ns_query(rev)
56- if not result:
57- return None
58+ if is_ip(address):
59+ try:
60+ import dns.reversename
61+ except ImportError:
62+ apt_install('python-dnspython')
63+ import dns.reversename
64+
65+ rev = dns.reversename.from_address(address)
66+ result = ns_query(rev)
67+ if not result:
68+ return None
69+ else:
70+ result = address
71
72 if fqdn:
73 # strip trailing .
74
75=== added directory 'charmhelpers/contrib/peerstorage'
76=== added file 'charmhelpers/contrib/peerstorage/__init__.py'
77--- charmhelpers/contrib/peerstorage/__init__.py 1970-01-01 00:00:00 +0000
78+++ charmhelpers/contrib/peerstorage/__init__.py 2014-03-17 11:41:06 +0000
79@@ -0,0 +1,83 @@
80+from charmhelpers.core.hookenv import (
81+ relation_ids,
82+ relation_get,
83+ local_unit,
84+ relation_set,
85+)
86+
87+"""
88+This helper provides functions to support use of a peer relation
89+for basic key/value storage, with the added benefit that all storage
90+can be replicated across peer units, so this is really useful for
91+services that issue usernames/passwords to remote services.
92+
93+def shared_db_changed()
94+ # Only the lead unit should create passwords
95+ if not is_leader():
96+ return
97+ username = relation_get('username')
98+ key = '{}.password'.format(username)
99+ # Attempt to retrieve any existing password for this user
100+ password = peer_retrieve(key)
101+ if password is None:
102+ # New user, create password and store
103+ password = pwgen(length=64)
104+ peer_store(key, password)
105+ create_access(username, password)
106+ relation_set(password=password)
107+
108+
109+def cluster_changed()
110+ # Echo any relation data other that *-address
111+ # back onto the peer relation so all units have
112+ # all *.password keys stored on their local relation
113+ # for later retrieval.
114+ peer_echo()
115+
116+"""
117+
118+
119+def peer_retrieve(key, relation_name='cluster'):
120+ """ Retrieve a named key from peer relation relation_name """
121+ cluster_rels = relation_ids(relation_name)
122+ if len(cluster_rels) > 0:
123+ cluster_rid = cluster_rels[0]
124+ return relation_get(attribute=key, rid=cluster_rid,
125+ unit=local_unit())
126+ else:
127+ raise ValueError('Unable to detect'
128+ 'peer relation {}'.format(relation_name))
129+
130+
131+def peer_store(key, value, relation_name='cluster'):
132+ """ Store the key/value pair on the named peer relation relation_name """
133+ cluster_rels = relation_ids(relation_name)
134+ if len(cluster_rels) > 0:
135+ cluster_rid = cluster_rels[0]
136+ relation_set(relation_id=cluster_rid,
137+ relation_settings={key: value})
138+ else:
139+ raise ValueError('Unable to detect '
140+ 'peer relation {}'.format(relation_name))
141+
142+
143+def peer_echo(includes=None):
144+ """Echo filtered attributes back onto the same relation for storage
145+
146+ Note that this helper must only be called within a peer relation
147+ changed hook
148+ """
149+ rdata = relation_get()
150+ echo_data = {}
151+ if includes is None:
152+ echo_data = rdata.copy()
153+ for ex in ['private-address', 'public-address']:
154+ if ex in echo_data:
155+ echo_data.pop(ex)
156+ else:
157+ for attribute, value in rdata.iteritems():
158+ for include in includes:
159+ if include in attribute:
160+ echo_data[attribute] = value
161+ if len(echo_data) > 0:
162+ relation_set(relation_settings=echo_data)
163
164=== modified file 'tests/contrib/openstack/test_openstack_utils.py'
165--- tests/contrib/openstack/test_openstack_utils.py 2014-01-13 10:31:33 +0000
166+++ tests/contrib/openstack/test_openstack_utils.py 2014-03-17 11:41:06 +0000
167@@ -599,11 +599,14 @@
168
169 @patch.object(openstack, 'apt_install')
170 def test_get_hostname_with_hostname(self, apt_install):
171- fake_dns = FakeDNS('5.5.5.5')
172- with patch('__builtin__.__import__', side_effect=[fake_dns]):
173- hn = openstack.get_hostname('www.ubuntu.com')
174+ hn = openstack.get_hostname('www.ubuntu.com')
175 self.assertEquals(hn, 'www.ubuntu.com')
176
177+ @patch.object(openstack, 'apt_install')
178+ def test_get_hostname_with_hostname_not_fqdn(self, apt_install):
179+ hn = openstack.get_hostname('packages.ubuntu.com', fqdn=False)
180+ self.assertEquals(hn, 'packages')
181+
182
183 if __name__ == '__main__':
184 unittest.main()
185
186=== modified file 'tests/contrib/openstack/test_os_contexts.py'
187--- tests/contrib/openstack/test_os_contexts.py 2014-02-27 16:20:51 +0000
188+++ tests/contrib/openstack/test_os_contexts.py 2014-03-17 11:41:06 +0000
189@@ -337,7 +337,6 @@
190 'rabbitmq_user': 'adam',
191 'rabbitmq_virtual_host': 'foo',
192 'rabbitmq_hosts': 'rabbithost2,rabbithost1',
193- 'rabbitmq_ha_queues': False
194 }
195 self.assertEquals(result, expected)
196
197
198=== added directory 'tests/contrib/peerstorage'
199=== added file 'tests/contrib/peerstorage/__init__.py'
200=== added file 'tests/contrib/peerstorage/test_peerstorage.py'
201--- tests/contrib/peerstorage/test_peerstorage.py 1970-01-01 00:00:00 +0000
202+++ tests/contrib/peerstorage/test_peerstorage.py 2014-03-17 11:41:06 +0000
203@@ -0,0 +1,69 @@
204+from tests.helpers import FakeRelation
205+from testtools import TestCase
206+from mock import patch
207+from charmhelpers.contrib import peerstorage
208+
209+
210+TO_PATCH = ['relation_ids', 'relation_set', 'relation_get', 'local_unit']
211+FAKE_RELATION_NAME = 'cluster'
212+FAKE_RELATION = {
213+ 'cluster:0': {
214+ 'cluster/0': {
215+ },
216+ 'cluster/1': {
217+ },
218+ 'cluster/2': {
219+ },
220+ },
221+
222+}
223+FAKE_RELATION_IDS = ['cluster:0']
224+FAKE_LOCAL_UNIT = 'test_host'
225+
226+
227+class TestPeerStorage(TestCase):
228+ def setUp(self):
229+ super(TestPeerStorage, self).setUp()
230+ for m in TO_PATCH:
231+ setattr(self, m, self._patch(m))
232+ self.fake_relation_name = FAKE_RELATION_NAME
233+ self.fake_relation = FakeRelation(FAKE_RELATION)
234+ self.local_unit.return_value = FAKE_LOCAL_UNIT
235+ self.relation_get.return_value = {'key1': 'value1',
236+ 'key2': 'value2',
237+ 'private-address': '127.0.0.1',
238+ 'public-address': '91.189.90.159'}
239+
240+ def _patch(self, method):
241+ _m = patch('charmhelpers.contrib.peerstorage.' + method)
242+ mock = _m.start()
243+ self.addCleanup(_m.stop)
244+ return mock
245+
246+ def test_peer_retrieve_no_relation(self):
247+ self.relation_ids.return_value = []
248+ self.assertRaises(ValueError, peerstorage.peer_retrieve, 'key', relation_name=self.fake_relation_name)
249+
250+ def test_peer_retrieve_with_relation(self):
251+ self.relation_ids.return_value = FAKE_RELATION_IDS
252+ peerstorage.peer_retrieve('key', self.fake_relation_name)
253+ self.relation_get.assert_called_with(attribute='key', rid=FAKE_RELATION_IDS[0], unit=FAKE_LOCAL_UNIT)
254+
255+ def test_peer_store_no_relation(self):
256+ self.relation_ids.return_value = []
257+ self.assertRaises(ValueError, peerstorage.peer_store, 'key', 'value', relation_name=self.fake_relation_name)
258+
259+ def test_peer_store_with_relation(self):
260+ self.relation_ids.return_value = FAKE_RELATION_IDS
261+ peerstorage.peer_store('key', 'value', self.fake_relation_name)
262+ self.relation_set.assert_called_with(relation_id=FAKE_RELATION_IDS[0],
263+ relation_settings={'key': 'value'})
264+
265+ def test_peer_echo_no_includes(self):
266+ peerstorage.peer_echo()
267+ self.relation_set.assert_called_with(relation_settings={'key1': 'value1',
268+ 'key2': 'value2'})
269+
270+ def test_peer_echo_includes(self):
271+ peerstorage.peer_echo(['key1'])
272+ self.relation_set.assert_called_with(relation_settings={'key1': 'value1'})

Subscribers

People subscribed via source and target branches