Merge lp:~james-page/charm-helpers/fixup-service-running into lp:charm-helpers

Proposed by James Page
Status: Merged
Merged at revision: 574
Proposed branch: lp:~james-page/charm-helpers/fixup-service-running
Merge into: lp:charm-helpers
Diff against target: 364 lines (+78/-94)
2 files modified
charmhelpers/core/host.py (+16/-23)
tests/core/test_host.py (+62/-71)
To merge this branch: bzr merge lp:~james-page/charm-helpers/fixup-service-running
Reviewer Review Type Date Requested Status
Ryan Beisner (community) Approve
Edward Hope-Morley Approve
Review via email: mp+294942@code.launchpad.net

Commit message

Refactor service_running to determine init approach

Use of "service --status-all" has some challenges on trusty where
a recent lsb-base update resulted in all calls being diverted to
upstart, where return codes for non-running daemons are still 0.

Detect whether to use upstart or the base init.d script to
determine whether a service is running, and look at either the
upstart output, or for a non-zero return code from an init.d
status call.

To post a comment you must log in.
574. By James Page

Avoid conditional for sysv init check

Revision history for this message
Ryan Beisner (1chb1n) wrote :

I'm syncing this into a few charms @master and stable/16.04 to exercise. Will report back here.

review: Needs Information
Revision history for this message
James Page (james-page) wrote :
Revision history for this message
Edward Hope-Morley (hopem) wrote :

lgtm +1

