Fix logging to stdout and file in classes/commands

Fix logging to console to depend on input CLI args. By default, keep
logging to stdout. Make --log-file argument working as well.

Use the input verbosity parameters as a controlling switch for
logs verbosity.

Evaluate log levels as:

  * 1 (WARNING+) - the default log level if neither -v nor --debug used
  * 2 (INFO+)    - applies if -v / --verbose
  * 4 (DEBUG+)   - applies if --debug, dumps command results to file,
    if --log-file is requested.
  * 5 (DEBUG+)   - applies if --debug and -v. Like the latter mode, but
    also dumps the executed commands results to console.

This is needed for better deployments troubleshootng.

Closes-Bug: #1799182

Change-Id: I653ac4cc520e40f3eb4d029e8c99ab482b17a859
Signed-off-by: Bogdan Dobrelya <bdobreli@redhat.com>
This commit is contained in:
Bogdan Dobrelya 2018-10-22 12:22:10 +02:00
parent 133bb38368
commit d3c83259bf
11 changed files with 291 additions and 157 deletions

View File

@ -13,21 +13,19 @@
'''Stable library interface to managing containers with paunch.''' '''Stable library interface to managing containers with paunch.'''
import json import json
import logging
import pbr.version import pbr.version
import yaml import yaml
from paunch.builder import compose1 from paunch.builder import compose1
from paunch.builder import podman from paunch.builder import podman
from paunch import runner from paunch import runner
from paunch.utils import common
__version__ = pbr.version.VersionInfo('paunch').version_string() __version__ = pbr.version.VersionInfo('paunch').version_string()
LOG = logging.getLogger(__name__)
def apply(config_id, config, managed_by, labels=None, cont_cmd=None, def apply(config_id, config, managed_by, labels=None, cont_cmd=None,
default_runtime='docker'): default_runtime='docker', log_level=None, log_file=None):
"""Execute supplied container configuration. """Execute supplied container configuration.
:param str config_id: Unique config ID, should not be re-used until any :param str config_id: Unique config ID, should not be re-used until any
@ -42,34 +40,41 @@ def apply(config_id, config, managed_by, labels=None, cont_cmd=None,
:param str cont_cmd: Optional override to the container command to run. :param str cont_cmd: Optional override to the container command to run.
:param str default_runtime: Optional override to the default runtime used :param str default_runtime: Optional override to the default runtime used
for containers. for containers.
:param int log_level: optional log level for loggers
:param int log_file: optional log file for messages
:returns (list, list, int) lists of stdout and stderr for each execution, :returns (list, list, int) lists of stdout and stderr for each execution,
and a single return code representing the and a single return code representing the
overall success of the apply. overall success of the apply.
:rtype: tuple :rtype: tuple
""" """
log = common.configure_logging(__name__, log_level, log_file)
if default_runtime == 'docker': if default_runtime == 'docker':
r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd) r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log)
builder = compose1.ComposeV1Builder( builder = compose1.ComposeV1Builder(
config_id=config_id, config_id=config_id,
config=config, config=config,
runner=r, runner=r,
labels=labels labels=labels,
log=log
) )
elif default_runtime == 'podman': elif default_runtime == 'podman':
r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd) r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log)
builder = podman.PodmanBuilder( builder = podman.PodmanBuilder(
config_id=config_id, config_id=config_id,
config=config, config=config,
runner=r, runner=r,
labels=labels labels=labels,
log=log
) )
else: else:
LOG.error("container runtime not supported: %s" % default_runtime) log.error("container runtime not supported: %s" % default_runtime)
return builder.apply() return builder.apply()
def cleanup(config_ids, managed_by, cont_cmd=None, default_runtime='docker'): def cleanup(config_ids, managed_by, cont_cmd=None, default_runtime='docker',
log_level=None, log_file=None):
"""Delete containers no longer applied, rename others to preferred name. """Delete containers no longer applied, rename others to preferred name.
:param list config_ids: List of config IDs still applied. All containers :param list config_ids: List of config IDs still applied. All containers
@ -80,19 +85,24 @@ def cleanup(config_ids, managed_by, cont_cmd=None, default_runtime='docker'):
:param str cont_cmd: Optional override to the container command to run. :param str cont_cmd: Optional override to the container command to run.
:param str default_runtime: Optional override to the default runtime used :param str default_runtime: Optional override to the default runtime used
for containers. for containers.
:param int log_level: optional log level for loggers
:param int log_file: optional log file for messages
""" """
log = common.configure_logging(__name__, log_level, log_file)
if default_runtime == 'docker': if default_runtime == 'docker':
r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd) r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log)
elif default_runtime == 'podman': elif default_runtime == 'podman':
r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd) r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log)
LOG.warning("paunch cleanup is partially supported with podman") log.warning("paunch cleanup is partially supported with podman")
else: else:
LOG.error("container runtime not supported: %s" % default_runtime) log.error("container runtime not supported: %s" % default_runtime)
r.delete_missing_configs(config_ids) r.delete_missing_configs(config_ids)
r.rename_containers() r.rename_containers()
def list(managed_by, cont_cmd=None, default_runtime='docker'): def list(managed_by, cont_cmd=None, default_runtime='docker', log_level=None,
log_file=None):
"""List all containers associated with all config IDs. """List all containers associated with all config IDs.
:param str managed_by: Name of the tool managing the containers. Only :param str managed_by: Name of the tool managing the containers. Only
@ -100,22 +110,27 @@ def list(managed_by, cont_cmd=None, default_runtime='docker'):
:param str cont_cmd: Optional override to the container command to run. :param str cont_cmd: Optional override to the container command to run.
:param str default_runtime: Optional override to the default runtime used :param str default_runtime: Optional override to the default runtime used
for containers. for containers.
:param int log_level: optional log level for loggers
:param int log_file: optional log file for messages
:returns a dict where the key is the config ID and the value is a list of :returns a dict where the key is the config ID and the value is a list of
'docker inspect' dicts for each container. 'docker inspect' dicts for each container.
:rtype: defaultdict(list) :rtype: defaultdict(list)
""" """
log = common.configure_logging(__name__, log_level, log_file)
if default_runtime == 'docker': if default_runtime == 'docker':
r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd) r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log)
elif default_runtime == 'podman': elif default_runtime == 'podman':
r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd) r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log)
else: else:
LOG.error("container runtime not supported: %s" % default_runtime) log.error("container runtime not supported: %s" % default_runtime)
return r.list_configs() return r.list_configs()
def debug(config_id, container_name, action, config, managed_by, labels=None, def debug(config_id, container_name, action, config, managed_by, labels=None,
cont_cmd=None, default_runtime='docker'): cont_cmd=None, default_runtime='docker', log_level=None,
log_file=None):
"""Execute supplied container configuration. """Execute supplied container configuration.
:param str config_id: Unique config ID, should not be re-used until any :param str config_id: Unique config ID, should not be re-used until any
@ -133,30 +148,35 @@ def debug(config_id, container_name, action, config, managed_by, labels=None,
:param str cont_cmd: Optional override to the container command to run. :param str cont_cmd: Optional override to the container command to run.
:param str default_runtime: Optional override to the default runtime used :param str default_runtime: Optional override to the default runtime used
for containers. for containers.
:param int log_level: optional log level for loggers
:param int log_file: optional log file for messages
:returns integer return value from running command or failure for any :returns integer return value from running command or failure for any
other reason. other reason.
:rtype: int :rtype: int
""" """
log = common.configure_logging(__name__, log_level, log_file)
if default_runtime == 'docker': if default_runtime == 'docker':
r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd) r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log)
builder = compose1.ComposeV1Builder( builder = compose1.ComposeV1Builder(
config_id=config_id, config_id=config_id,
config=config, config=config,
runner=r, runner=r,
labels=labels labels=labels,
log=log
) )
elif default_runtime == 'podman': elif default_runtime == 'podman':
r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd) r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log)
builder = podman.PodmanBuilder( builder = podman.PodmanBuilder(
config_id=config_id, config_id=config_id,
config=config, config=config,
runner=r, runner=r,
labels=labels labels=labels,
log=log
) )
else: else:
LOG.error("container runtime not supported: %s" % default_runtime) log.error("container runtime not supported: %s" % default_runtime)
if action == 'print-cmd': if action == 'print-cmd':
cmd = [ cmd = [
r.cont_cmd, r.cont_cmd,
@ -174,7 +194,7 @@ def debug(config_id, container_name, action, config, managed_by, labels=None,
r.unique_container_name(container_name) r.unique_container_name(container_name)
] ]
builder.container_run_args(cmd, container_name) builder.container_run_args(cmd, container_name)
return r.execute_interactive(cmd) return r.execute_interactive(cmd, log)
elif action == 'dump-yaml': elif action == 'dump-yaml':
print(yaml.safe_dump(config, default_flow_style=False)) print(yaml.safe_dump(config, default_flow_style=False))
elif action == 'dump-json': elif action == 'dump-json':
@ -184,7 +204,8 @@ def debug(config_id, container_name, action, config, managed_by, labels=None,
'"print-cmd", or "run"') '"print-cmd", or "run"')
def delete(config_ids, managed_by, cont_cmd=None, default_runtime='docker'): def delete(config_ids, managed_by, cont_cmd=None, default_runtime='docker',
log_level=None, log_file=None):
"""Delete containers with the specified config IDs. """Delete containers with the specified config IDs.
:param list config_ids: List of config IDs to delete the containers for. :param list config_ids: List of config IDs to delete the containers for.
@ -194,15 +215,17 @@ def delete(config_ids, managed_by, cont_cmd=None, default_runtime='docker'):
:param str default_runtime: Optional override to the default runtime used :param str default_runtime: Optional override to the default runtime used
for containers. for containers.
""" """
log = common.configure_logging(__name__, log_level, log_file)
if not config_ids: if not config_ids:
LOG.warn('No config IDs specified') log.warn('No config IDs specified')
if default_runtime == 'docker': if default_runtime == 'docker':
r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd) r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log)
elif default_runtime == 'podman': elif default_runtime == 'podman':
r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd) r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log)
LOG.warning("paunch cleanup is partially supported with podman") log.warning("paunch cleanup is partially supported with podman")
else: else:
LOG.error("container runtime not supported: %s" % default_runtime) log.error("container runtime not supported: %s" % default_runtime)
for conf_id in config_ids: for conf_id in config_ids:
r.remove_containers(conf_id) r.remove_containers(conf_id)

