Merge lp:~xfactor973/charm-helpers/ceph-keystore into lp:charm-helpers

Proposed by Chris Holcombe
Status: Merged
Merged at revision: 536
Proposed branch: lp:~xfactor973/charm-helpers/ceph-keystore
Merge into: lp:charm-helpers
Diff against target: 265 lines (+222/-1)
2 files modified
charmhelpers/contrib/storage/linux/ceph.py (+131/-1)
tests/contrib/storage/test_linux_ceph.py (+91/-0)
To merge this branch: bzr merge lp:~xfactor973/charm-helpers/ceph-keystore
Reviewer Review Type Date Requested Status
James Page Needs Fixing
Review via email: mp+287205@code.launchpad.net

Description of the change

This change adds functionality to the ceph contrib library to allow using the ceph monitor cluster as a generic key/value store.

To post a comment you must log in.
Revision history for this message
Chris Holcombe (xfactor973) :
Revision history for this message
James Page (james-page) wrote :

Hi Chris

Generally this looks OK - but please could you add some unit tests around monitor_key_exists.

Other than that LGTM

review: Needs Fixing
535. By Chris Holcombe

Add more unit tests and fix the CalledProcessError message

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/storage/linux/ceph.py'
2--- charmhelpers/contrib/storage/linux/ceph.py 2016-02-22 15:18:38 +0000
3+++ charmhelpers/contrib/storage/linux/ceph.py 2016-03-02 18:16:59 +0000
4@@ -24,6 +24,8 @@
5 # Adam Gandelman <adamg@ubuntu.com>
6 #
7 import bisect
8+import errno
9+import hashlib
10 import six
11
12 import os
13@@ -163,7 +165,7 @@
14 :return: None
15 """
16 # read-only is easy, writeback is much harder
17- mode = get_cache_mode(cache_pool)
18+ mode = get_cache_mode(self.service, cache_pool)
19 if mode == 'readonly':
20 check_call(['ceph', '--id', self.service, 'osd', 'tier', 'cache-mode', cache_pool, 'none'])
21 check_call(['ceph', '--id', self.service, 'osd', 'tier', 'remove', self.name, cache_pool])
22@@ -259,6 +261,134 @@
23 Returns json formatted output"""
24
25
26+def get_mon_map(service):
27+ """
28+ Returns the current monitor map.
29+ :param service: six.string_types. The Ceph user name to run the command under
30+ :return: json string. :raise: ValueError if the monmap fails to parse.
31+ Also raises CalledProcessError if our ceph command fails
32+ """
33+ try:
34+ mon_status = check_output(
35+ ['ceph', '--id', service,
36+ 'ceph', 'mon_status', '--format=json'])
37+ try:
38+ return json.loads(mon_status)
39+ except ValueError as v:
40+ log("Unable to parse mon_status json: {}. Error: {}".format(
41+ mon_status, v.message))
42+ raise
43+ except CalledProcessError as e:
44+ log("mon_status command failed with message: {}".format(
45+ e.message))
46+ raise
47+
48+
49+def hash_monitor_names(service):
50+ """
51+ Uses the get_mon_map() function to get information about the monitor
52+ cluster.
53+ Hash the name of each monitor. Return a sorted list of monitor hashes
54+ in an ascending order.
55+ :param service: six.string_types. The Ceph user name to run the command under
56+ :rtype : dict. json dict of monitor name, ip address and rank
57+ example: {
58+ 'name': 'ip-172-31-13-165',
59+ 'rank': 0,
60+ 'addr': '172.31.13.165:6789/0'}
61+ """
62+ try:
63+ hash_list = []
64+ monitor_list = get_mon_map(service=service)
65+ if monitor_list['monmap']['mons']:
66+ for mon in monitor_list['monmap']['mons']:
67+ hash_list.append(
68+ hashlib.sha224(mon['name'].encode('utf-8')).hexdigest())
69+ return sorted(hash_list)
70+ else:
71+ return None
72+ except (ValueError, CalledProcessError):
73+ raise
74+
75+
76+def monitor_key_delete(service, key):
77+ """
78+ Delete a key and value pair from the monitor cluster
79+ :param service: six.string_types. The Ceph user name to run the command under
80+ Deletes a key value pair on the monitor cluster.
81+ :param key: six.string_types. The key to delete.
82+ """
83+ try:
84+ check_output(
85+ ['ceph', '--id', service,
86+ 'ceph', 'config-key', 'del', str(key)])
87+ except CalledProcessError as e:
88+ log("Monitor config-key put failed with message: {}".format(
89+ e.output))
90+ raise
91+
92+
93+def monitor_key_set(service, key, value):
94+ """
95+ Sets a key value pair on the monitor cluster.
96+ :param service: six.string_types. The Ceph user name to run the command under
97+ :param key: six.string_types. The key to set.
98+ :param value: The value to set. This will be converted to a string
99+ before setting
100+ """
101+ try:
102+ check_output(
103+ ['ceph', '--id', service,
104+ 'ceph', 'config-key', 'put', str(key), str(value)])
105+ except CalledProcessError as e:
106+ log("Monitor config-key put failed with message: {}".format(
107+ e.output))
108+ raise
109+
110+
111+def monitor_key_get(service, key):
112+ """
113+ Gets the value of an existing key in the monitor cluster.
114+ :param service: six.string_types. The Ceph user name to run the command under
115+ :param key: six.string_types. The key to search for.
116+ :return: Returns the value of that key or None if not found.
117+ """
118+ try:
119+ output = check_output(
120+ ['ceph', '--id', service,
121+ 'ceph', 'config-key', 'get', str(key)])
122+ return output
123+ except CalledProcessError as e:
124+ log("Monitor config-key get failed with message: {}".format(
125+ e.output))
126+ return None
127+
128+
129+def monitor_key_exists(service, key):
130+ """
131+ Searches for the existence of a key in the monitor cluster.
132+ :param service: six.string_types. The Ceph user name to run the command under
133+ :param key: six.string_types. The key to search for
134+ :return: Returns True if the key exists, False if not and raises an
135+ exception if an unknown error occurs. :raise: CalledProcessError if
136+ an unknown error occurs
137+ """
138+ try:
139+ check_call(
140+ ['ceph', '--id', service,
141+ 'config-key', 'exists', str(key)])
142+ # I can return true here regardless because Ceph returns
143+ # ENOENT if the key wasn't found
144+ return True
145+ except CalledProcessError as e:
146+ if e.returncode == errno.ENOENT:
147+ return False
148+ else:
149+ log("Unknown error from ceph config-get exists: {} {}".format(
150+ e.returncode, e.output))
151+ raise
152+
153+
154 def get_erasure_profile(service, name):
155 """
156 :param service: six.string_types. The Ceph user name to run the command under
157
158=== modified file 'tests/contrib/storage/test_linux_ceph.py'
159--- tests/contrib/storage/test_linux_ceph.py 2016-02-22 15:18:38 +0000
160+++ tests/contrib/storage/test_linux_ceph.py 2016-03-02 18:16:59 +0000
161@@ -81,6 +81,32 @@
162 }
163 """
164
165+MONMAP_DUMP = """{
166+ "name": "ip-172-31-13-119", "rank": 0, "state": "leader",
167+ "election_epoch": 18, "quorum": [0, 1, 2],
168+ "outside_quorum": [],
169+ "extra_probe_peers": [],
170+ "sync_provider": [],
171+ "monmap": {
172+ "epoch": 1,
173+ "fsid": "9fdc313c-db30-11e5-9805-0242fda74275",
174+ "modified": "0.000000",
175+ "created": "0.000000",
176+ "mons": [
177+ {
178+ "rank": 0,
179+ "name": "ip-172-31-13-119",
180+ "addr": "172.31.13.119:6789\/0"},
181+ {
182+ "rank": 1,
183+ "name": "ip-172-31-24-50",
184+ "addr": "172.31.24.50:6789\/0"},
185+ {
186+ "rank": 2,
187+ "name": "ip-172-31-33-107",
188+ "addr": "172.31.33.107:6789\/0"}
189+ ]}}"""
190+
191 CEPH_CLIENT_RELATION = {
192 'ceph:8': {
193 'ceph/0': {
194@@ -386,6 +412,71 @@
195 self.check_call.assert_called_with(cmd)
196 self.assertEqual(True, profile_exists)
197
198+ def test_set_monitor_key(self):
199+ cmd = ['ceph', '--id', 'admin',
200+ 'ceph', 'config-key', 'put', 'foo', 'bar']
201+ ceph_utils.monitor_key_set(service='admin', key='foo', value='bar')
202+ self.check_output.assert_called_with(cmd)
203+
204+ def test_get_monitor_key(self):
205+ cmd = ['ceph', '--id', 'admin',
206+ 'ceph', 'config-key', 'get', 'foo']
207+ ceph_utils.monitor_key_get(service='admin', key='foo')
208+ self.check_output.assert_called_with(cmd)
209+
210+ def test_get_monitor_key_failed(self):
211+ self.check_output.side_effect = CalledProcessError(
212+ returncode=2,
213+ cmd='ceph',
214+ output='key foo does not exist')
215+ output = ceph_utils.monitor_key_get(service='admin', key='foo')
216+ self.assertEqual(None, output)
217+
218+ def test_monitor_key_exists(self):
219+ cmd = ['ceph', '--id', 'admin',
220+ 'config-key', 'exists', 'foo']
221+ ceph_utils.monitor_key_exists(service='admin', key='foo')
222+ self.check_call.assert_called_with(cmd)
223+
224+ def test_monitor_key_doesnt_exist(self):
225+ self.check_call.side_effect = CalledProcessError(
226+ returncode=2,
227+ cmd='ceph',
228+ output='key foo does not exist')
229+ output = ceph_utils.monitor_key_exists(service='admin', key='foo')
230+ self.assertEqual(False, output)
231+
232+ def test_delete_monitor_key(self):
233+ ceph_utils.monitor_key_delete(service='admin', key='foo')
234+ cmd = ['ceph', '--id', 'admin',
235+ 'ceph', 'config-key', 'del', 'foo']
236+ self.check_output.assert_called_with(cmd)
237+
238+ def test_delete_monitor_key_failed(self):
239+ self.check_output.side_effect = CalledProcessError(
240+ returncode=2,
241+ cmd='ceph',
242+ output='deletion failed')
243+ self.assertRaises(CalledProcessError, ceph_utils.monitor_key_delete,
244+ service='admin', key='foo')
245+
246+ def test_get_monmap(self):
247+ self.check_output.return_value = MONMAP_DUMP
248+ cmd = ['ceph', '--id', 'admin',
249+ 'ceph', 'mon_status', '--format=json']
250+ ceph_utils.get_mon_map(service='admin')
251+ self.check_output.assert_called_with(cmd)
252+
253+ @patch.object(ceph_utils, 'get_mon_map')
254+ def test_hash_monitor_names(self, monmap):
255+ expected_hash_list = [
256+ '010d57d581604d411b315dd64112bff832ab92c7323fa06077134b50',
257+ '8e0a9705c1aeafa1ce250cc9f1bb443fc6e5150e5edcbeb6eeb82e3c',
258+ 'c3f8d36ba098c23ee920cb08cfb9beda6b639f8433637c190bdd56ec']
259+ monmap.return_value = json.loads(MONMAP_DUMP)
260+ hashed_mon_list = ceph_utils.hash_monitor_names(service='admin')
261+ self.assertEqual(expected=expected_hash_list, observed=hashed_mon_list)
262+
263 def test_get_cache_mode(self):
264 self.check_output.return_value = OSD_DUMP
265 cache_mode = ceph_utils.get_cache_mode(service='admin', pool_name='rbd')

Subscribers

People subscribed via source and target branches