review: Needs Resubmitting
Revision history for this message
Edward Hope-Morley (hopem) :
review: Approve
Revision history for this message
Ryan Beisner (1chb1n) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmhelpers/core/host.py'
--- charmhelpers/core/host.py 2016-05-13 16:04:36 +0000
+++ charmhelpers/core/host.py 2016-05-17 17:50:07 +0000
@@ -128,11 +128,8 @@
128 return subprocess.call(cmd) == 0128 return subprocess.call(cmd) == 0
129129
130130
131def systemv_services_running():131_UPSTART_CONF = "/etc/init/{}.conf"
132 output = subprocess.check_output(132_INIT_D_CONF = "/etc/init.d/{}"
133 ['service', '--status-all'],
134 stderr=subprocess.STDOUT).decode('UTF-8')
135 return [row.split()[-1] for row in output.split('\n') if '[ + ]' in row]
136133
137134
138def service_running(service_name):135def service_running(service_name):
@@ -140,26 +137,22 @@
140 if init_is_systemd():137 if init_is_systemd():
141 return service('is-active', service_name)138 return service('is-active', service_name)
142 else:139 else:
143 try:140 if os.path.exists(_UPSTART_CONF.format(service_name)):
144 output = subprocess.check_output(141 try:
145 ['service', service_name, 'status'],142 output = subprocess.check_output(
146 stderr=subprocess.STDOUT).decode('UTF-8')143 ['status', service_name],
147 except subprocess.CalledProcessError:144 stderr=subprocess.STDOUT).decode('UTF-8')
148 return False145 except subprocess.CalledProcessError:
149 else:
150 # This works for upstart scripts where the 'service' command
151 # returns a consistent string to represent running 'start/running'
152 if ("start/running" in output or "is running" in output or
153 "up and running" in output):
154 return True
155 # Actively check for upstart stopped message(s) as the fall through
156 # SystemV check can return false positives
157 if ("stop/waiting" in output):
158 return False146 return False
147 else:
148 # This works for upstart scripts where the 'service' command
149 # returns a consistent string to represent running 'start/running'
150 if "start/running" in output:
151 return True
152 elif os.path.exists(_INIT_D_CONF.format(service_name)):
159 # Check System V scripts init script return codes153 # Check System V scripts init script return codes
160 if service_name in systemv_services_running():154 return service('status', service_name)
161 return True155 return False
162 return False
163156
164157
165def service_available(service_name):158def service_available(service_name):
166159
=== modified file 'tests/core/test_host.py'
--- tests/core/test_host.py 2016-05-13 16:29:19 +0000
+++ tests/core/test_host.py 2016-05-17 17:50:07 +0000
@@ -60,13 +60,6 @@
60link/ether 08:00:27:16:b9:5f brd ff:ff:ff:ff:ff:ff60link/ether 08:00:27:16:b9:5f brd ff:ff:ff:ff:ff:ff
61"""61"""
6262
63SERVICE_STATUS_ALL = b"""
64 [ + ] rabbitmq-server
65 [ ? ] rc.local
66 [ + ] resolvconf
67 [ - ] rsync
68"""
69
7063
71class HelpersTest(TestCase):64class HelpersTest(TestCase):
7265
@@ -176,15 +169,16 @@
176 call('enable', service_name),169 call('enable', service_name),
177 call('start', service_name)])170 call('start', service_name)])
178171
172 @patch.object(host, 'service_running')
179 @patch.object(host, 'init_is_systemd')173 @patch.object(host, 'init_is_systemd')
180 @patch('subprocess.check_output')
181 @patch.object(host, 'service')174 @patch.object(host, 'service')
182 def test_pauses_a_running_upstart_service(self, service, check_output, systemd):175 def test_pauses_a_running_upstart_service(self, service, systemd,
176 service_running):
183 """Pause on a running service will call service stop."""177 """Pause on a running service will call service stop."""
184 check_output.return_value = b'foo-service start/running, process 123'
185 service_name = 'foo-service'178 service_name = 'foo-service'
186 service.side_effect = [True]179 service.side_effect = [True]
187 systemd.return_value = False180 systemd.return_value = False
181 service_running.return_value = True
188 tempdir = mkdtemp(prefix="test_pauses_an_upstart_service")182 tempdir = mkdtemp(prefix="test_pauses_an_upstart_service")
189 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))183 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))
190 # Just needs to exist184 # Just needs to exist
@@ -200,15 +194,16 @@
200 override_contents = fh.read()194 override_contents = fh.read()
201 self.assertEqual("manual\n", override_contents)195 self.assertEqual("manual\n", override_contents)
202196
197 @patch.object(host, 'service_running')
203 @patch.object(host, 'init_is_systemd')198 @patch.object(host, 'init_is_systemd')
204 @patch('subprocess.check_output')
205 @patch.object(host, 'service')199 @patch.object(host, 'service')
206 def test_pauses_a_stopped_upstart_service(self, service, check_output, systemd):200 def test_pauses_a_stopped_upstart_service(self, service, systemd,
201 service_running):
207 """Pause on a stopped service will not call service stop."""202 """Pause on a stopped service will not call service stop."""
208 check_output.return_value = b'foo-service stop/waiting'
209 service_name = 'foo-service'203 service_name = 'foo-service'
210 service.side_effect = [True]204 service.side_effect = [True]
211 systemd.return_value = False205 systemd.return_value = False
206 service_running.return_value = False
212 tempdir = mkdtemp(prefix="test_pauses_an_upstart_service")207 tempdir = mkdtemp(prefix="test_pauses_an_upstart_service")
213 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))208 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))
214 # Just needs to exist209 # Just needs to exist
@@ -226,17 +221,17 @@
226 override_contents = fh.read()221 override_contents = fh.read()
227 self.assertEqual("manual\n", override_contents)222 self.assertEqual("manual\n", override_contents)
228223
224 @patch.object(host, 'service_running')
229 @patch.object(host, 'init_is_systemd')225 @patch.object(host, 'init_is_systemd')
230 @patch('subprocess.check_output')
231 @patch('subprocess.check_call')226 @patch('subprocess.check_call')
232 @patch.object(host, 'service')227 @patch.object(host, 'service')
233 def test_pauses_a_running_sysv_service(self, service, check_call,228 def test_pauses_a_running_sysv_service(self, service, check_call,
234 check_output, systemd):229 systemd, service_running):
235 """Pause calls service stop on a running sysv service."""230 """Pause calls service stop on a running sysv service."""
236 check_output.return_value = b'foo-service start/running, process 123'
237 service_name = 'foo-service'231 service_name = 'foo-service'
238 service.side_effect = [True]232 service.side_effect = [True]
239 systemd.return_value = False233 systemd.return_value = False
234 service_running.return_value = True
240 tempdir = mkdtemp(prefix="test_pauses_a_sysv_service")235 tempdir = mkdtemp(prefix="test_pauses_a_sysv_service")
241 sysv_path = os.path.join(tempdir, service_name)236 sysv_path = os.path.join(tempdir, service_name)
242 # Just needs to exist237 # Just needs to exist
@@ -249,17 +244,17 @@
249 service.assert_called_with('stop', service_name)244 service.assert_called_with('stop', service_name)
250 check_call.assert_called_with(["update-rc.d", service_name, "disable"])245 check_call.assert_called_with(["update-rc.d", service_name, "disable"])
251246
247 @patch.object(host, 'service_running')
252 @patch.object(host, 'init_is_systemd')248 @patch.object(host, 'init_is_systemd')
253 @patch('subprocess.check_output')
254 @patch('subprocess.check_call')249 @patch('subprocess.check_call')
255 @patch.object(host, 'service')250 @patch.object(host, 'service')
256 def test_pauses_a_stopped_sysv_service(self, service, check_call,251 def test_pauses_a_stopped_sysv_service(self, service, check_call,
257 check_output, systemd):252 systemd, service_running):
258 """Pause does not call service stop on a stopped sysv service."""253 """Pause does not call service stop on a stopped sysv service."""
259 check_output.return_value = b'foo-service stop/waiting'
260 service_name = 'foo-service'254 service_name = 'foo-service'
261 service.side_effect = [True]255 service.side_effect = [True]
262 systemd.return_value = False256 systemd.return_value = False
257 service_running.return_value = False
263 tempdir = mkdtemp(prefix="test_pauses_a_sysv_service")258 tempdir = mkdtemp(prefix="test_pauses_a_sysv_service")
264 sysv_path = os.path.join(tempdir, service_name)259 sysv_path = os.path.join(tempdir, service_name)
265 # Just needs to exist260 # Just needs to exist
@@ -289,15 +284,17 @@
289 "Unable to detect {0}".format(service_name), str(exception))284 "Unable to detect {0}".format(service_name), str(exception))
290 self.assertIn(tempdir, str(exception))285 self.assertIn(tempdir, str(exception))
291286
287 @patch.object(host, 'service_running')
292 @patch.object(host, 'init_is_systemd')288 @patch.object(host, 'init_is_systemd')
293 @patch('subprocess.check_output')289 @patch('subprocess.check_output')
294 @patch.object(host, 'service')290 @patch.object(host, 'service')
295 def test_resumes_a_running_upstart_service(self, service, check_output, systemd):291 def test_resumes_a_running_upstart_service(self, service, check_output, systemd,
292 service_running):
296 """When the service is already running, service start isn't called."""293 """When the service is already running, service start isn't called."""
297 check_output.return_value = b'foo-service start/running, process 111'
298 service_name = 'foo-service'294 service_name = 'foo-service'
299 service.side_effect = [True]295 service.side_effect = [True]
300 systemd.return_value = False296 systemd.return_value = False
297 service_running.return_value = True
301 tempdir = mkdtemp(prefix="test_resumes_an_upstart_service")298 tempdir = mkdtemp(prefix="test_resumes_an_upstart_service")
302 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))299 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))
303 with open(conf_path, "w") as fh:300 with open(conf_path, "w") as fh:
@@ -306,21 +303,23 @@
306 self.assertTrue(host.service_resume(service_name, init_dir=tempdir))303 self.assertTrue(host.service_resume(service_name, init_dir=tempdir))
307304
308 # Start isn't called because service is already running305 # Start isn't called because service is already running
309 self.assertRaises(306 self.assertFalse(service.called)
310 AssertionError, service.assert_called_with, 'start', service_name)
311 override_path = os.path.join(307 override_path = os.path.join(
312 tempdir, "{}.override".format(service_name))308 tempdir, "{}.override".format(service_name))
313 self.assertFalse(os.path.exists(override_path))309 self.assertFalse(os.path.exists(override_path))
314310
311 @patch.object(host, 'service_running')
315 @patch.object(host, 'init_is_systemd')312 @patch.object(host, 'init_is_systemd')
316 @patch('subprocess.check_output')313 @patch('subprocess.check_output')
317 @patch.object(host, 'service')314 @patch.object(host, 'service')
318 def test_resumes_a_stopped_upstart_service(self, service, check_output, systemd):315 def test_resumes_a_stopped_upstart_service(self, service, check_output, systemd,
316 service_running):
319 """When the service is stopped, service start is called."""317 """When the service is stopped, service start is called."""
320 check_output.return_value = b'foo-service stop/waiting'318 check_output.return_value = b'foo-service stop/waiting'
321 service_name = 'foo-service'319 service_name = 'foo-service'
322 service.side_effect = [True]320 service.side_effect = [True]
323 systemd.return_value = False321 systemd.return_value = False
322 service_running.return_value = False
324 tempdir = mkdtemp(prefix="test_resumes_an_upstart_service")323 tempdir = mkdtemp(prefix="test_resumes_an_upstart_service")
325 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))324 conf_path = os.path.join(tempdir, "{}.conf".format(service_name))
326 with open(conf_path, "w") as fh:325 with open(conf_path, "w") as fh:
@@ -333,16 +332,17 @@
333 tempdir, "{}.override".format(service_name))332 tempdir, "{}.override".format(service_name))
334 self.assertFalse(os.path.exists(override_path))333 self.assertFalse(os.path.exists(override_path))
335334
335 @patch.object(host, 'service_running')
336 @patch.object(host, 'init_is_systemd')336 @patch.object(host, 'init_is_systemd')
337 @patch('subprocess.check_output')
338 @patch('subprocess.check_call')337 @patch('subprocess.check_call')
339 @patch.object(host, 'service')338 @patch.object(host, 'service')
340 def test_resumes_a_sysv_service(self, service, check_call, check_output, systemd):339 def test_resumes_a_sysv_service(self, service, check_call, systemd,
340 service_running):
341 """When process is in a stop/waiting state, service start is called."""341 """When process is in a stop/waiting state, service start is called."""
342 check_output.return_value = b'foo-service stop/waiting'
343 service_name = 'foo-service'342 service_name = 'foo-service'
344 service.side_effect = [True]343 service.side_effect = [True]
345 systemd.return_value = False344 systemd.return_value = False
345 service_running.return_value = False
346 tempdir = mkdtemp(prefix="test_resumes_a_sysv_service")346 tempdir = mkdtemp(prefix="test_resumes_a_sysv_service")
347 sysv_path = os.path.join(tempdir, service_name)347 sysv_path = os.path.join(tempdir, service_name)
348 # Just needs to exist348 # Just needs to exist
@@ -355,17 +355,16 @@
355 service.assert_called_with('start', service_name)355 service.assert_called_with('start', service_name)
356 check_call.assert_called_with(["update-rc.d", service_name, "enable"])356 check_call.assert_called_with(["update-rc.d", service_name, "enable"])
357357
358 @patch.object(host, 'service_running')
358 @patch.object(host, 'init_is_systemd')359 @patch.object(host, 'init_is_systemd')
359 @patch('subprocess.check_output')
360 @patch('subprocess.check_call')360 @patch('subprocess.check_call')
361 @patch.object(host, 'service')361 @patch.object(host, 'service')
362 def test_resume_a_running_sysv_service(self, service, check_call,362 def test_resume_a_running_sysv_service(self, service, check_call,
363 check_output, systemd):363 systemd, service_running):
364 """When process is already running, service start isn't called."""364 """When process is already running, service start isn't called."""
365 check_output.return_value = b'foo-service start/running, process 123'
366 service_name = 'foo-service'365 service_name = 'foo-service'
367 service.side_effect = [True]
368 systemd.return_value = False366 systemd.return_value = False
367 service_running.return_value = True
369 tempdir = mkdtemp(prefix="test_resumes_a_sysv_service")368 tempdir = mkdtemp(prefix="test_resumes_a_sysv_service")
370 sysv_path = os.path.join(tempdir, service_name)369 sysv_path = os.path.join(tempdir, service_name)
371 # Just needs to exist370 # Just needs to exist
@@ -376,16 +375,18 @@
376 service_name, init_dir=tempdir, initd_dir=tempdir))375 service_name, init_dir=tempdir, initd_dir=tempdir))
377376
378 # Start isn't called because service is already running377 # Start isn't called because service is already running
379 self.assertRaises(378 self.assertFalse(service.called)
380 AssertionError, service.assert_called_with, 'start', service_name)
381 check_call.assert_called_with(["update-rc.d", service_name, "enable"])379 check_call.assert_called_with(["update-rc.d", service_name, "enable"])
382380
381 @patch.object(host, 'service_running')
383 @patch.object(host, 'init_is_systemd')382 @patch.object(host, 'init_is_systemd')
384 @patch.object(host, 'service')383 @patch.object(host, 'service')
385 def test_resume_with_unknown_service(self, service, systemd):384 def test_resume_with_unknown_service(self, service, systemd,
385 service_running):
386 service_name = 'foo-service'386 service_name = 'foo-service'
387 service.side_effect = [True]387 service.side_effect = [True]
388 systemd.return_value = False388 systemd.return_value = False
389 service_running.return_value = False
389 tempdir = mkdtemp(prefix="test_resumes_with_unknown_service")390 tempdir = mkdtemp(prefix="test_resumes_with_unknown_service")
390 self.addCleanup(rmtree, tempdir)391 self.addCleanup(rmtree, tempdir)
391 exception = self.assertRaises(392 exception = self.assertRaises(
@@ -467,67 +468,57 @@
467 call('restart', service_name)468 call('restart', service_name)
468 ])469 ])
469470
470 @patch.object(host, 'systemv_services_running')471 @patch.object(host, 'os')
471 @patch.object(host, 'init_is_systemd')472 @patch.object(host, 'init_is_systemd')
472 @patch('subprocess.check_output')473 @patch('subprocess.check_output')
473 def test_service_running_on_stopped_service(self, check_output, systemd,474 def test_service_running_on_stopped_service(self, check_output, systemd,
474 systemv_services_running):475 os):
475 systemd.return_value = False476 systemd.return_value = False
477 os.path.exists.return_value = True
476 check_output.return_value = b'foo stop/waiting'478 check_output.return_value = b'foo stop/waiting'
477 self.assertFalse(host.service_running('foo'))479 self.assertFalse(host.service_running('foo'))
478 self.assertFalse(systemv_services_running.called)
479480
481 @patch.object(host, 'os')
480 @patch.object(host, 'init_is_systemd')482 @patch.object(host, 'init_is_systemd')
481 @patch('subprocess.check_output')483 @patch('subprocess.check_output')
482 def test_service_running_on_running_service(self, check_output, systemd):484 def test_service_running_on_running_service(self, check_output, systemd,
485 os):
483 systemd.return_value = False486 systemd.return_value = False
487 os.path.exists.return_value = True
484 check_output.return_value = b'foo start/running, process 23871'488 check_output.return_value = b'foo start/running, process 23871'
485 self.assertTrue(host.service_running('foo'))489 self.assertTrue(host.service_running('foo'))
486490
487 @patch.object(host, 'init_is_systemd')491 @patch.object(host, 'os')
488 @patch('subprocess.check_output')492 @patch.object(host, 'init_is_systemd')
489 def test_service_running_on_running_service_pxc(self, check_output, systemd):493 @patch('subprocess.check_output')
490 systemd.return_value = False494 def test_service_running_on_unknown_service(self, check_output, systemd,
491 check_output.return_value = b' * Percona XtraDB Cluster up and running'495 os):
492 self.assertTrue(host.service_running('foo'))496 systemd.return_value = False
493497 os.path.exists.return_value = True
494 @patch.object(host, 'init_is_systemd')
495 @patch('subprocess.check_output')
496 def test_service_running_on_unknown_service(self, check_output, systemd):
497 systemd.return_value = False
498 exc = subprocess.CalledProcessError(1, ['status'])498 exc = subprocess.CalledProcessError(1, ['status'])
499 check_output.side_effect = exc499 check_output.side_effect = exc
500 self.assertFalse(host.service_running('foo'))500 self.assertFalse(host.service_running('foo'))
501501
502 @patch.object(host, 'systemv_services_running')502 @patch.object(host, 'os')
503 @patch.object(host, 'service')
503 @patch.object(host, 'init_is_systemd')504 @patch.object(host, 'init_is_systemd')
504 @patch('subprocess.check_output')505 def test_service_systemv_running(self, systemd, service, os):
505 def test_service_systemv_running(self, check_output, systemd,
506 systemv_services_running):
507 systemd.return_value = False506 systemd.return_value = False
508 check_output.return_value = b' * Unhelpfull guff, thanks a lot rabbit'507 service.return_value = True
509 systemv_services_running.return_value = [u'udev', u'rabbitmq-server']508 os.path.exists.side_effect = [False, True]
510 self.assertTrue(host.service_running('rabbitmq-server'))509 self.assertTrue(host.service_running('rabbitmq-server'))
511 self.assertTrue(systemv_services_running.called)510 service.assert_called_with('status', 'rabbitmq-server')
512511
513 @patch.object(host, 'systemv_services_running')512 @patch.object(host, 'os')
513 @patch.object(host, 'service')
514 @patch.object(host, 'init_is_systemd')514 @patch.object(host, 'init_is_systemd')
515 @patch('subprocess.check_output')515 def test_service_systemv_not_running(self, systemd, service,
516 def test_service_systemv_not_running(self, check_output, systemd,516 os):
517 systemv_services_running):
518 systemd.return_value = False517 systemd.return_value = False
519 check_output.return_value = b' * Unhelpfull guff, thanks a lot rabbit'518 service.return_value = False
520 systemv_services_running.return_value = [u'udev', u'rabbitmq-server']519 os.path.exists.side_effect = [False, True]
521 self.assertFalse(host.service_running('keystone'))520 self.assertFalse(host.service_running('keystone'))
522 self.assertTrue(systemv_services_running.called)521 service.assert_called_with('status', 'keystone')
523
524 @patch('subprocess.check_output')
525 def test_systemv_services_running(self, check_output):
526 check_output.return_value = SERVICE_STATUS_ALL
527 self.assertEqual(
528 host.systemv_services_running(),
529 [u'rabbitmq-server', u'resolvconf']
530 )
531522
532 @patch('grp.getgrnam')523 @patch('grp.getgrnam')
533 @patch('pwd.getpwnam')524 @patch('pwd.getpwnam')

Subscribers

People subscribed via source and target branches