Update nspawn module to use common code for copy_to function

Also update calls to container_resource.run to reflect earlier
modifications to that module.
This commit is contained in:
Erik Johnson 2015-03-29 12:05:23 -05:00
parent 681b3c4f5a
commit f0cbf2ceed

View File

@ -24,6 +24,7 @@ Minions running systemd >= 219 will place new containers in
# Import python libs
from __future__ import absolute_import
import errno
import functools
import logging
import os
import re
@ -47,8 +48,7 @@ __func_alias__ = {
__virtualname__ = 'nspawn'
SEED_MARKER = '/nspawn.initial_seed'
WANT = '/etc/systemd/system/multi-user.target.wants/systemd-nspawn@{0}.service'
PATH = 'PATH=/bin:/usr/bin:/sbin:/usr/sbin:/opt/bin:' \
'/usr/local/bin:/usr/local/sbin'
EXEC_METHOD = 'nsenter'
def __virtual__():
@ -73,6 +73,20 @@ def _sd_version():
return salt.utils.systemd.version(__context__)
def _ensure_exists(wrapped):
'''
Decorator to ensure that the named container exists.
'''
@functools.wraps(wrapped)
def check_exists(name, *args, **kwargs):
if not exists(name):
raise CommandExecutionError(
'Container \'{0}\' does not exist'.format(name)
)
return wrapped(name, *args, **salt.utils.clean_kwargs(**kwargs))
return check_exists
def _root(name='', all_roots=False):
'''
Return the container root directory. Starting with systemd 219, new
@ -196,25 +210,6 @@ def _ensure_running(name):
return start(name)
def _ensure_exists(name):
'''
Raise an exception if the container does not exist
'''
contextkey = 'nspawn.exists.{0}'.format(name)
if contextkey in __context__:
# No need to return a value, this function is only here to raise an
# exception if the container doesn't exist, and just dropping a key
# into __context__ and returning early here will keep us from checking
# if the container exists multiple times.
return
if exists(name):
__context__[contextkey] = True
else:
raise CommandExecutionError(
'Container \'{0}\' does not exist'.format(name)
)
def _ensure_systemd(version):
'''
Raises an exception if the systemd version is not greater than the
@ -238,38 +233,21 @@ def _ensure_systemd(version):
)
def _machinectl(cmd, output_loglevel='debug', use_vt=False):
def _machinectl(cmd,
output_loglevel='debug',
ignore_retcode=False,
use_vt=False):
'''
Helper function to run machinectl
'''
prefix = 'machinectl --no-legend --no-pager'
return __salt__['cmd.run_all']('{0} {1}'.format(prefix, cmd),
output_loglevel=output_loglevel,
ignore_retcode=ignore_retcode,
use_vt=use_vt)
def _pid(name):
'''
Return container pid
'''
try:
return int(info(name).get('PID'))
except (TypeError, ValueError) as exc:
raise CommandExecutionError(
'Unable to get PID for container \'{0}\': {1}'.format(name, exc)
)
def _nsenter(name):
'''
Return the nsenter command to attach to the named container
'''
return (
'nsenter --target {0} --mount --uts --ipc --net --pid'
.format(_pid(name))
)
@_ensure_exists
def _run(name,
cmd,
output=None,
@ -284,32 +262,23 @@ def _run(name,
'''
Common logic for nspawn.run functions
'''
# No need to call _ensure_exists(), it will be called via _nsenter()
full_cmd = _nsenter(name)
if keep_env is not True:
full_cmd += ' env -i'
if keep_env is None:
full_cmd += ' {0}'.format(PATH)
elif isinstance(keep_env, six.string_types):
for var in keep_env.split(','):
if var in os.environ:
full_cmd += ' {0}="{1}"'.format(var, os.environ[var])
full_cmd += ' {0}'.format(cmd)
orig_state = state(name)
exc = None
try:
ret = __salt__['container_resource.run'](
name,
full_cmd,
cmd,
container_type=__virtualname__,
exec_method=EXEC_METHOD,
output=output,
no_start=no_start,
stdin=stdin,
python_shell=python_shell,
output_loglevel=output_loglevel,
ignore_retcode=ignore_retcode,
use_vt=use_vt)
except Exception as exc:
use_vt=use_vt,
keep_env=keep_env)
except Exception:
raise
finally:
# Make sure we stop the container if necessary, even if an exception
@ -336,6 +305,28 @@ def _invalid_kwargs(*args, **kwargs):
)
@_ensure_exists
def pid(name):
'''
Returns the PID of a container
name
Container name
CLI Example:
.. code-block:: bash
salt myminion nspawn.pid arch1
'''
try:
return int(info(name).get('PID'))
except (TypeError, ValueError) as exc:
raise CommandExecutionError(
'Unable to get PID for container \'{0}\': {1}'.format(name, exc)
)
def run(name,
cmd,
no_start=False,
@ -369,8 +360,7 @@ def run(name,
suppress logging.
use_vt : False
Use SaltStack's utils.vt to stream output to console. Assumes
``output=all``.
Use SaltStack's utils.vt to stream output to console.
keep_env : None
If not passed, only a sane default PATH environment variable will be
@ -379,7 +369,6 @@ def run(name,
environment variable names can be passed, and those environment
variables will be kept.
CLI Example:
.. code-block:: bash
@ -771,15 +760,15 @@ def bootstrap_salt(name,
('tmpdir {0} creation'
' failed ({1}').format(dest_dir, cmd))
return False
cp(name,
copy_to(name,
bs_,
'{0}/bootstrap.sh'.format(dest_dir),
makedirs=True)
cp(name, cfg_files['config'],
copy_to(name, cfg_files['config'],
os.path.join(configdir, 'minion'))
cp(name, cfg_files['privkey'],
copy_to(name, cfg_files['privkey'],
os.path.join(configdir, 'minion.pem'))
cp(name, cfg_files['pubkey'],
copy_to(name, cfg_files['pubkey'],
os.path.join(configdir, 'minion.pub'))
bootstrap_args = bootstrap_args.format(configdir)
cmd = ('{0} {2}/bootstrap.sh {1}'
@ -797,9 +786,9 @@ def bootstrap_salt(name,
else:
minion_config = salt.config.minion_config(cfg_files['config'])
pki_dir = minion_config['pki_dir']
cp(name, cfg_files['config'], '/etc/salt/minion')
cp(name, cfg_files['privkey'], os.path.join(pki_dir, 'minion.pem'))
cp(name, cfg_files['pubkey'], os.path.join(pki_dir, 'minion.pub'))
copy_to(name, cfg_files['config'], '/etc/salt/minion')
copy_to(name, cfg_files['privkey'], os.path.join(pki_dir, 'minion.pem'))
copy_to(name, cfg_files['pubkey'], os.path.join(pki_dir, 'minion.pub'))
run(name,
'salt-call --local service.enable salt-minion',
python_shell=False)
@ -894,17 +883,14 @@ def exists(name):
salt myminion nspawn.exists <name>
'''
return name in list_all()
def _get_state(name):
try:
cmd = 'show {0} --property=State'.format(name)
return _machinectl(cmd)['stdout'].split('=')[1]
except IndexError:
return 'stopped'
contextkey = 'nspawn.exists.{0}'.format(name)
if contextkey in __context__:
return __context__[contextkey]
__context__[contextkey] = name in list_all()
return __context__[contextkey]
@_ensure_exists
def state(name):
'''
Return state of container (running or stopped)
@ -915,8 +901,11 @@ def state(name):
salt myminion nspawn.state <name>
'''
_ensure_exists(name)
return _get_state(name)
try:
cmd = 'show {0} --property=State'.format(name)
return _machinectl(cmd, ignore_retcode=True)['stdout'].split('=')[-1]
except IndexError:
return 'stopped'
def info(name, **kwargs):
@ -1003,6 +992,7 @@ def info(name, **kwargs):
return ret
@_ensure_exists
def enable(name):
'''
Set the named container to be launched at boot
@ -1013,7 +1003,6 @@ def enable(name):
salt myminion nspawn.enable <name>
'''
_ensure_exists(name)
cmd = 'systemctl enable systemd-nspawn@{0}'.format(name)
if __salt__['cmd.retcode'](cmd, python_shell=False) != 0:
__context__['retcode'] = salt.defaults.exitcodes.EX_UNAVAILABLE
@ -1021,6 +1010,7 @@ def enable(name):
return True
@_ensure_exists
def disable(name):
'''
Set the named container to *not* be launched at boot
@ -1031,7 +1021,6 @@ def disable(name):
salt myminion nspawn.enable <name>
'''
_ensure_exists(name)
cmd = 'systemctl disable systemd-nspawn@{0}'.format(name)
if __salt__['cmd.retcode'](cmd, python_shell=False) != 0:
__context__['retcode'] = salt.defaults.exitcodes.EX_UNAVAILABLE
@ -1039,6 +1028,7 @@ def disable(name):
return True
@_ensure_exists
def start(name):
'''
Start the named container
@ -1049,7 +1039,6 @@ def start(name):
salt myminion nspawn.start <name>
'''
_ensure_exists(name)
if _sd_version() >= 219:
ret = _machinectl('start {0}'.format(name))
else:
@ -1063,12 +1052,12 @@ def start(name):
# This function is hidden from sphinx docs
@_ensure_exists
def stop(name, kill=False):
'''
This is a compatibility function which provides the logic for
nspawn.poweroff and nspawn.terminate.
'''
_ensure_exists(name)
if _sd_version() >= 219:
if kill:
action = 'terminate'
@ -1142,6 +1131,7 @@ def restart(name):
return reboot(name)
@_ensure_exists
def reboot(name, kill=False):
'''
Reboot the container by sending a SIGINT to its init process. Equivalent
@ -1163,7 +1153,6 @@ def reboot(name, kill=False):
salt myminion nspawn.reboot arch1
salt myminion nspawn.restart arch1
'''
_ensure_exists(name)
if _sd_version() >= 219:
if state(name) == 'running':
ret = _machinectl('reboot {0}'.format(name))
@ -1193,6 +1182,7 @@ def reboot(name, kill=False):
return True
@_ensure_exists
def remove(name, stop=False):
'''
Remove the named container
@ -1217,7 +1207,6 @@ def remove(name, stop=False):
salt '*' nspawn.remove foo
salt '*' nspawn.remove foo stop=True
'''
_ensure_exists(name)
if not stop and state(name) != 'stopped':
raise CommandExecutionError(
'Container \'{0}\' is not stopped'.format(name)
@ -1245,21 +1234,10 @@ def remove(name, stop=False):
destroy = remove
def _get_md5(name, path):
@_ensure_exists
def copy_to(name, source, dest, overwrite=False, makedirs=False):
'''
Get the MD5 checksum of a file from a container
'''
output = run_stdout(name, 'md5sum "{0}"'.format(path), ignore_retcode=True)
try:
return output.split()[0]
except IndexError:
# Destination file does not exist or could not be accessed
return None
def cp(name, source, dest, makedirs=False):
'''
Copy a file or directory from the host into a container.
Copy a file from the host into a container
name
Container name
@ -1270,96 +1248,46 @@ def cp(name, source, dest, makedirs=False):
dest
Destination on the container. Must be an absolute path.
.. versionchanged:: 2015.2.0
If the destination is a directory, the file will be copied into
that directory.
overwrite : False
Unless this option is set to ``True``, then if a file exists at the
location specified by the ``dest`` argument, an error will be raised.
makedirs : False
Create the parent directory on the container if it does not already
exist.
.. versionadded:: 2015.2.0
CLI Example:
.. code-block:: bash
salt 'minion' lxc.cp /tmp/foo /root/foo
salt 'minion' nspawn.copy_to /tmp/foo /root/foo
'''
if _sd_version() >= 219:
pass
c_state = state(name)
if c_state != 'running':
raise CommandExecutionError(
'Container \'{0}\' {1}'.format(
name,
'is {0}'.format(c_state)
if c_state is not None
else 'does not exist'
)
)
log.debug('Copying {0} to container \'{1}\' as {2}'
.format(source, name, dest))
# Source file sanity checks
if not os.path.isabs(source):
raise SaltInvocationError('Source path must be absolute')
elif not os.path.exists(source):
raise SaltInvocationError(
'Source file {0} does not exist'.format(source)
)
elif not os.path.isfile(source):
raise SaltInvocationError('Source must be a regular file')
source_dir, source_name = os.path.split(source)
# Destination file sanity checks
if not os.path.isabs(dest):
raise SaltInvocationError('Destination path must be absolute')
if retcode(name,
'test -d \'{0}\''.format(dest),
ignore_retcode=True) == 0:
# Destination is a directory, full path to dest file will include the
# basename of the source file.
dest = os.path.join(dest, source_name)
else:
# Destination was not a directory. We will check to see if the parent
# dir is a directory, and then (if makedirs=True) attempt to create the
# parent directory.
dest_dir, dest_name = os.path.split(dest)
if retcode(name,
'test -d \'{0}\''.format(dest_dir),
ignore_retcode=True) != 0:
if makedirs:
result = run_all(name, 'mkdir -p \'{0}\''.format(dest_dir))
if result['retcode'] != 0:
error = ('Unable to create destination directory {0} in '
'container \'{1}\''.format(dest_dir, name))
if result['stderr']:
error += ': {0}'.format(result['stderr'])
raise CommandExecutionError(error)
else:
raise SaltInvocationError(
'Directory {0} does not exist on container \'{1}\''
.format(dest_dir, name)
path = source
try:
if source.startswith('salt://'):
cached_source = __salt__['cp.cache_file'](source)
if not cached_source:
raise CommandExecutionError(
'Unable to cache {0}'.format(source)
)
path = cached_source
except AttributeError:
raise SaltInvocationError('Invalid source file {0}'.format(source))
# Before we try to replace the file, compare checksums.
source_md5 = __salt__['file.get_sum'](source, 'md5')
if source_md5 != _get_md5(name, dest):
# Using cat here instead of opening the file, reading it into memory,
# and passing it as stdin to run(). This will keep down memory
# usage for the minion and make the operation run quicker.
__salt__['cmd.run_stdout'](
'cat "{0}" | {1} env -i {2} tee "{3}"'
.format(source, _nsenter(name), PATH, dest),
python_shell=True
)
return source_md5 == _get_md5(name, dest)
# Checksums matched, no need to copy, just return True
return True
if _sd_version() >= 219:
# TODO: Use machinectl copy-to
pass
return __salt__['container_resource.copy_to'](
name,
path,
dest,
container_type=__virtualname__,
exec_method=EXEC_METHOD,
overwrite=overwrite,
makedirs=makedirs)
cp = copy_to
# Everything below requres systemd >= 219