View File

@ -12,22 +12,22 @@
# #
import json import json
import logging
import re import re
import tenacity import tenacity
from paunch.utils import common
from paunch.utils import systemd from paunch.utils import systemd
LOG = logging.getLogger(__name__)
class BaseBuilder(object): class BaseBuilder(object):
def __init__(self, config_id, config, runner, labels): def __init__(self, config_id, config, runner, labels, log=None):
self.config_id = config_id self.config_id = config_id
self.config = config self.config = config
self.labels = labels self.labels = labels
self.runner = runner self.runner = runner
# Leverage pre-configured logger
self.log = log or common.configure_logging(__name__)
def apply(self): def apply(self):
@ -46,7 +46,7 @@ class BaseBuilder(object):
desired_names = set([cn[-1] for cn in container_names]) desired_names = set([cn[-1] for cn in container_names])
for container in sorted(self.config, key=key_fltr): for container in sorted(self.config, key=key_fltr):
LOG.debug("Running container: %s" % container) self.log.debug("Running container: %s" % container)
cconfig = self.config[container] cconfig = self.config[container]
action = cconfig.get('action', 'run') action = cconfig.get('action', 'run')
restart = cconfig.get('restart', 'none') restart = cconfig.get('restart', 'none')
@ -59,7 +59,8 @@ class BaseBuilder(object):
if action == 'run': if action == 'run':
if container in desired_names: if container in desired_names:
LOG.debug('Skipping existing container: %s' % container) self.log.debug('Skipping existing container: %s' %
container)
continue continue
cmd = [ cmd = [
@ -74,23 +75,26 @@ class BaseBuilder(object):
cmd = [self.runner.cont_cmd, 'exec'] cmd = [self.runner.cont_cmd, 'exec']
self.cont_exec_args(cmd, container) self.cont_exec_args(cmd, container)
(cmd_stdout, cmd_stderr, returncode) = self.runner.execute(cmd) (cmd_stdout, cmd_stderr, returncode) = self.runner.execute(
cmd, self.log)
if cmd_stdout: if cmd_stdout:
stdout.append(cmd_stdout) stdout.append(cmd_stdout)
if cmd_stderr: if cmd_stderr:
stderr.append(cmd_stderr) stderr.append(cmd_stderr)
if returncode not in exit_codes: if returncode not in exit_codes:
LOG.error("Error running %s. [%s]\n" % (cmd, returncode)) self.log.error("Error running %s. [%s]\n" % (cmd, returncode))
LOG.error("stdout: %s" % cmd_stdout) self.log.error("stdout: %s" % cmd_stdout)
LOG.error("stderr: %s" % cmd_stderr) self.log.error("stderr: %s" % cmd_stderr)
deploy_status_code = returncode deploy_status_code = returncode
else: else:
LOG.debug('Completed $ %s' % ' '.join(cmd)) self.log.debug('Completed $ %s' % ' '.join(cmd))
LOG.info("stdout: %s" % cmd_stdout) self.log.info("stdout: %s" % cmd_stdout)
LOG.info("stderr: %s" % cmd_stderr) self.log.info("stderr: %s" % cmd_stderr)
if systemd_managed: if systemd_managed:
systemd.service_create(container_name, cconfig) systemd.service_create(container=container_name,
cconfig=cconfig,
log=self.log)
return stdout, stderr, deploy_status_code return stdout, stderr, deploy_status_code
def delete_missing_and_updated(self): def delete_missing_and_updated(self):
@ -100,15 +104,15 @@ class BaseBuilder(object):
# if the desired name is not in the config, delete it # if the desired name is not in the config, delete it
if cn[-1] not in self.config: if cn[-1] not in self.config:
LOG.debug("Deleting container (removed): %s" % container) self.log.debug("Deleting container (removed): %s" % container)
self.runner.remove_container(container) self.runner.remove_container(container)
continue continue
ex_data_str = self.runner.inspect( ex_data_str = self.runner.inspect(
container, '{{index .Config.Labels "config_data"}}') container, '{{index .Config.Labels "config_data"}}')
if not ex_data_str: if not ex_data_str:
LOG.debug("Deleting container (no config_data): %s" self.log.debug("Deleting container (no config_data): %s"
% container) % container)
self.runner.remove_container(container) self.runner.remove_container(container)
continue continue
@ -119,8 +123,8 @@ class BaseBuilder(object):
new_data = self.config.get(cn[-1]) new_data = self.config.get(cn[-1])
if new_data != ex_data: if new_data != ex_data:
LOG.debug("Deleting container (changed config_data): %s" self.log.debug("Deleting container (changed config_data): %s"
% container) % container)
self.runner.remove_container(container) self.runner.remove_container(container)
# deleting containers is an opportunity for renames to their # deleting containers is an opportunity for renames to their
@ -208,13 +212,14 @@ class BaseBuilder(object):
returncode = e.rc returncode = e.rc
cmd_stdout = e.stdout cmd_stdout = e.stdout
cmd_stderr = e.stderr cmd_stderr = e.stderr
LOG.error("Error pulling %s. [%s]\n" % (image, returncode)) self.log.error("Error pulling %s. [%s]\n" %
LOG.error("stdout: %s" % e.stdout) (image, returncode))
LOG.error("stderr: %s" % e.stderr) self.log.error("stdout: %s" % e.stdout)
self.log.error("stderr: %s" % e.stderr)
else: else:
LOG.debug('Pulled %s' % image) self.log.debug('Pulled %s' % image)
LOG.info("stdout: %s" % cmd_stdout) self.log.info("stdout: %s" % cmd_stdout)
LOG.info("stderr: %s" % cmd_stderr) self.log.info("stderr: %s" % cmd_stderr)
if cmd_stdout: if cmd_stdout:
stdout.append(cmd_stdout) stdout.append(cmd_stdout)
@ -230,7 +235,7 @@ class BaseBuilder(object):
) )
def _pull(self, image): def _pull(self, image):
cmd = [self.runner.cont_cmd, 'pull', image] cmd = [self.runner.cont_cmd, 'pull', image]
(stdout, stderr, rc) = self.runner.execute(cmd) (stdout, stderr, rc) = self.runner.execute(cmd, self.log)
if rc != 0: if rc != 0:
raise PullException(stdout, stderr, rc) raise PullException(stdout, stderr, rc)
return stdout, stderr return stdout, stderr

