Merge lp:~james-page/charms/trusty/ceph-osd/status into lp:~openstack-charmers-archive/charms/trusty/ceph-osd/next

Proposed by James Page on 2015-10-07
Status: Merged
Merged at revision: 52
Proposed branch: lp:~james-page/charms/trusty/ceph-osd/status
Merge into: lp:~openstack-charmers-archive/charms/trusty/ceph-osd/next
Diff against target: 360 lines (+240/-4)
9 files modified
.coveragerc (+7/-0)
.pydevproject (+1/-0)
Makefile (+7/-2)
hooks/ceph.py (+13/-1)
hooks/ceph_hooks.py (+28/-1)
setup.cfg (+5/-0)
unit_tests/__init__.py (+2/-0)
unit_tests/test_status.py (+56/-0)
unit_tests/test_utils.py (+121/-0)
To merge this branch: bzr merge lp:~james-page/charms/trusty/ceph-osd/status
Reviewer Review Type Date Requested Status
David Ames Approve on 2015-10-08
Chris Holcombe (community) 2015-10-07 Approve on 2015-10-08
James Page Resubmit on 2015-10-08
Review via email: mp+273634@code.launchpad.net
To post a comment you must log in.

charm_unit_test #10667 ceph-osd-next for james-page mp273634
    UNIT FAIL: unit-test failed

UNIT Results (max last 2 lines):
make: *** [test] Error 1
ERROR:root:Make target returned non-zero.

Full unit test output: http://paste.ubuntu.com/12701797/
Build: http://10.245.162.77:8080/job/charm_unit_test/10667/

charm_lint_check #11480 ceph-osd-next for james-page mp273634
    LINT FAIL: lint-test failed

LINT Results (max last 2 lines):
make: *** [lint] Error 1
ERROR:root:Make target returned non-zero.

Full lint test output: http://paste.ubuntu.com/12702222/
Build: http://10.245.162.77:8080/job/charm_lint_check/11480/

charm_amulet_test #7177 ceph-osd-next for james-page mp273634
    AMULET FAIL: amulet-test failed

AMULET Results (max last 2 lines):
make: *** [functional_test] Error 1
ERROR:root:Make target returned non-zero.

Full amulet test output: http://paste.ubuntu.com/12703539/
Build: http://10.245.162.77:8080/job/charm_amulet_test/7177/

Ryan Beisner (1chb1n) wrote :

FWIW, I've seen a ton of this in the past ~36hrs across various charm tests. One of the ceph units failed in the install hook due to:

2015-10-07 11:26:36 INFO install WARNING: The following packages cannot be authenticated!
2015-10-07 11:26:36 INFO install libjs-jquery python-werkzeug python-markupsafe python-jinja2
2015-10-07 11:26:36 INFO install python-itsdangerous python-flask libreadline5 xfsprogs libunwind8 libsnappy1
2015-10-07 11:26:36 INFO install libleveldb1 libopts25 btrfs-tools python-blinker python-pyinotify
2015-10-07 11:26:36 INFO install E: There are problems and -y was used without --force-yes

charm_unit_test #10673 ceph-osd-next for james-page mp273634
    UNIT OK: passed

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

charm_amulet_test #7184 ceph-osd-next for james-page mp273634
    AMULET OK: passed

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

David Ames (thedac) wrote :

Lint test is failing.
Other than that this looks good.

54. By James Page on 2015-10-08

Tidy lint

James Page (james-page) wrote :

Lint tidied up.

review: Resubmit

charm_lint_check #11505 ceph-osd-next for james-page mp273634
    LINT OK: passed

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

charm_unit_test #10698 ceph-osd-next for james-page mp273634
    UNIT OK: passed

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

charm_amulet_test #7210 ceph-osd-next for james-page mp273634
    AMULET OK: passed

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

Chris Holcombe (xfactor973) wrote :

Looks good to me

review: Approve
David Ames (thedac) wrote :

