220 lines
6.1 KiB
Python
220 lines
6.1 KiB
Python
# Copyright 2021 Canonical Ltd.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
"""Abstractions for stopping, starting and managing system services via systemd.
|
|
|
|
This library assumes that your charm is running on a platform that uses systemd. E.g.,
|
|
Centos 7 or later, Ubuntu Xenial (16.04) or later.
|
|
|
|
For the most part, we transparently provide an interface to a commonly used selection of
|
|
systemd commands, with a few shortcuts baked in. For example, service_pause and
|
|
service_resume with run the mask/unmask and enable/disable invocations.
|
|
|
|
Example usage:
|
|
```python
|
|
from charms.operator_libs_linux.v0.systemd import service_running, service_reload
|
|
|
|
# Start a service
|
|
if not service_running("mysql"):
|
|
success = service_start("mysql")
|
|
|
|
# Attempt to reload a service, restarting if necessary
|
|
success = service_reload("nginx", restart_on_failure=True)
|
|
```
|
|
|
|
"""
|
|
|
|
import logging
|
|
import subprocess
|
|
|
|
__all__ = [ # Don't export `_systemctl`. (It's not the intended way of using this lib.)
|
|
"service_pause",
|
|
"service_reload",
|
|
"service_restart",
|
|
"service_resume",
|
|
"service_running",
|
|
"service_start",
|
|
"service_stop",
|
|
"daemon_reload",
|
|
]
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# The unique Charmhub library identifier, never change it
|
|
LIBID = "045b0d179f6b4514a8bb9b48aee9ebaf"
|
|
|
|
# Increment this major API version when introducing breaking changes
|
|
LIBAPI = 1
|
|
|
|
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
|
# to 0 if you are raising the major API version
|
|
LIBPATCH = 0
|
|
|
|
|
|
class SystemdError(Exception):
|
|
pass
|
|
|
|
|
|
def _popen_kwargs():
|
|
return dict(
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
bufsize=1,
|
|
universal_newlines=True,
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def _systemctl(
|
|
sub_cmd: str, service_name: str = None, now: bool = None, quiet: bool = None
|
|
) -> bool:
|
|
"""Control a system service.
|
|
|
|
Args:
|
|
sub_cmd: the systemctl subcommand to issue
|
|
service_name: the name of the service to perform the action on
|
|
now: passes the --now flag to the shell invocation.
|
|
quiet: passes the --quiet flag to the shell invocation.
|
|
"""
|
|
cmd = ["systemctl", sub_cmd]
|
|
|
|
if service_name is not None:
|
|
cmd.append(service_name)
|
|
if now is not None:
|
|
cmd.append("--now")
|
|
if quiet is not None:
|
|
cmd.append("--quiet")
|
|
if sub_cmd != "is-active":
|
|
logger.debug("Attempting to {} '{}' with command {}.".format(cmd, service_name, cmd))
|
|
else:
|
|
logger.debug("Checking if '{}' is active".format(service_name))
|
|
|
|
proc = subprocess.Popen(cmd, **_popen_kwargs())
|
|
last_line = ""
|
|
for line in iter(proc.stdout.readline, ""):
|
|
last_line = line
|
|
logger.debug(line)
|
|
|
|
proc.wait()
|
|
|
|
if sub_cmd == "is-active":
|
|
# If we are just checking whether a service is running, return True/False, rather
|
|
# than raising an error.
|
|
if proc.returncode < 1:
|
|
return True
|
|
if proc.returncode == 3: # Code returned when service is not active.
|
|
return False
|
|
|
|
if proc.returncode < 1:
|
|
return True
|
|
|
|
raise SystemdError(
|
|
"Could not {}{}: systemd output: {}".format(
|
|
sub_cmd, " {}".format(service_name) if service_name else "", last_line
|
|
)
|
|
)
|
|
|
|
|
|
def service_running(service_name: str) -> bool:
|
|
"""Determine whether a system service is running.
|
|
|
|
Args:
|
|
service_name: the name of the service
|
|
"""
|
|
return _systemctl("is-active", service_name, quiet=True)
|
|
|
|
|
|
def service_start(service_name: str) -> bool:
|
|
"""Start a system service.
|
|
|
|
Args:
|
|
service_name: the name of the service to stop
|
|
"""
|
|
return _systemctl("start", service_name)
|
|
|
|
|
|
def service_stop(service_name: str) -> bool:
|
|
"""Stop a system service.
|
|
|
|
Args:
|
|
service_name: the name of the service to stop
|
|
"""
|
|
return _systemctl("stop", service_name)
|
|
|
|
|
|
def service_restart(service_name: str) -> bool:
|
|
"""Restart a system service.
|
|
|
|
Args:
|
|
service_name: the name of the service to restart
|
|
"""
|
|
return _systemctl("restart", service_name)
|
|
|
|
|
|
def service_reload(service_name: str, restart_on_failure: bool = False) -> bool:
|
|
"""Reload a system service, optionally falling back to restart if reload fails.
|
|
|
|
Args:
|
|
service_name: the name of the service to reload
|
|
restart_on_failure: boolean indicating whether to fallback to a restart if the
|
|
reload fails.
|
|
"""
|
|
try:
|
|
return _systemctl("reload", service_name)
|
|
except SystemdError:
|
|
if restart_on_failure:
|
|
return _systemctl("restart", service_name)
|
|
else:
|
|
raise
|
|
|
|
|
|
def service_pause(service_name: str) -> bool:
|
|
"""Pause a system service.
|
|
|
|
Stop it, and prevent it from starting again at boot.
|
|
|
|
Args:
|
|
service_name: the name of the service to pause
|
|
"""
|
|
_systemctl("disable", service_name, now=True)
|
|
_systemctl("mask", service_name)
|
|
|
|
if not service_running(service_name):
|
|
return True
|
|
|
|
raise SystemdError("Attempted to pause '{}', but it is still running.".format(service_name))
|
|
|
|
|
|
def service_resume(service_name: str) -> bool:
|
|
"""Resume a system service.
|
|
|
|
Re-enable starting again at boot. Start the service.
|
|
|
|
Args:
|
|
service_name: the name of the service to resume
|
|
"""
|
|
_systemctl("unmask", service_name)
|
|
_systemctl("enable", service_name, now=True)
|
|
|
|
if service_running(service_name):
|
|
return True
|
|
|
|
raise SystemdError("Attempted to resume '{}', but it is not running.".format(service_name))
|
|
|
|
|
|
def daemon_reload() -> bool:
|
|
"""Reload systemd manager configuration."""
|
|
return _systemctl("daemon-reload")
|