View File

@ -11,18 +11,14 @@
# under the License. # under the License.
# #
import logging
from paunch.builder import base from paunch.builder import base
LOG = logging.getLogger(__name__)
class ComposeV1Builder(base.BaseBuilder): class ComposeV1Builder(base.BaseBuilder):
def __init__(self, config_id, config, runner, labels=None): def __init__(self, config_id, config, runner, labels=None, log=None):
super(ComposeV1Builder, self).__init__(config_id, config, runner, super(ComposeV1Builder, self).__init__(config_id, config, runner,
labels) labels, log)
def container_run_args(self, cmd, container): def container_run_args(self, cmd, container):
cconfig = self.config[container] cconfig = self.config[container]

View File

@ -11,18 +11,14 @@
# under the License. # under the License.
# #
import logging
from paunch.builder import base from paunch.builder import base
LOG = logging.getLogger(__name__)
class PodmanBuilder(base.BaseBuilder): class PodmanBuilder(base.BaseBuilder):
def __init__(self, config_id, config, runner, labels=None): def __init__(self, config_id, config, runner, labels=None, log=None):
super(PodmanBuilder, self).__init__(config_id, config, runner, super(PodmanBuilder, self).__init__(config_id, config, runner,
labels) labels, log)
def container_run_args(self, cmd, container): def container_run_args(self, cmd, container):
cconfig = self.config[container] cconfig = self.config[container]