Approved. Merging.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file '.coveragerc'
2--- .coveragerc 1970-01-01 00:00:00 +0000
3+++ .coveragerc 2015-10-08 11:42:10 +0000
4@@ -0,0 +1,7 @@
5+[report]
6+# Regexes for lines to exclude from consideration
7+exclude_lines =
8+ if __name__ == .__main__.:
9+include=
10+ hooks/hooks.py
11+ hooks/ceph*.py
12
13=== modified file '.pydevproject'
14--- .pydevproject 2012-10-08 14:07:16 +0000
15+++ .pydevproject 2015-10-08 11:42:10 +0000
16@@ -4,5 +4,6 @@
17 <pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
18 <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
19 <path>/ceph-osd/hooks</path>
20+<path>/ceph-osd/unit_tests</path>
21 </pydev_pathproperty>
22 </pydev_project>
23
24=== modified file 'Makefile'
25--- Makefile 2015-07-02 15:08:10 +0000
26+++ Makefile 2015-10-08 11:42:10 +0000
27@@ -3,9 +3,14 @@
28
29 lint:
30 @flake8 --exclude hooks/charmhelpers,tests/charmhelpers \
31- hooks tests unit_tests
32+ hooks tests unit_tests
33 @charm proof
34
35+test:
36+ @# Bundletester expects unit tests here.
37+ @echo Starting unit tests...
38+ @$(PYTHON) /usr/bin/nosetests --nologcapture --with-coverage unit_tests
39+
40 functional_test:
41 @echo Starting Amulet tests...
42 @juju test -v -p AMULET_HTTP_PROXY,AMULET_OS_VIP --timeout 2700
43@@ -13,7 +18,7 @@
44 bin/charm_helpers_sync.py:
45 @mkdir -p bin
46 @bzr cat lp:charm-helpers/tools/charm_helpers_sync/charm_helpers_sync.py \
47- > bin/charm_helpers_sync.py
48+ > bin/charm_helpers_sync.py
49
50 sync: bin/charm_helpers_sync.py
51 $(PYTHON) bin/charm_helpers_sync.py -c charm-helpers-hooks.yaml
52
53=== modified file 'hooks/ceph.py'
54--- hooks/ceph.py 2014-09-12 12:12:47 +0000
55+++ hooks/ceph.py 2015-10-08 11:42:10 +0000
56@@ -18,7 +18,8 @@
57 )
58 from charmhelpers.core.hookenv import (
59 log,
60- ERROR, WARNING
61+ ERROR, WARNING,
62+ status_set,
63 )
64 from charmhelpers.contrib.storage.linux.utils import (
65 zap_disk,
66@@ -333,6 +334,7 @@
67 log('Looks like {} is in use, skipping.'.format(dev))
68 return
69
70+ status_set('maintenance', 'Initializing device {}'.format(dev))
71 cmd = ['ceph-disk-prepare']
72 # Later versions of ceph support more options
73 if cmp_pkgrevno('ceph', '0.48.3') >= 0:
74@@ -382,3 +384,13 @@
75
76 def filesystem_mounted(fs):
77 return subprocess.call(['grep', '-wqs', fs, '/proc/mounts']) == 0
78+
79+
80+def get_running_osds():
81+ '''Returns a list of the pids of the current running OSD daemons'''
82+ cmd = ['pgrep', 'ceph-osd']
83+ try:
84+ result = subprocess.check_output(cmd)
85+ return result.split()
86+ except subprocess.CalledProcessError:
87+ return []
88
89=== renamed file 'hooks/hooks.py' => 'hooks/ceph_hooks.py'
90--- hooks/hooks.py 2015-09-22 13:35:49 +0000
91+++ hooks/ceph_hooks.py 2015-10-08 11:42:10 +0000
92@@ -22,7 +22,8 @@
93 relation_get,
94 Hooks,
95 UnregisteredHookError,
96- service_name
97+ service_name,
98+ status_set,
99 )
100 from charmhelpers.core.host import (
101 umount,
102@@ -227,8 +228,34 @@
103 nrpe_setup.write()
104
105
106+def assess_status():
107+ '''Assess status of current unit'''
108+ # Check for mon relation
109+ if len(relation_ids('mon')) < 1:
110+ status_set('blocked', 'Missing relation: monitor')
111+ return
112+
113+ # Check for monitors with presented addresses
114+ # Check for bootstrap key presentation
115+ monitors = get_mon_hosts()
116+ if len(monitors) < 1 or not get_conf('osd_bootstrap_key'):
117+ status_set('waiting', 'Incomplete relation: monitor')
118+ return
119+
120+ # Check for OSD device creation parity i.e. at least some devices
121+ # must have been presented and used for this charm to be operational
122+ running_osds = ceph.get_running_osds()
123+ if not running_osds:
124+ status_set('blocked',
125+ 'No block devices detected using current configuration')
126+ else:
127+ status_set('active',
128+ 'Unit is ready ({} OSD)'.format(len(running_osds)))
129+
130+
131 if __name__ == '__main__':
132 try:
133 hooks.execute(sys.argv)
134 except UnregisteredHookError as e:
135 log('Unknown hook {} - skipping.'.format(e))
136+ assess_status()
137
138=== modified symlink 'hooks/config-changed'
139=== target changed u'hooks.py' => u'ceph_hooks.py'
140=== modified symlink 'hooks/install.real'
141=== target changed u'hooks.py' => u'ceph_hooks.py'
142=== modified symlink 'hooks/mon-relation-changed'
143=== target changed u'hooks.py' => u'ceph_hooks.py'
144=== modified symlink 'hooks/mon-relation-departed'
145=== target changed u'hooks.py' => u'ceph_hooks.py'
146=== modified symlink 'hooks/nrpe-external-master-relation-changed'
147=== target changed u'hooks.py' => u'ceph_hooks.py'
148=== modified symlink 'hooks/nrpe-external-master-relation-joined'
149=== target changed u'hooks.py' => u'ceph_hooks.py'
150=== modified symlink 'hooks/start'
151=== target changed u'hooks.py' => u'ceph_hooks.py'
152=== modified symlink 'hooks/stop'
153=== target changed u'hooks.py' => u'ceph_hooks.py'
154=== added symlink 'hooks/update-status'
155=== target is u'ceph_hooks.py'
156=== modified symlink 'hooks/upgrade-charm'
157=== target changed u'hooks.py' => u'ceph_hooks.py'
158=== added file 'setup.cfg'
159--- setup.cfg 1970-01-01 00:00:00 +0000
160+++ setup.cfg 2015-10-08 11:42:10 +0000
161@@ -0,0 +1,5 @@
162+[nosetests]
163+verbosity=2
164+with-coverage=1
165+cover-erase=1
166+cover-package=hooks
167
168=== modified file 'unit_tests/__init__.py'
169--- unit_tests/__init__.py 2014-11-25 18:27:01 +0000
170+++ unit_tests/__init__.py 2015-10-08 11:42:10 +0000
171@@ -0,0 +1,2 @@
172+import sys
173+sys.path.append('hooks')
174
175=== added file 'unit_tests/test_status.py'
176--- unit_tests/test_status.py 1970-01-01 00:00:00 +0000
177+++ unit_tests/test_status.py 2015-10-08 11:42:10 +0000
178@@ -0,0 +1,56 @@
179+import mock
180+import test_utils
181+
182+import ceph_hooks as hooks
183+
184+TO_PATCH = [
185+ 'status_set',
186+ 'config',
187+ 'ceph',
188+ 'relation_ids',
189+ 'relation_get',
190+ 'related_units',
191+ 'get_conf',
192+]
193+
194+CEPH_MONS = [
195+ 'ceph/0',
196+ 'ceph/1',
197+ 'ceph/2',
198+]
199+
200+
201+class ServiceStatusTestCase(test_utils.CharmTestCase):
202+
203+ def setUp(self):
204+ super(ServiceStatusTestCase, self).setUp(hooks, TO_PATCH)
205+ self.config.side_effect = self.test_config.get
206+
207+ def test_assess_status_no_monitor_relation(self):
208+ self.relation_ids.return_value = []
209+ hooks.assess_status()
210+ self.status_set.assert_called_with('blocked', mock.ANY)
211+
212+ def test_assess_status_monitor_relation_incomplete(self):
213+ self.relation_ids.return_value = ['mon:1']
214+ self.related_units.return_value = CEPH_MONS
215+ self.get_conf.return_value = None
216+ hooks.assess_status()
217+ self.status_set.assert_called_with('waiting', mock.ANY)
218+
219+ def test_assess_status_monitor_complete_no_disks(self):
220+ self.relation_ids.return_value = ['mon:1']
221+ self.related_units.return_value = CEPH_MONS
222+ self.get_conf.return_value = 'monitor-bootstrap-key'
223+ self.ceph.get_running_osds.return_value = []
224+ hooks.assess_status()
225+ self.status_set.assert_called_with('blocked', mock.ANY)
226+
227+ def test_assess_status_monitor_complete_disks(self):
228+ self.relation_ids.return_value = ['mon:1']
229+ self.related_units.return_value = CEPH_MONS
230+ self.get_conf.return_value = 'monitor-bootstrap-key'
231+ self.ceph.get_running_osds.return_value = ['12345',
232+ '67890']
233+ hooks.assess_status()
234+ self.status_set.assert_called_with('active', mock.ANY)
235
236=== added file 'unit_tests/test_utils.py'
237--- unit_tests/test_utils.py 1970-01-01 00:00:00 +0000
238+++ unit_tests/test_utils.py 2015-10-08 11:42:10 +0000
239@@ -0,0 +1,121 @@
240+import logging
241+import unittest
242+import os
243+import yaml
244+
245+from contextlib import contextmanager
246+from mock import patch, MagicMock
247+
248+
249+def load_config():
250+ '''
251+ Walk backwords from __file__ looking for config.yaml, load and return the
252+ 'options' section'
253+ '''
254+ config = None
255+ f = __file__
256+ while config is None:
257+ d = os.path.dirname(f)
258+ if os.path.isfile(os.path.join(d, 'config.yaml')):
259+ config = os.path.join(d, 'config.yaml')
260+ break
261+ f = d
262+
263+ if not config:
264+ logging.error('Could not find config.yaml in any parent directory '
265+ 'of %s. ' % f)
266+ raise Exception
267+
268+ return yaml.safe_load(open(config).read())['options']
269+
270+
271+def get_default_config():
272+ '''
273+ Load default charm config from config.yaml return as a dict.
274+ If no default is set in config.yaml, its value is None.
275+ '''
276+ default_config = {}
277+ config = load_config()
278+ for k, v in config.iteritems():
279+ if 'default' in v:
280+ default_config[k] = v['default']
281+ else:
282+ default_config[k] = None
283+ return default_config
284+
285+
286+class CharmTestCase(unittest.TestCase):
287+
288+ def setUp(self, obj, patches):
289+ super(CharmTestCase, self).setUp()
290+ self.patches = patches
291+ self.obj = obj
292+ self.test_config = TestConfig()
293+ self.test_relation = TestRelation()
294+ self.patch_all()
295+
296+ def patch(self, method):
297+ _m = patch.object(self.obj, method)
298+ mock = _m.start()
299+ self.addCleanup(_m.stop)
300+ return mock
301+
302+ def patch_all(self):
303+ for method in self.patches:
304+ setattr(self, method, self.patch(method))
305+
306+
307+class TestConfig(object):
308+
309+ def __init__(self):
310+ self.config = get_default_config()
311+
312+ def get(self, attr=None):
313+ if not attr:
314+ return self.get_all()
315+ try:
316+ return self.config[attr]
317+ except KeyError:
318+ return None
319+
320+ def get_all(self):
321+ return self.config
322+
323+ def set(self, attr, value):
324+ if attr not in self.config:
325+ raise KeyError
326+ self.config[attr] = value
327+
328+
329+class TestRelation(object):
330+
331+ def __init__(self, relation_data={}):
332+ self.relation_data = relation_data
333+
334+ def set(self, relation_data):
335+ self.relation_data = relation_data
336+
337+ def get(self, attr=None, unit=None, rid=None):
338+ if attr is None:
339+ return self.relation_data
340+ elif attr in self.relation_data:
341+ return self.relation_data[attr]
342+ return None
343+
344+
345+@contextmanager
346+def patch_open():
347+ '''Patch open() to allow mocking both open() itself and the file that is
348+ yielded.
349+
350+ Yields the mock for "open" and "file", respectively.'''
351+ mock_open = MagicMock(spec=open)
352+ mock_file = MagicMock(spec=file)
353+
354+ @contextmanager
355+ def stub_open(*args, **kwargs):
356+ mock_open(*args, **kwargs)
357+ yield mock_file
358+
359+ with patch('__builtin__.open', stub_open):
360+ yield mock_open, mock_file

Subscribers

People subscribed via source and target branches