2
# -*- coding: utf-8 -*-
4
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
6
# This file is part of Ansible
8
# Ansible is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# Ansible is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
24
author: Michael DeHaan
26
short_description: Manage services.
28
- Controls services on remote hosts.
33
- Name of the service.
36
choices: [ started, stopped, restarted, reloaded ]
38
- C(started)/C(stopped) are idempotent actions that will not run
39
commands unless necessary. C(restarted) will always bounce the
40
service. C(reloaded) will always reload.
45
- If the service does not respond to the status command, name a
46
substring to look for as would be found in the output of the I(ps)
47
command as a stand-in for a status result. If the string is found,
48
the service will be assumed to be running.
51
choices: [ "yes", "no" ]
53
- Whether the service should start on boot.
56
- Additional arguments provided on the command line
59
- description: Example action to start service httpd, if not running
60
code: "service: name=httpd state=started"
61
- description: Example action to stop service httpd, if running
62
code: "service: name=httpd state=stopped"
63
- description: Example action to restart service httpd, in all cases
64
code: "service: name=httpd state=restarted"
65
- description: Example action to reload service httpd, in all cases
66
code: "service: name=httpd state=reloaded"
67
- description: Example action to start service foo, based on running process /usr/bin/foo
68
code: "service: name=foo pattern=/usr/bin/foo state=started"
69
- description: Example action to restart network service for interface eth0
70
code: "service: name=network state=restarted args=eth0"
79
class Service(object):
81
This is the generic Service manipulation class that is subclassed
84
A subclass should override the following action methods:-
90
All subclasses MUST define platform and distribution (which may be None).
96
def __new__(cls, *args, **kwargs):
97
return load_platform_subclass(Service, args, kwargs)
99
def __init__(self, module):
101
self.name = module.params['name']
102
self.state = module.params['state']
103
self.pattern = module.params['pattern']
104
self.enable = module.params['enabled']
109
self.svc_initscript = None
110
self.svc_initctl = None
111
self.enable_cmd = None
112
self.arguments = module.params.get('arguments', '')
113
self.rcconf_file = None
114
self.rcconf_key = None
115
self.rcconf_value = None
117
# select whether we dump additional debug info through syslog
118
self.syslogging = False
120
# ===========================================
121
# Platform specific methods (must be replaced by subclass).
123
def get_service_tools(self):
124
self.module.fail_json(msg="get_service_tools not implemented on target platform")
126
def service_enable(self):
127
self.module.fail_json(msg="service_enable not implemented on target platform")
129
def get_service_status(self):
130
self.module.fail_json(msg="get_service_status not implemented on target platform")
132
def service_control(self):
133
self.module.fail_json(msg="service_control not implemented on target platform")
135
# ===========================================
136
# Generic methods that should be used on all platforms.
138
def execute_command(self, cmd, daemonize=False):
140
syslog.openlog('ansible-%s' % os.path.basename(__file__))
141
syslog.syslog(syslog.LOG_NOTICE, 'Command %s, daemonize %r' % (cmd, daemonize))
143
# Most things don't need to be daemonized
145
return self.module.run_command(cmd)
147
# This is complex because daemonization is hard for people.
148
# What we do is daemonize a part of this module, the daemon runs the
149
# command, picks up the return code and output, and returns it to the
155
# Set stdin/stdout/stderr to /dev/null
156
fd = os.open(os.devnull, os.O_RDWR)
163
if fd not in (0, 1, 2):
166
# Make us a daemon. Yes, that's all it takes.
177
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=lambda: os.close(pipe[1]))
180
fds = [p.stdout, p.stderr]
181
# Wait for all output, or until the main process is dead and its output is done.
183
rfd, wfd, efd = select.select(fds, [], fds, 1)
184
if not (rfd + wfd + efd) and p.poll() is not None:
187
dat = os.read(p.stdout.fileno(), 4096)
192
dat = os.read(p.stderr.fileno(), 4096)
197
# Return a JSON blob to parent
198
os.write(pipe[1], json.dumps([p.returncode, stdout, stderr]))
202
self.module.fail_json(msg="unable to fork")
206
# Wait for data from daemon process and process it.
209
rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
211
dat = os.read(pipe[0], 4096)
215
return json.loads(data)
219
if platform.system() == 'SunOS':
225
psbin = self.module.get_bin_path('ps', True)
227
(rc, psout, pserr) = self.execute_command('%s %s' % (psbin, psflags))
228
# If rc is 0, set running as appropriate
231
lines = psout.split("\n")
233
if self.pattern in line and not "pattern=" in line:
234
# so as to not confuse ./hacking/test-module
238
def check_service_changed(self):
239
if self.state and self.running is None:
240
self.module.fail_json(msg="failed determining service state, possible typo of service name?")
241
# Find out if state has changed
242
if not self.running and self.state in ["started", "running"]:
244
elif self.running and self.state in ["stopped","reloaded"]:
246
elif self.state == "restarted":
248
if self.module.check_mode and self.changed:
249
self.module.exit_json(changed=True, msg='service state changed')
251
def modify_service_state(self):
253
# Only do something if state will change
256
if self.state in ['started', 'running']:
257
self.action = "start"
258
elif self.state == 'stopped':
260
elif self.state == 'reloaded':
261
self.action = "reload"
262
elif self.state == 'restarted':
263
self.action = "restart"
265
if self.module.check_mode:
266
self.module.exit_json(changed=True, msg='changing service state')
268
return self.service_control()
271
# If nothing needs to change just say all is well
277
def service_enable_rcconf(self):
278
if self.rcconf_file is None or self.rcconf_key is None or self.rcconf_value is None:
279
self.module.fail_json(msg="service_enable_rcconf() requires rcconf_file, rcconf_key and rcconf_value")
282
entry = '%s="%s"\n' % (self.rcconf_key, self.rcconf_value)
283
RCFILE = open(self.rcconf_file, "r")
286
# Build a list containing the possibly modified file.
287
for rcline in RCFILE:
288
# Parse line removing whitespaces, quotes, etc.
289
rcarray = shlex.split(rcline, comments=True)
290
if len(rcarray) >= 1 and '=' in rcarray[0]:
291
(key, value) = rcarray[0].split("=", 1)
292
if key == self.rcconf_key:
293
if value == self.rcconf_value:
294
# Since the proper entry already exists we can stop iterating.
298
# We found the key but the value is wrong, replace with new entry.
302
# Add line to the list.
303
new_rc_conf.append(rcline)
305
# We are done with reading the current rc.conf, close it.
308
# If we did not see any trace of our entry we need to add it.
310
new_rc_conf.append(entry)
315
if self.module.check_mode:
316
self.module.exit_json(changed=True, msg="changing service enablement")
318
# Create a temporary file next to the current rc.conf (so we stay on the same filesystem).
319
# This way the replacement operation is atomic.
320
rcconf_dir = os.path.dirname(self.rcconf_file)
321
rcconf_base = os.path.basename(self.rcconf_file)
322
(TMP_RCCONF, tmp_rcconf_file) = tempfile.mkstemp(dir=rcconf_dir, prefix="%s-" % rcconf_base)
324
# Write out the contents of the list into our temporary file.
325
for rcline in new_rc_conf:
326
os.write(TMP_RCCONF, rcline)
328
# Close temporary file.
331
# Replace previous rc.conf.
332
self.module.atomic_replace(tmp_rcconf_file, self.rcconf_file)
334
# ===========================================
337
class LinuxService(Service):
339
This is the Linux Service manipulation class - it is currently supporting
340
a mixture of binaries and init scripts for controlling services started at
341
boot, as well as for controlling the current state.
347
def get_service_tools(self):
349
paths = [ '/sbin', '/usr/sbin', '/bin', '/usr/bin' ]
350
binaries = [ 'service', 'chkconfig', 'update-rc.d', 'initctl', 'systemctl', 'start', 'stop', 'restart' ]
351
initpaths = [ '/etc/init.d' ]
354
for binary in binaries:
355
location[binary] = None
356
for binary in binaries:
357
location[binary] = self.module.get_bin_path(binary)
359
# Locate a tool for enable options
360
if location.get('chkconfig', None) and os.path.exists("/etc/init.d/%s" % self.name):
361
# we are using a standard SysV service
362
self.enable_cmd = location['chkconfig']
363
elif location.get('update-rc.d', None) and os.path.exists("/etc/init/%s.conf" % self.name):
364
# service is managed by upstart
365
self.enable_cmd = location['update-rc.d']
366
elif location.get('update-rc.d', None) and os.path.exists("/etc/init.d/%s" % self.name):
367
# service is managed by with SysV init scripts, but with update-rc.d
368
self.enable_cmd = location['update-rc.d']
369
elif location.get('systemctl', None):
371
# verify service is managed by systemd
372
rc, out, err = self.execute_command("%s list-unit-files" % (location['systemctl']))
374
# adjust the service name to account for template service unit files
375
index = self.name.find('@')
379
name = self.name[:index+1]
381
look_for = "%s.service" % name
382
for line in out.splitlines():
383
if line.startswith(look_for):
384
self.enable_cmd = location['systemctl']
387
# Locate a tool for runtime service management (start, stop etc.)
389
if location.get('service', None) and os.path.exists("/etc/init.d/%s" % self.name):
391
self.svc_cmd = location['service']
392
elif location.get('start', None) and os.path.exists("/etc/init/%s.conf" % self.name):
393
# upstart -- rather than being managed by one command, start/stop/restart are actual commands
396
# still a SysV init script, but /sbin/service isn't installed
397
for initdir in initpaths:
398
initscript = "%s/%s" % (initdir,self.name)
399
if os.path.isfile(initscript):
400
self.svc_initscript = initscript
402
# couldn't find anything yet, assume systemd
403
if self.svc_initscript is None:
404
if location.get('systemctl'):
405
self.svc_cmd = location['systemctl']
407
if self.svc_cmd is None and not self.svc_initscript:
408
self.module.fail_json(msg='cannot find \'service\' binary or init script for service, aborting')
410
if location.get('initctl', None):
411
self.svc_initctl = location['initctl']
413
def get_service_status(self):
414
self.action = "status"
415
rc, status_stdout, status_stderr = self.service_control()
417
# if we have decided the service is managed by upstart, we check for some additional output...
418
if self.svc_initctl and self.running is None:
419
# check the job status by upstart response
420
initctl_rc, initctl_status_stdout, initctl_status_stderr = self.execute_command("%s status %s" % (self.svc_initctl, self.name))
421
if initctl_status_stdout.find("stop/waiting") != -1:
423
elif initctl_status_stdout.find("start/running") != -1:
426
# if the job status is still not known check it by response code
427
# For reference, see:
428
# http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
429
if self.running is None:
430
if rc in [1, 2, 3, 4, 69]:
435
# if the job status is still not known check it by status output keywords
436
if self.running is None:
437
# first tranform the status output that could irritate keyword matching
438
cleanout = status_stdout.lower().replace(self.name.lower(), '')
439
if "stop" in cleanout:
441
elif "run" in cleanout and "not" in cleanout:
443
elif "run" in cleanout and "not" not in cleanout:
445
elif "start" in cleanout and "not" not in cleanout:
447
elif 'could not access pid file' in cleanout:
449
elif 'is dead and pid file exists' in cleanout:
451
elif 'dead but subsys locked' in cleanout:
453
elif 'dead but pid file exists' in cleanout:
456
# if the job status is still not known check it by special conditions
457
if self.running is None:
458
if self.name == 'iptables' and status_stdout.find("ACCEPT") != -1:
459
# iptables status command output is lame
460
# TODO: lookup if we can use a return code for this instead?
466
def service_enable(self):
468
if self.enable_cmd is None:
469
self.module.fail_json(msg='service name not recognized')
471
# FIXME: we use chkconfig or systemctl
472
# to decide whether to run the command here but need something
473
# similar for upstart
475
if self.enable_cmd.endswith("chkconfig"):
476
(rc, out, err) = self.execute_command("%s --list %s" % (self.enable_cmd, self.name))
477
if 'chkconfig --add %s' % self.name in err:
478
self.execute_command("%s --add %s" % (self.enable_cmd, self.name))
479
(rc, out, err) = self.execute_command("%s --list %s" % (self.enable_cmd, self.name))
480
if not self.name in out:
481
self.module.fail_json(msg="unknown service name")
482
state = out.split()[-1]
483
if self.enable and ( "3:on" in out and "5:on" in out ):
485
elif not self.enable and ( "3:off" in out and "5:off" in out ):
488
if self.enable_cmd.endswith("systemctl"):
489
(rc, out, err) = self.execute_command("%s show %s.service" % (self.enable_cmd, self.name))
491
d = dict(line.split('=', 1) for line in out.splitlines())
492
if "UnitFileState" in d:
493
if self.enable and d["UnitFileState"] == "enabled":
495
elif not self.enable and d["UnitFileState"] == "disabled":
497
elif not self.enable:
500
# we change argument depending on real binary used
501
# update-rc.d wants enable/disable while
502
# chkconfig wants on/off
503
# also, systemctl needs the argument order reversed
506
enable_disable = "enable"
509
enable_disable = "disable"
511
if self.enable_cmd.endswith("update-rc.d"):
512
args = (self.enable_cmd, self.name, enable_disable)
513
elif self.enable_cmd.endswith("systemctl"):
514
args = (self.enable_cmd, enable_disable, self.name + ".service")
516
args = (self.enable_cmd, self.name, on_off)
520
if self.module.check_mode and self.changed:
521
self.module.exit_json(changed=True)
523
return self.execute_command("%s %s %s" % args)
526
def service_control(self):
528
# Decide what command to run
530
arguments = self.arguments
532
if not self.svc_cmd.endswith("systemctl"):
533
# SysV take the form <cmd> <name> <action>
534
svc_cmd = "%s %s" % (self.svc_cmd, self.name)
536
# systemd commands take the form <cmd> <action> <name>
537
svc_cmd = self.svc_cmd
538
arguments = "%s %s" % (self.name, arguments)
539
elif self.svc_initscript:
541
svc_cmd = "%s" % self.svc_initscript
543
if self.action is not "restart":
546
rc_state, stdout, stderr = self.execute_command("%s %s %s" % (svc_cmd, self.action, arguments), daemonize=True)
549
rc_state, stdout, stderr = self.execute_command("%s %s %s" % (self.action, self.name, arguments), daemonize=True)
551
# not all services support restart. Do it the hard way.
554
rc1, stdout1, stderr1 = self.execute_command("%s %s %s" % (svc_cmd, 'stop', arguments), daemonize=True)
557
rc1, stdout1, stderr1 = self.execute_command("%s %s %s" % ('stop', self.name, arguments), daemonize=True)
561
rc2, stdout2, stderr2 = self.execute_command("%s %s %s" % (svc_cmd, 'start', arguments), daemonize=True)
564
rc2, stdout2, stderr2 = self.execute_command("%s %s %s" % ('start', self.name, arguments), daemonize=True)
566
# merge return information
567
if rc1 != 0 and rc2 == 0:
573
stdout = stdout1 + stdout2
574
stderr = stderr1 + stderr2
576
return(rc_state, stdout, stderr)
578
# ===========================================
581
class FreeBsdService(Service):
583
This is the FreeBSD Service manipulation class - it uses the /etc/rc.conf
584
file for controlling services started at boot and the 'service' binary to
585
check status and perform direct service manipulation.
591
def get_service_tools(self):
592
self.svc_cmd = self.module.get_bin_path('service', True)
595
self.module.fail_json(msg='unable to find service binary')
597
def get_service_status(self):
598
rc, stdout, stderr = self.execute_command("%s %s %s" % (self.svc_cmd, self.name, 'onestatus'))
604
def service_enable(self):
606
self.rcconf_value = "YES"
608
self.rcconf_value = "NO"
610
rcfiles = [ '/etc/rc.conf','/usr/local/etc/rc.conf' ]
611
for rcfile in rcfiles:
612
if os.path.isfile(rcfile):
613
self.rcconf_file = rcfile
615
self.rcconf_key = "%s_enable" % self.name
617
return self.service_enable_rcconf()
619
def service_control(self):
621
if self.action is "start":
622
self.action = "onestart"
623
if self.action is "stop":
624
self.action = "onestop"
625
if self.action is "reload":
626
self.action = "onereload"
628
return self.execute_command("%s %s %s" % (self.svc_cmd, self.name, self.action))
630
# ===========================================
633
class OpenBsdService(Service):
635
This is the OpenBSD Service manipulation class - it uses /etc/rc.d for
636
service control. Enabling a service is currently not supported because the
637
<service>_flags variable is not boolean, you should supply a rc.conf.local
638
file in some other way.
644
def get_service_tools(self):
647
rc_script = "%s/%s" % (rcdir, self.name)
648
if os.path.isfile(rc_script):
649
self.svc_cmd = rc_script
652
self.module.fail_json(msg='unable to find rc.d script')
654
def get_service_status(self):
655
rc, stdout, stderr = self.execute_command("%s %s" % (self.svc_cmd, 'check'))
661
def service_control(self):
662
return self.execute_command("%s %s" % (self.svc_cmd, self.action))
664
# ===========================================
667
class NetBsdService(Service):
669
This is the NetBSD Service manipulation class - it uses the /etc/rc.conf
670
file for controlling services started at boot, check status and perform
671
direct service manipulation. Init scripts in /etc/rcd are used for
672
controlling services (start/stop) as well as for controlling the current
679
def get_service_tools(self):
680
initpaths = [ '/etc/rc.d' ] # better: $rc_directories - how to get in here? Run: sh -c '. /etc/rc.conf ; echo $rc_directories'
682
for initdir in initpaths:
683
initscript = "%s/%s" % (initdir,self.name)
684
if os.path.isfile(initscript):
685
self.svc_initscript = initscript
687
if not self.svc_initscript:
688
self.module.fail_json(msg='unable to find rc.d script')
690
def service_enable(self):
692
self.rcconf_value = "YES"
694
self.rcconf_value = "NO"
696
rcfiles = [ '/etc/rc.conf' ] # Overkill?
697
for rcfile in rcfiles:
698
if os.path.isfile(rcfile):
699
self.rcconf_file = rcfile
701
self.rcconf_key = "%s" % self.name
703
return self.service_enable_rcconf()
705
def get_service_status(self):
706
self.svc_cmd = "%s" % self.svc_initscript
707
rc, stdout, stderr = self.execute_command("%s %s" % (self.svc_cmd, 'onestatus'))
713
def service_control(self):
714
if self.action is "start":
715
self.action = "onestart"
716
if self.action is "stop":
717
self.action = "onestop"
719
self.svc_cmd = "%s" % self.svc_initscript
720
return self.execute_command("%s %s" % (self.svc_cmd, self.action), daemonize=True)
722
# ===========================================
724
class SunOSService(Service):
726
This is the SunOS Service manipulation class - it uses the svcadm
727
command for controlling services, and svcs command for checking status.
728
It also tries to be smart about taking the service out of maintenance
734
def get_service_tools(self):
735
self.svcs_cmd = self.module.get_bin_path('svcs', True)
737
if not self.svcs_cmd:
738
self.module.fail_json(msg='unable to find svcs binary')
740
self.svcadm_cmd = self.module.get_bin_path('svcadm', True)
742
if not self.svcadm_cmd:
743
self.module.fail_json(msg='unable to find svcadm binary')
745
def get_service_status(self):
746
status = self.get_sunos_svcs_status()
747
# Only 'online' is considered properly running. Everything else is off
748
# or has some sort of problem.
749
if status == 'online':
754
def get_sunos_svcs_status(self):
755
rc, stdout, stderr = self.execute_command("%s %s" % (self.svcs_cmd, self.name))
758
self.module.fail_json(msg=stderr)
760
self.module.fail_json(msg=stdout)
762
lines = stdout.rstrip("\n").split("\n")
763
status = lines[-1].split(" ")[0]
764
# status is one of: online, offline, degraded, disabled, maintenance, uninitialized
768
def service_enable(self):
769
# Get current service enablement status
770
rc, stdout, stderr = self.execute_command("%s -l %s" % (self.svcs_cmd, self.name))
774
self.module.fail_json(msg=stderr)
776
self.module.fail_json(msg=stdout)
781
# look for enabled line, which could be one of:
782
# enabled true (temporary)
783
# enabled false (temporary)
786
for line in stdout.split("\n"):
787
if line.find("enabled") == 0:
788
if line.find("true") != -1:
790
if line.find("temporary") != -1:
793
startup_enabled = (enabled and not temporary) or (not enabled and temporary)
795
if self.enable and startup_enabled:
797
elif (not self.enable) and (not startup_enabled):
800
# Mark service as started or stopped (this will have the side effect of
801
# actually stopping or starting the service)
803
subcmd = "enable -rs"
805
subcmd = "disable -s"
807
rc, stdout, stderr = self.execute_command("%s %s %s" % (self.svcadm_cmd, subcmd, self.name))
811
self.module.fail_json(msg=stderr)
813
self.module.fail_json(msg=stdout)
818
def service_control(self):
819
status = self.get_sunos_svcs_status()
821
# if starting or reloading, clear maintenace states
822
if self.action in ['start', 'reload', 'restart'] and status in ['maintenance', 'degraded']:
823
rc, stdout, stderr = self.execute_command("%s clear %s" % (self.svcadm_cmd, self.name))
825
return rc, stdout, stderr
826
status = self.get_sunos_svcs_status()
828
if status in ['maintenance', 'degraded']:
829
self.module.fail_json(msg="Failed to bring service out of %s status." % status)
831
if self.action == 'start':
832
subcmd = "enable -rst"
833
elif self.action == 'stop':
834
subcmd = "disable -st"
835
elif self.action == 'reload':
837
elif self.action == 'restart' and status == 'online':
839
elif self.action == 'restart' and status != 'online':
840
subcmd = "enable -rst"
842
return self.execute_command("%s %s %s" % (self.svcadm_cmd, subcmd, self.name))
845
# ===========================================
849
module = AnsibleModule(
850
argument_spec = dict(
851
name = dict(required=True),
852
state = dict(choices=['running', 'started', 'stopped', 'restarted', 'reloaded']),
853
pattern = dict(required=False, default=None),
854
enabled = dict(choices=BOOLEANS, type='bool'),
855
arguments = dict(aliases=['args'], default=''),
857
supports_check_mode=True
860
service = Service(module)
862
if service.syslogging:
863
syslog.openlog('ansible-%s' % os.path.basename(__file__))
864
syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - platform %s' % service.platform)
865
if service.distribution:
866
syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - distribution %s' % service.distribution)
872
result['name'] = service.name
873
result['state'] = service.state
875
# Find service management tools
876
service.get_service_tools()
878
# Enable/disable service startup at boot if requested
879
if service.module.params['enabled'] is not None:
880
# FIXME: ideally this should detect if we need to toggle the enablement state, though
881
# it's unlikely the changed handler would need to fire in this case so it's a minor thing.
882
service.service_enable()
884
# Collect service status
887
service.get_service_status()
889
# Calculate if request will change service state
890
service.check_service_changed()
892
# Modify service state if necessary
893
(rc, out, err) = service.modify_service_state()
897
module.fail_json(msg=err)
899
module.fail_json(msg=out)
901
result['changed'] = service.changed
902
if service.module.params['enabled'] is not None:
903
result['enabled'] = service.module.params['enabled']
905
if not service.module.params['state']:
906
status = service.get_service_status()
908
result['state'] = 'absent'
909
elif status is False:
910
result['state'] = 'started'
912
result['state'] = 'stopped'
914
# as we may have just bounced the service the service command may not
915
# report accurate state at this moment so just show what we ran
916
if service.module.params['state'] in ['started','restarted','running']:
917
result['state'] = 'started'
919
result['state'] = 'stopped'
921
module.exit_json(**result)
923
# this is magic, see lib/ansible/module_common.py
924
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>