View File

@ -12,7 +12,6 @@
# #
import collections import collections
import logging
from cliff import command from cliff import command
from cliff import lister from cliff import lister
@ -24,7 +23,7 @@ import paunch
class Apply(command.Command): class Apply(command.Command):
log = logging.getLogger(__name__) log = None
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(Apply, self).get_parser(prog_name) parser = super(Apply, self).get_parser(prog_name)
@ -66,6 +65,11 @@ class Apply(command.Command):
def take_action(self, parsed_args): def take_action(self, parsed_args):
# takes 1, or 2 if --verbose, or 4 - 5 if --debug
log_level = (self.app_args.verbose_level +
int(self.app_args.debug) * 3)
self.log = paunch.utils.common.configure_logging(
__name__, log_level, self.app_args.log_file)
labels = collections.OrderedDict() labels = collections.OrderedDict()
for l in parsed_args.labels: for l in parsed_args.labels:
k, v = l.split(('='), 1) k, v = l.split(('='), 1)
@ -79,7 +83,9 @@ class Apply(command.Command):
config, config,
managed_by='paunch', managed_by='paunch',
labels=labels, labels=labels,
default_runtime=parsed_args.default_runtime default_runtime=parsed_args.default_runtime,
log_level=log_level,
log_file=self.app_args.log_file
) )
return rc return rc
@ -87,7 +93,7 @@ class Apply(command.Command):
class Cleanup(command.Command): class Cleanup(command.Command):
log = logging.getLogger(__name__) log = None
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(Cleanup, self).get_parser(prog_name) parser = super(Cleanup, self).get_parser(prog_name)
@ -108,15 +114,22 @@ class Cleanup(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
# takes 1, or 2 if --verbose, or 4 - 5 if --debug
log_level = (self.app_args.verbose_level +
int(self.app_args.debug) * 3)
self.log = paunch.utils.common.configure_logging(
__name__, log_level, self.app_args.log_file)
paunch.cleanup( paunch.cleanup(
parsed_args.config_id, parsed_args.config_id,
managed_by=parsed_args.managed_by managed_by=parsed_args.managed_by,
log_level=log_level,
log_file=self.app_args.log_file
) )
class Delete(command.Command): class Delete(command.Command):
log = logging.getLogger(__name__) log = None
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(Delete, self).get_parser(prog_name) parser = super(Delete, self).get_parser(prog_name)
@ -136,12 +149,22 @@ class Delete(command.Command):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
paunch.delete(parsed_args.config_id, parsed_args.managed_by) # takes 1, or 2 if --verbose, or 4 - 5 if --debug
log_level = (self.app_args.verbose_level +
int(self.app_args.debug) * 3)
self.log = paunch.utils.common.configure_logging(
__name__, log_level, self.app_args.log_file)
paunch.delete(
parsed_args.config_id,
parsed_args.managed_by,
log_level=log_level,
log_file=self.app_args.log_file
)
class Debug(command.Command): class Debug(command.Command):
log = logging.getLogger(__name__) log = None
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(Debug, self).get_parser(prog_name) parser = super(Debug, self).get_parser(prog_name)
@ -223,6 +246,11 @@ class Debug(command.Command):
def take_action(self, parsed_args): def take_action(self, parsed_args):
# takes 1, or 2 if --verbose, or 4 - 5 if --debug
log_level = (self.app_args.verbose_level +
int(self.app_args.debug) * 3)
self.log = paunch.utils.common.configure_logging(
__name__, log_level, self.app_args.log_file)
labels = collections.OrderedDict() labels = collections.OrderedDict()
for l in parsed_args.labels: for l in parsed_args.labels:
k, v = l.split(('='), 1) k, v = l.split(('='), 1)
@ -263,13 +291,15 @@ class Debug(command.Command):
parsed_args.action, parsed_args.action,
cconfig, cconfig,
parsed_args.managed_by, parsed_args.managed_by,
labels=labels labels=labels,
log_level=log_level,
log_file=self.app_args.log_file
) )
class List(lister.Lister): class List(lister.Lister):
log = logging.getLogger(__name__) log = None
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(List, self).get_parser(prog_name) parser = super(List, self).get_parser(prog_name)
@ -283,7 +313,16 @@ class List(lister.Lister):
return parser return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
configs = paunch.list(parsed_args.managed_by) # takes 1, or 2 if --verbose, or 4 - 5 if --debug
log_level = (self.app_args.verbose_level +
int(self.app_args.debug) * 3)
self.log = paunch.utils.common.configure_logging(
__name__, log_level, self.app_args.log_file)
configs = paunch.list(
parsed_args.managed_by,
log_level=log_level,
log_file=self.app_args.log_file
)
columns = [ columns = [
'config', 'config',
'container', 'container',

View File

@ -13,36 +13,40 @@
import collections import collections
import json import json
import logging
import random import random
import string import string
import subprocess import subprocess
from paunch.utils import common
from paunch.utils import systemd from paunch.utils import systemd
LOG = logging.getLogger(__name__)
class BaseRunner(object): class BaseRunner(object):
def __init__(self, managed_by, cont_cmd): def __init__(self, managed_by, cont_cmd, log=None):
self.managed_by = managed_by self.managed_by = managed_by
self.cont_cmd = cont_cmd self.cont_cmd = cont_cmd
# Leverage pre-configured logger
self.log = log or common.configure_logging(__name__)
@staticmethod @staticmethod
def execute(cmd): def execute(cmd, log=None):
LOG.debug('$ %s' % ' '.join(cmd)) if not log:
log = common.configure_logging(__name__)
log.debug('$ %s' % ' '.join(cmd))
subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
cmd_stdout, cmd_stderr = subproc.communicate() cmd_stdout, cmd_stderr = subproc.communicate()
LOG.debug(cmd_stdout) log.debug(cmd_stdout)
LOG.debug(cmd_stderr) log.debug(cmd_stderr)
return (cmd_stdout.decode('utf-8'), return (cmd_stdout.decode('utf-8'),
cmd_stderr.decode('utf-8'), cmd_stderr.decode('utf-8'),
subproc.returncode) subproc.returncode)
@staticmethod @staticmethod
def execute_interactive(cmd): def execute_interactive(cmd, log=None):
LOG.debug('$ %s' % ' '.join(cmd)) if not log:
log = common.configure_logging(__name__)
log.debug('$ %s' % ' '.join(cmd))
return subprocess.call(cmd) return subprocess.call(cmd)
def current_config_ids(self): def current_config_ids(self):
@ -52,7 +56,7 @@ class BaseRunner(object):
'--filter', 'label=managed_by=%s' % self.managed_by, '--filter', 'label=managed_by=%s' % self.managed_by,
'--format', '{{.Label "config_id"}}' '--format', '{{.Label "config_id"}}'
] ]
cmd_stdout, cmd_stderr, returncode = self.execute(cmd) cmd_stdout, cmd_stderr, returncode = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
return set() return set()
return set(cmd_stdout.split()) return set(cmd_stdout.split())
@ -63,7 +67,7 @@ class BaseRunner(object):
'--filter', 'label=managed_by=%s' % self.managed_by, '--filter', 'label=managed_by=%s' % self.managed_by,
'--filter', 'label=config_id=%s' % conf_id '--filter', 'label=config_id=%s' % conf_id
] ]
cmd_stdout, cmd_stderr, returncode = self.execute(cmd) cmd_stdout, cmd_stderr, returncode = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
return [] return []
@ -75,7 +79,7 @@ class BaseRunner(object):
cmd.append('--format') cmd.append('--format')
cmd.append(format) cmd.append(format)
cmd.append(name) cmd.append(name)
(cmd_stdout, cmd_stderr, returncode) = self.execute(cmd) (cmd_stdout, cmd_stderr, returncode) = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
return return
try: try:
@ -84,7 +88,8 @@ class BaseRunner(object):
else: else:
return json.loads(cmd_stdout)[0] return json.loads(cmd_stdout)[0]
except Exception as e: except Exception as e:
LOG.error('Problem parsing %s inspect: %s' % (e, self.cont_cmd)) self.log.error('Problem parsing %s inspect: %s' %
(e, self.cont_cmd))
def unique_container_name(self, container): def unique_container_name(self, container):
container_name = container container_name = container
@ -106,7 +111,7 @@ class BaseRunner(object):
'--format', '--format',
'{{.Names}}' '{{.Names}}'
] ]
(cmd_stdout, cmd_stderr, returncode) = self.execute(cmd) (cmd_stdout, cmd_stderr, returncode) = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
return container return container
names = cmd_stdout.split() names = cmd_stdout.split()
@ -120,7 +125,8 @@ class BaseRunner(object):
for conf_id in self.current_config_ids(): for conf_id in self.current_config_ids():
if conf_id not in config_ids: if conf_id not in config_ids:
LOG.debug('%s no longer exists, deleting containers' % conf_id) self.log.debug('%s no longer exists, deleting containers' %
conf_id)
self.remove_containers(conf_id) self.remove_containers(conf_id)
def list_configs(self): def list_configs(self):
@ -143,7 +149,7 @@ class BaseRunner(object):
cmd.extend(( cmd.extend((
'--format', '{{.Names}} {{.Label "container_name"}}' '--format', '{{.Names}} {{.Label "container_name"}}'
)) ))
cmd_stdout, cmd_stderr, returncode = self.execute(cmd) cmd_stdout, cmd_stderr, returncode = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
return return
for line in cmd_stdout.split("\n"): for line in cmd_stdout.split("\n"):
@ -156,12 +162,12 @@ class BaseRunner(object):
def remove_container(self, container): def remove_container(self, container):
if self.cont_cmd == 'podman': if self.cont_cmd == 'podman':
systemd.service_delete(container) systemd.service_delete(container=container, log=self.log)
cmd = [self.cont_cmd, 'rm', '-f', container] cmd = [self.cont_cmd, 'rm', '-f', container]
cmd_stdout, cmd_stderr, returncode = self.execute(cmd) cmd_stdout, cmd_stderr, returncode = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
LOG.error('Error removing container: %s' % container) self.log.error('Error removing container: %s' % container)
LOG.error(cmd_stderr) self.log.error(cmd_stderr)
def rename_containers(self): def rename_containers(self):
current_containers = [] current_containers = []
@ -181,36 +187,36 @@ class BaseRunner(object):
for current, desired in sorted(need_renaming.items()): for current, desired in sorted(need_renaming.items()):
if desired in current_containers: if desired in current_containers:
LOG.info('Cannot rename "%s" since "%s" still exists' % ( self.log.info('Cannot rename "%s" since "%s" still exists' % (
current, desired)) current, desired))
else: else:
LOG.info('Renaming "%s" to "%s"' % (current, desired)) self.log.info('Renaming "%s" to "%s"' % (current, desired))
self.rename_container(current, desired) self.rename_container(current, desired)
current_containers.append(desired) current_containers.append(desired)
class DockerRunner(BaseRunner): class DockerRunner(BaseRunner):
def __init__(self, managed_by, cont_cmd=None): def __init__(self, managed_by, cont_cmd=None, log=None):
cont_cmd = cont_cmd or 'docker' cont_cmd = cont_cmd or 'docker'
super(DockerRunner, self).__init__(managed_by, cont_cmd) super(DockerRunner, self).__init__(managed_by, cont_cmd, log)
def rename_container(self, container, name): def rename_container(self, container, name):
cmd = [self.cont_cmd, 'rename', container, name] cmd = [self.cont_cmd, 'rename', container, name]
cmd_stdout, cmd_stderr, returncode = self.execute(cmd) cmd_stdout, cmd_stderr, returncode = self.execute(cmd, self.log)
if returncode != 0: if returncode != 0:
LOG.error('Error renaming container: %s' % container) self.log.error('Error renaming container: %s' % container)
LOG.error(cmd_stderr) self.log.error(cmd_stderr)
class PodmanRunner(BaseRunner): class PodmanRunner(BaseRunner):
def __init__(self, managed_by, cont_cmd=None): def __init__(self, managed_by, cont_cmd=None, log=None):
cont_cmd = cont_cmd or 'podman' cont_cmd = cont_cmd or 'podman'
super(PodmanRunner, self).__init__(managed_by, cont_cmd) super(PodmanRunner, self).__init__(managed_by, cont_cmd, log)
def rename_container(self, container, name): def rename_container(self, container, name):
# TODO(emilien) podman doesn't support rename, we'll handle it # TODO(emilien) podman doesn't support rename, we'll handle it
# in paunch itself, probably. # in paunch itself, probably.
LOG.warning("container renaming isn't supported by podman") self.log.warning("container renaming isn't supported by podman")
pass pass

