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:
parent
133bb38368
commit
d3c83259bf
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
Logging verbosity and destination file can be controled with ``--debug``
|
||||||
|
``--verbose`` and ``--log-file``.
|
Loading…
Reference in New Issue