Merge lp:~stub/charm-helpers/status-set into lp:charm-helpers

Proposed by Stuart Bishop
Status: Rejected
Rejected by: Stuart Bishop
Proposed branch: lp:~stub/charm-helpers/status-set
Merge into: lp:charm-helpers
Diff against target: 221 lines (+186/-4)
2 files modified
charmhelpers/core/hookenv.py (+68/-4)
tests/core/test_hookenv.py (+118/-0)
To merge this branch: bzr merge lp:~stub/charm-helpers/status-set
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Disapprove
Review via email: mp+258618@code.launchpad.net

Description of the change

Wrap the new status-set and status-get hook tools, with fallback when running with versions of Juju that do not support the service status feature.

To post a comment you must log in.
Revision history for this message
Stuart Bishop (stub) wrote :

The implicit termination causes trouble with the Config persistence feature, so that will need to be fixed. Or scrap this and go with https://code.launchpad.net/~gnuoy/charm-helpers/status-set/+merge/258533 which I just found.

Revision history for this message
Stuart Bishop (stub) :
review: Disapprove

Unmerged revisions

370. By Stuart Bishop

Wrap status-set and status-get, with fallback behavior for pre-service-status Juju

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/core/hookenv.py'
2--- charmhelpers/core/hookenv.py 2015-04-07 16:12:51 +0000
3+++ charmhelpers/core/hookenv.py 2015-05-08 12:54:37 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2014-2015 Canonical Limited.
6+# Copyright 2013-2015 Canonical Limited.
7 #
8 # This file is part of charm-helpers.
9 #
10@@ -13,14 +13,13 @@
11 #
12 # You should have received a copy of the GNU Lesser General Public License
13 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
14-
15-"Interactions with the Juju environment"
16-# Copyright 2013 Canonical Ltd.
17 #
18 # Authors:
19 # Charm Helpers Developers <juju@lists.ubuntu.com>
20+"Interactions with the Juju environment"
21
22 from __future__ import print_function
23+from distutils.spawn import find_executable
24 import os
25 import json
26 import yaml
27@@ -605,3 +604,68 @@
28
29 The results set by action_set are preserved."""
30 subprocess.check_call(['action-fail', message])
31+
32+
33+@cached
34+def _has_status_feature():
35+ return find_executable('status-set') is not None
36+
37+
38+MAINTENANCE = 'MAINTENANCE'
39+BLOCKED = 'BLOCKED'
40+WAITING = 'WAITING'
41+ACTIVE = 'ACTIVE'
42+
43+
44+def status_set(status, message):
45+ """Sets the unit's service status. If waiting or blocked, terminate.
46+
47+ `status` must be one of 'maintenance', 'blocked', 'waiting' or 'active'.
48+
49+ If the service status feature is unavailable, logs the message and
50+ puts the unit into an error state if blocked, or terminates cleanly
51+ if waiting.
52+
53+ This API is subject to change until the Juju service status feature
54+ is available in a stable release (est. Juju 1.24).
55+ """
56+ status = str(status).lower()
57+ assert status in ('maintenance', 'blocked', 'waiting', 'active'), \
58+ 'Invalid status code {!r}'.format(status)
59+
60+ if _has_status_feature():
61+ subprocess.check_call(['status-set', status, str(message)])
62+ if status in ('waiting', 'blocked'):
63+ sys.exit(0) # Exit cleanly.
64+ else:
65+ message = '{}: {}'.format(status, message)
66+ if status == 'waiting':
67+ log(message, WARNING)
68+ sys.exit(0) # Exit cleanly.
69+ elif status == 'blocked':
70+ log(message, CRITICAL)
71+ sys.exit(1) # Exit uncleanly. Signal manual intervention required.
72+ else:
73+ log(message, INFO)
74+
75+
76+def status_get():
77+ """Return the unit's service status."""
78+ if _has_status_feature():
79+ s = subprocess.check_output(['status-get'], universal_newlines=True)
80+ return s.strip()
81+ else:
82+ return "unknown"
83+
84+
85+def status_get_details():
86+ """Return the unit's service status and any associated data as a dict.
87+
88+ The format is as defined by the 'status-get --include-data' hook tool.
89+ """
90+ if _has_status_feature():
91+ raw = subprocess.check_output(['status-get', '--include-data'],
92+ universal_newlines=True)
93+ return yaml.safe_load(raw)
94+ else:
95+ return {}
96
97=== modified file 'tests/core/test_hookenv.py'
98--- tests/core/test_hookenv.py 2015-03-12 10:31:22 +0000
99+++ tests/core/test_hookenv.py 2015-05-08 12:54:37 +0000
100@@ -1115,3 +1115,121 @@
101 message = "Ooops, the action failed"
102 hookenv.action_fail(message)
103 check_call.assert_called_with(['action-fail', message])
104+
105+ @patch('charmhelpers.core.hookenv.find_executable')
106+ def test_has_status_feature(self, find_executable):
107+ hookenv.cache.clear()
108+ find_executable.return_value = '/bin/status-set'
109+ self.assertTrue(hookenv._has_status_feature())
110+ find_executable.assert_called_once_with('status-set')
111+ hookenv.cache.clear()
112+ find_executable.return_value = None
113+ self.assertFalse(hookenv._has_status_feature())
114+
115+ @patch('subprocess.check_call')
116+ @patch('charmhelpers.core.hookenv._has_status_feature')
117+ @patch('sys.exit')
118+ def test_status_set_maintenance(self, exit, hasf, check_call):
119+ hasf.return_value = True
120+ hookenv.status_set(hookenv.MAINTENANCE, 'mincing kittens')
121+ check_call.assert_called_once_with(['status-set', 'maintenance',
122+ 'mincing kittens'])
123+ self.assertFalse(exit.called)
124+
125+ @patch('subprocess.check_call')
126+ @patch('charmhelpers.core.hookenv._has_status_feature')
127+ @patch('sys.exit')
128+ def test_status_set_waiting(self, exit, hasf, check_call):
129+ hasf.return_value = True
130+ hookenv.status_set(hookenv.WAITING, 'kitten delivery')
131+ check_call.assert_called_once_with(['status-set', 'waiting',
132+ 'kitten delivery'])
133+ exit.assert_called_once_with(0)
134+
135+ @patch('subprocess.check_call')
136+ @patch('charmhelpers.core.hookenv._has_status_feature')
137+ @patch('sys.exit')
138+ def test_status_set_blocked(self, exit, hasf, check_call):
139+ hasf.return_value = True
140+ hookenv.status_set(hookenv.BLOCKED, 'feed moar kittens')
141+ check_call.assert_called_once_with(['status-set', 'blocked',
142+ 'feed moar kittens'])
143+ exit.assert_called_once_with(0)
144+
145+ @patch('subprocess.check_call')
146+ @patch('charmhelpers.core.hookenv._has_status_feature')
147+ @patch('sys.exit')
148+ def test_status_set_active(self, exit, hasf, check_call):
149+ hasf.return_value = True
150+ hookenv.status_set(hookenv.ACTIVE, 'serving kittens')
151+ check_call.assert_called_once_with(['status-set', 'active',
152+ 'serving kittens'])
153+ self.assertFalse(exit.called)
154+
155+ @patch('charmhelpers.core.hookenv.log')
156+ @patch('charmhelpers.core.hookenv._has_status_feature')
157+ @patch('sys.exit')
158+ def test_status_set_maintenance_fallback(self, exit, hasf, log):
159+ hasf.return_value = False
160+ hookenv.status_set(hookenv.MAINTENANCE, 'mincing kittens')
161+ log.assert_called_once_with('maintenance: mincing kittens',
162+ hookenv.INFO)
163+ self.assertFalse(exit.called)
164+
165+ @patch('charmhelpers.core.hookenv.log')
166+ @patch('charmhelpers.core.hookenv._has_status_feature')
167+ @patch('sys.exit')
168+ def test_status_set_waiting_fallback(self, exit, hasf, log):
169+ hasf.return_value = False
170+ hookenv.status_set(hookenv.WAITING, 'kitten delivery')
171+ log.assert_called_once_with('waiting: kitten delivery',
172+ hookenv.WARNING)
173+ exit.assert_called_once_with(0) # No unit error state.
174+
175+ @patch('charmhelpers.core.hookenv.log')
176+ @patch('charmhelpers.core.hookenv._has_status_feature')
177+ @patch('sys.exit')
178+ def test_status_set_blocked_fallback(self, exit, hasf, log):
179+ hasf.return_value = False
180+ hookenv.status_set(hookenv.BLOCKED, 'feed me kittens')
181+ log.assert_called_once_with('blocked: feed me kittens',
182+ hookenv.CRITICAL)
183+ exit.assert_called_once_with(1) # Force unit error state
184+
185+ @patch('charmhelpers.core.hookenv.log')
186+ @patch('charmhelpers.core.hookenv._has_status_feature')
187+ @patch('sys.exit')
188+ def test_status_set_active_fallback(self, exit, hasf, log):
189+ hasf.return_value = False
190+ hookenv.status_set(hookenv.ACTIVE, 'serving kittens')
191+ log.assert_called_once_with('active: serving kittens',
192+ hookenv.INFO)
193+ self.assertFalse(exit.called)
194+
195+ @patch('subprocess.check_output')
196+ @patch('charmhelpers.core.hookenv._has_status_feature')
197+ def test_status_get(self, hasf, check_output):
198+ hasf.return_value = True
199+ check_output.return_value = 'whatever'
200+ self.assertEqual(hookenv.status_get(), 'whatever')
201+ check_output.assert_called_once_with(['status-get'],
202+ universal_newlines=True)
203+
204+ @patch('subprocess.check_output')
205+ @patch('charmhelpers.core.hookenv._has_status_feature')
206+ def test_status_get_details(self, hasf, check_output):
207+ hasf.return_value = True
208+ check_output.return_value = 'some_yaml: true'
209+ self.assertEqual(hookenv.status_get_details(), dict(some_yaml=True))
210+ check_output.assert_called_once_with(['status-get', '--include-data'],
211+ universal_newlines=True)
212+
213+ @patch('charmhelpers.core.hookenv._has_status_feature')
214+ def test_status_get_fallback(self, hasf):
215+ hasf.return_value = False
216+ self.assertEqual(hookenv.status_get(), 'unknown')
217+
218+ @patch('charmhelpers.core.hookenv._has_status_feature')
219+ def test_status_get_details_fallback(self, hasf):
220+ hasf.return_value = False
221+ self.assertDictEqual(hookenv.status_get_details(), {})

Subscribers

People subscribed via source and target branches