View File

@ -94,40 +94,43 @@ class TestBaseBuilder(base.TestCase):
# inspect existing image centos:6 # inspect existing image centos:6
mock.call( mock.call(
['docker', 'inspect', '--type', 'image', ['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:6'] '--format', 'exists', 'centos:6'], mock.ANY
), ),
# inspect and pull missing image centos:7 # inspect and pull missing image centos:7
mock.call( mock.call(
['docker', 'inspect', '--type', 'image', ['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:7'] '--format', 'exists', 'centos:7'], mock.ANY
), ),
# first pull attempt fails # first pull attempt fails
mock.call( mock.call(
['docker', 'pull', 'centos:7'] ['docker', 'pull', 'centos:7'], mock.ANY
), ),
# second pull attempt succeeds # second pull attempt succeeds
mock.call( mock.call(
['docker', 'pull', 'centos:7'] ['docker', 'pull', 'centos:7'], mock.ANY
), ),
# ps for delete_missing_and_updated container_names # ps for delete_missing_and_updated container_names
mock.call( mock.call(
['docker', 'ps', '-a', ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester', '--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=foo', '--filter', 'label=config_id=foo',
'--format', '{{.Names}} {{.Label "container_name"}}'] '--format', '{{.Names}} {{.Label "container_name"}}'],
mock.ANY
), ),
# ps for after delete_missing_and_updated renames # ps for after delete_missing_and_updated renames
mock.call( mock.call(
['docker', 'ps', '-a', ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester', '--filter', 'label=managed_by=tester',
'--format', '{{.Names}} {{.Label "container_name"}}'] '--format', '{{.Names}} {{.Label "container_name"}}'],
mock.ANY
), ),
# ps to only create containers which don't exist # ps to only create containers which don't exist
mock.call( mock.call(
['docker', 'ps', '-a', ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester', '--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=foo', '--filter', 'label=config_id=foo',
'--format', '{{.Names}} {{.Label "container_name"}}'] '--format', '{{.Names}} {{.Label "container_name"}}'],
mock.ANY
), ),
# run one # run one
mock.call( mock.call(
@ -136,7 +139,7 @@ class TestBaseBuilder(base.TestCase):
'--label', 'container_name=one', '--label', 'container_name=one',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['one']), '--label', 'config_data=%s' % json.dumps(config['one']),
'--detach=true', 'centos:7'] '--detach=true', 'centos:7'], mock.ANY
), ),
# run two # run two
mock.call( mock.call(
@ -145,7 +148,7 @@ class TestBaseBuilder(base.TestCase):
'--label', 'container_name=two', '--label', 'container_name=two',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['two']), '--label', 'config_data=%s' % json.dumps(config['two']),
'--detach=true', 'centos:7'] '--detach=true', 'centos:7'], mock.ANY
), ),
# run three # run three
mock.call( mock.call(
@ -154,7 +157,7 @@ class TestBaseBuilder(base.TestCase):
'--label', 'container_name=three', '--label', 'container_name=three',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['three']), '--label', 'config_data=%s' % json.dumps(config['three']),
'--detach=true', 'centos:6'] '--detach=true', 'centos:6'], mock.ANY
), ),
# run four # run four
mock.call( mock.call(
@ -163,11 +166,11 @@ class TestBaseBuilder(base.TestCase):
'--label', 'container_name=four', '--label', 'container_name=four',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['four']), '--label', 'config_data=%s' % json.dumps(config['four']),
'--detach=true', 'centos:7'] '--detach=true', 'centos:7'], mock.ANY
), ),
# execute within four # execute within four
mock.call( mock.call(
['docker', 'exec', 'four-12345678', 'ls', '-l', '/'] ['docker', 'exec', 'four-12345678', 'ls', '-l', '/'], mock.ANY
), ),
]) ])
@ -248,39 +251,42 @@ three-12345678 three''', '', 0),
# inspect image centos:7 # inspect image centos:7
mock.call( mock.call(
['docker', 'inspect', '--type', 'image', ['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:7'] '--format', 'exists', 'centos:7'], mock.ANY
), ),
# ps for delete_missing_and_updated container_names # ps for delete_missing_and_updated container_names
mock.call( mock.call(
['docker', 'ps', '-a', ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester', '--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=foo', '--filter', 'label=config_id=foo',
'--format', '{{.Names}} {{.Label "container_name"}}'] '--format', '{{.Names}} {{.Label "container_name"}}'],
mock.ANY
), ),
# rm containers not in config # rm containers not in config
mock.call(['docker', 'rm', '-f', 'five']), mock.call(['docker', 'rm', '-f', 'five'], mock.ANY),
mock.call(['docker', 'rm', '-f', 'six']), mock.call(['docker', 'rm', '-f', 'six'], mock.ANY),
# rm two, changed config # rm two, changed config
mock.call(['docker', 'inspect', '--type', 'container', mock.call(['docker', 'inspect', '--type', 'container',
'--format', '{{index .Config.Labels "config_data"}}', '--format', '{{index .Config.Labels "config_data"}}',
'two-12345678']), 'two-12345678'], mock.ANY),
mock.call(['docker', 'rm', '-f', 'two-12345678']), mock.call(['docker', 'rm', '-f', 'two-12345678'], mock.ANY),
# check three, config hasn't changed # check three, config hasn't changed
mock.call(['docker', 'inspect', '--type', 'container', mock.call(['docker', 'inspect', '--type', 'container',
'--format', '{{index .Config.Labels "config_data"}}', '--format', '{{index .Config.Labels "config_data"}}',
'three-12345678']), 'three-12345678'], mock.ANY),
# ps for after delete_missing_and_updated renames # ps for after delete_missing_and_updated renames
mock.call( mock.call(
['docker', 'ps', '-a', ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester', '--filter', 'label=managed_by=tester',
'--format', '{{.Names}} {{.Label "container_name"}}'] '--format', '{{.Names}} {{.Label "container_name"}}'],
mock.ANY
), ),
# ps to only create containers which don't exist # ps to only create containers which don't exist
mock.call( mock.call(
['docker', 'ps', '-a', ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester', '--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=foo', '--filter', 'label=config_id=foo',
'--format', '{{.Names}} {{.Label "container_name"}}'] '--format', '{{.Names}} {{.Label "container_name"}}'],
mock.ANY
), ),
# run one # run one
mock.call( mock.call(
@ -289,7 +295,7 @@ three-12345678 three''', '', 0),
'--label', 'container_name=one', '--label', 'container_name=one',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['one']), '--label', 'config_data=%s' % json.dumps(config['one']),
'--detach=true', 'centos:7'] '--detach=true', 'centos:7'], mock.ANY
), ),
# run two # run two
mock.call( mock.call(
@ -298,7 +304,7 @@ three-12345678 three''', '', 0),
'--label', 'container_name=two', '--label', 'container_name=two',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['two']), '--label', 'config_data=%s' % json.dumps(config['two']),
'--detach=true', 'centos:7'] '--detach=true', 'centos:7'], mock.ANY
), ),
# don't run three, its already running # don't run three, its already running
# run four # run four
@ -308,11 +314,11 @@ three-12345678 three''', '', 0),
'--label', 'container_name=four', '--label', 'container_name=four',
'--label', 'managed_by=tester', '--label', 'managed_by=tester',
'--label', 'config_data=%s' % json.dumps(config['four']), '--label', 'config_data=%s' % json.dumps(config['four']),
'--detach=true', 'centos:7'] '--detach=true', 'centos:7'], mock.ANY
), ),
# execute within four # execute within four
mock.call( mock.call(
['docker', 'exec', 'four-12345678', 'ls', '-l', '/'] ['docker', 'exec', 'four-12345678', 'ls', '-l', '/'], mock.ANY
), ),
]) ])
@ -370,15 +376,15 @@ three-12345678 three''', '', 0),
# inspect existing image centos:6 # inspect existing image centos:6
mock.call( mock.call(
['docker', 'inspect', '--type', 'image', ['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:6'] '--format', 'exists', 'centos:6'], mock.ANY
), ),
# inspect and pull missing image centos:7 # inspect and pull missing image centos:7
mock.call( mock.call(
['docker', 'inspect', '--type', 'image', ['docker', 'inspect', '--type', 'image',
'--format', 'exists', 'centos:7'] '--format', 'exists', 'centos:7'], mock.ANY
), ),
mock.call( mock.call(
['docker', 'pull', 'centos:7'] ['docker', 'pull', 'centos:7'], mock.ANY
), ),
]) ])

View File

@ -24,12 +24,13 @@ class TestPaunch(base.TestCase):
@mock.patch('paunch.runner.DockerRunner', autospec=True) @mock.patch('paunch.runner.DockerRunner', autospec=True)
def test_apply(self, runner, builder): def test_apply(self, runner, builder):
paunch.apply('foo', {'bar': 'baz'}, 'tester') paunch.apply('foo', {'bar': 'baz'}, 'tester')
runner.assert_called_once_with('tester', cont_cmd=None) runner.assert_called_once_with('tester', cont_cmd=None, log=mock.ANY)
builder.assert_called_once_with( builder.assert_called_once_with(
config_id='foo', config_id='foo',
config={'bar': 'baz'}, config={'bar': 'baz'},
runner=runner.return_value, runner=runner.return_value,
labels=None labels=None,
log=mock.ANY
) )
builder.return_value.apply.assert_called_once_with() builder.return_value.apply.assert_called_once_with()
@ -42,19 +43,20 @@ class TestPaunch(base.TestCase):
managed_by='tester', managed_by='tester',
labels={'bink': 'boop'}) labels={'bink': 'boop'})
runner.assert_called_once_with('tester', cont_cmd=None) runner.assert_called_once_with('tester', cont_cmd=None, log=mock.ANY)
builder.assert_called_once_with( builder.assert_called_once_with(
config_id='foo', config_id='foo',
config={'bar': 'baz'}, config={'bar': 'baz'},
runner=runner.return_value, runner=runner.return_value,
labels={'bink': 'boop'} labels={'bink': 'boop'},
log=mock.ANY
) )
builder.return_value.apply.assert_called_once_with() builder.return_value.apply.assert_called_once_with()
@mock.patch('paunch.runner.DockerRunner', autospec=True) @mock.patch('paunch.runner.DockerRunner', autospec=True)
def test_cleanup(self, runner): def test_cleanup(self, runner):
paunch.cleanup(['foo', 'bar'], 'tester') paunch.cleanup(['foo', 'bar'], 'tester')
runner.assert_called_once_with('tester', cont_cmd=None) runner.assert_called_once_with('tester', cont_cmd=None, log=mock.ANY)
runner.return_value.delete_missing_configs.assert_called_once_with( runner.return_value.delete_missing_configs.assert_called_once_with(
['foo', 'bar']) ['foo', 'bar'])
runner.return_value.rename_containers.assert_called_once_with() runner.return_value.rename_containers.assert_called_once_with()
@ -62,13 +64,13 @@ class TestPaunch(base.TestCase):
@mock.patch('paunch.runner.DockerRunner', autospec=True) @mock.patch('paunch.runner.DockerRunner', autospec=True)
def test_list(self, runner): def test_list(self, runner):
paunch.list('tester') paunch.list('tester')
runner.assert_called_once_with('tester', cont_cmd=None) runner.assert_called_once_with('tester', cont_cmd=None, log=mock.ANY)
runner.return_value.list_configs.assert_called_once_with() runner.return_value.list_configs.assert_called_once_with()
@mock.patch('paunch.runner.DockerRunner', autospec=True) @mock.patch('paunch.runner.DockerRunner', autospec=True)
def test_delete(self, runner): def test_delete(self, runner):
paunch.delete(['foo', 'bar'], 'tester') paunch.delete(['foo', 'bar'], 'tester')
runner.assert_called_once_with('tester', cont_cmd=None) runner.assert_called_once_with('tester', cont_cmd=None, log=mock.ANY)
runner.return_value.remove_containers.assert_has_calls([ runner.return_value.remove_containers.assert_has_calls([
mock.call('foo'), mock.call('bar') mock.call('foo'), mock.call('bar')
]) ])
@ -77,11 +79,13 @@ class TestPaunch(base.TestCase):
@mock.patch('paunch.runner.DockerRunner') @mock.patch('paunch.runner.DockerRunner')
def test_debug(self, runner, builder): def test_debug(self, runner, builder):
paunch.debug('foo', 'testcont', 'run', {'bar': 'baz'}, 'tester', paunch.debug('foo', 'testcont', 'run', {'bar': 'baz'}, 'tester',
cont_cmd='docker') cont_cmd='docker', log_level=42, log_file='/dev/null')
builder.assert_called_once_with( builder.assert_called_once_with(
config_id='foo', config_id='foo',
config={'bar': 'baz'}, config={'bar': 'baz'},
runner=runner.return_value, runner=runner.return_value,
labels=None labels=None,
log=mock.ANY
) )
runner.assert_called_once_with('tester', cont_cmd='docker') runner.assert_called_once_with('tester', cont_cmd='docker',
log=mock.ANY)

46
paunch/utils/common.py Normal file
View File

@ -0,0 +1,46 @@
# Copyright 2018 Red Hat, Inc.
# All Rights Reserved.
#
# 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.
import logging
import sys
def configure_logging(name, level=3, log_file=None):
'''Mimic oslo_log default levels and formatting for the logger. '''
log = logging.getLogger(name)
if level and level > 2:
ll = logging.DEBUG
elif level and level == 2:
ll = logging.INFO
else:
ll = logging.WARNING
log.setLevel(ll)
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(ll)
if log_file:
fhandler = logging.FileHandler(log_file)
formatter = logging.Formatter(
'%(asctime)s.%(msecs)03d %(process)d %(levelname)s '
'%(name)s [ ] %(message)s',
'%Y-%m-%d %H:%M:%S')
fhandler.setLevel(ll)
fhandler.setFormatter(formatter)
log.addHandler(fhandler)
log.addHandler(handler)
log.propagate = False
return log

View File

@ -13,14 +13,14 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import logging
import os import os
import subprocess import subprocess
LOG = logging.getLogger(__name__) from paunch.utils import common
def service_create(container, cconfig, sysdir='/etc/systemd/system/'): def service_create(container, cconfig, sysdir='/etc/systemd/system/',
log=None):
"""Create a service in systemd """Create a service in systemd
:param container: container name :param container: container name
@ -31,7 +31,11 @@ def service_create(container, cconfig, sysdir='/etc/systemd/system/'):
:param sysdir: systemd unit files directory :param sysdir: systemd unit files directory
:type sysdir: string :type sysdir: string
:param log: optional pre-defined logger for messages
:type log: logging.RootLogger
""" """
log = log or common.configure_logging(__name__)
wants = " ".join(str(x) + '.service' for x in wants = " ".join(str(x) + '.service' for x in
cconfig.get('depends_on', [])) cconfig.get('depends_on', []))
@ -46,7 +50,7 @@ def service_create(container, cconfig, sysdir='/etc/systemd/system/'):
restart = 'always' restart = 'always'
sysd_unit_f = sysdir + container + '.service' sysd_unit_f = sysdir + container + '.service'
LOG.debug('Creating systemd unit file: %s' % sysd_unit_f) log.debug('Creating systemd unit file: %s' % sysd_unit_f)
s_config = { s_config = {
'name': container, 'name': container,
'wants': wants, 'wants': wants,
@ -70,20 +74,24 @@ WantedBy=multi-user.target""" % s_config)
subprocess.call(['systemctl', 'daemon-reload']) subprocess.call(['systemctl', 'daemon-reload'])
def service_delete(container): def service_delete(container, log=None):
"""Delete a service in systemd """Delete a service in systemd
:param container: container name :param container: container name
:type container: String :type container: String
:param log: optional pre-defined logger for messages
:type log: logging.RootLogger
""" """
log = log or common.configure_logging(__name__)
sysd_unit_f = '/etc/systemd/system/' + container + '.service' sysd_unit_f = '/etc/systemd/system/' + container + '.service'
if os.path.isfile(sysd_unit_f): if os.path.isfile(sysd_unit_f):
LOG.debug('Stopping and disabling systemd service for %s' % container) log.debug('Stopping and disabling systemd service for %s' % container)
subprocess.call(['systemctl', 'stop', container]) subprocess.call(['systemctl', 'stop', container])
subprocess.call(['systemctl', 'disable', container]) subprocess.call(['systemctl', 'disable', container])
LOG.debug('Removing systemd unit file %s' % sysd_unit_f) log.debug('Removing systemd unit file %s' % sysd_unit_f)
os.remove(sysd_unit_f) os.remove(sysd_unit_f)
subprocess.call(['systemctl', 'daemon-reload']) subprocess.call(['systemctl', 'daemon-reload'])
else: else:
LOG.warning('No systemd unit file was found for %s' % container) log.warning('No systemd unit file was found for %s' % container)

View File

@ -0,0 +1,5 @@
---
other:
- |
Logging verbosity and destination file can be controled with ``--debug``
``--verbose`` and ``--log-file``.