7032f4bb1e
Previously, clone operations where the new container already exists were reporting as failed. This commit modifies that behavior so that if the new container exists, it returns the facts for that container instead of failing. To facilitate these changes, a _container_exists method was added which returns True or False based on whether or not the container name is in the output from self._list(). I also made a few fixes in the documentation by removing trailing spaces and fixing a typo in the state on line 222.
1605 lines
52 KiB
Python
1605 lines
52 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# (c) 2014, Kevin Carter <kevin.carter@rackspace.com>
|
|
# (c) 2014, Hugh Saunders <hugh.saunders@rackspace.co.uk>
|
|
#
|
|
# This file is part of Ansible
|
|
#
|
|
# Ansible is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Ansible is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
import ast
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
|
|
|
|
DOCUMENTATION = """
|
|
---
|
|
module: lxc
|
|
version_added: "1.6.2"
|
|
short_description: Manage LXC Containers
|
|
description:
|
|
- Management of LXC containers
|
|
options:
|
|
name:
|
|
description:
|
|
- Name of a container.
|
|
required: false
|
|
return_code:
|
|
description:
|
|
- Allow for return Codes other than 0 when executing commands.
|
|
This is a comma separated list of acceptable return codes.
|
|
default: 0
|
|
backingstore:
|
|
description:
|
|
- Options 'dir', 'lvm', 'loop', 'btrfs', 'best'. The default is
|
|
'none'.
|
|
required: false
|
|
template:
|
|
description:
|
|
- Name of the template to use within an LXC create.
|
|
required: false
|
|
default: ubuntu
|
|
template_options:
|
|
description:
|
|
- Template options when building the container.
|
|
required: false
|
|
config:
|
|
description:
|
|
- Path to the LXC configuration file.
|
|
required: false
|
|
default: /etc/lxc/default.conf
|
|
bdev:
|
|
description:
|
|
- Backend device for use with an LXC container.
|
|
required: false
|
|
lvname:
|
|
description:
|
|
- Backend store for lvm.
|
|
required: false
|
|
vgname:
|
|
description:
|
|
- If Backend store is lvm, specify the name of the volume group.
|
|
required: false
|
|
thinpool:
|
|
description:
|
|
- Use LVM thin pool called TP (Default lxc).
|
|
required: false
|
|
fstype:
|
|
description:
|
|
- Create fstype TYPE (Default ext3).
|
|
required: false
|
|
fssize:
|
|
description:
|
|
- Filesystem Size (Default 1G, default unit 1M).
|
|
required: false
|
|
dir:
|
|
description:
|
|
- Place rootfs directory under DIR.
|
|
required: false
|
|
zfsroot:
|
|
description:
|
|
- Create zfs under given zfsroot (Default tank/lxc).
|
|
required: false
|
|
container_command:
|
|
description:
|
|
- Run a command in a container. Only used in the "attach" operation.
|
|
required: false
|
|
return_facts:
|
|
description:
|
|
- Return stdout after an attach command. Only used in the "attach"
|
|
operation.
|
|
required: false
|
|
default: false
|
|
lxcpath:
|
|
description:
|
|
- Place container under PATH
|
|
required: false
|
|
snapshot:
|
|
description:
|
|
- The new container's rootfs should be a LVM or btrfs snapshot of
|
|
the original. Only used in "clone" operation.
|
|
required: false
|
|
keepname:
|
|
description:
|
|
- Do not change the hostname of the container (in the root
|
|
filesystem). Only used in "clone" operation.
|
|
required: false
|
|
newpath:
|
|
description:
|
|
- The lxcpath for the new container. Only used in "clone" operation.
|
|
required: false
|
|
orig:
|
|
description:
|
|
- The name of the original container to clone. Only used in "clone"
|
|
operation.
|
|
required: false
|
|
new:
|
|
description:
|
|
- The name of the new container to create. Only used in "clone"
|
|
operation.
|
|
required: false
|
|
state:
|
|
choices:
|
|
- running
|
|
- stopped
|
|
description:
|
|
- Start a container right after it's created.
|
|
required: false
|
|
default: 'running'
|
|
options:
|
|
description:
|
|
- Dictionary of options to use in a containers configuration. Only
|
|
used in "config" operation. When dropping additional configuration
|
|
options the values are strings IE "key=value", see example section
|
|
for more details.
|
|
required: false
|
|
command:
|
|
choices:
|
|
- list
|
|
- create
|
|
- destroy
|
|
- info
|
|
- attach
|
|
- start
|
|
- stop
|
|
- restart
|
|
- config
|
|
- createtar
|
|
- clone
|
|
description:
|
|
- Type of command to run, see Examples.
|
|
required: true
|
|
author: Kevin Carter, Hugh Saunders
|
|
requirements: ['lxc >= 1.0']
|
|
"""
|
|
|
|
EXAMPLES = """
|
|
# Create a new LXC container.
|
|
- lxc: name=test-container
|
|
template=ubuntu
|
|
config=/etc/lxc/lxc-rpc.conf
|
|
command=create
|
|
state=running
|
|
|
|
# Create tar archive from Container this is a bzip2 compressed archive
|
|
- lxc: name="{{ container_name }}"
|
|
command=createtar
|
|
tarpath="/tmp/{{ container_name }}"
|
|
|
|
# Run a command within a built and started container.
|
|
- lxc: name=test-container
|
|
container_command="git clone https://github.com/cloudnull/lxc_defiant"
|
|
command=attach
|
|
|
|
# List all containers and return a dict of all found information
|
|
- lxc: command=list
|
|
|
|
# Get information on a given container.
|
|
- lxc: name=test-container
|
|
command=info
|
|
|
|
# Stop a container.
|
|
- lxc: name=test-container
|
|
command=stop
|
|
|
|
# Start a container.
|
|
- lxc: name=test-container
|
|
command=start
|
|
|
|
# Restart a container.
|
|
- lxc: name=test-container
|
|
command=restart
|
|
|
|
# Update the configuration for a container.
|
|
# Uses a list of "key=value" pairs.
|
|
container_config_options:
|
|
- 'cpuset.cpus="0,3"'
|
|
- 'lxc.cgroup.devices.allow="a rmw"'
|
|
|
|
- lxc: name=test-container
|
|
command=config
|
|
options="{{ container_config_options }}"
|
|
|
|
# Clone a container.
|
|
- lxc: orig=test-container
|
|
new=test-container-new
|
|
command=clone
|
|
state=running
|
|
|
|
# Destroy a container.
|
|
- lxc: name=test-container
|
|
command=destroy
|
|
"""
|
|
|
|
|
|
COMMAND_MAP = {
|
|
'list': {
|
|
'command': 'container_list',
|
|
'variables': [
|
|
'lxcpath'
|
|
],
|
|
},
|
|
'create': {
|
|
'command': 'container_create',
|
|
'variables': [
|
|
'name',
|
|
'config',
|
|
'template',
|
|
'bdev',
|
|
'template',
|
|
'lxcpath',
|
|
'lvname',
|
|
'vgname',
|
|
'thinpool',
|
|
'fstype',
|
|
'fssize',
|
|
'dir',
|
|
'zfsroot',
|
|
'template_options',
|
|
'state'
|
|
]
|
|
},
|
|
'destroy': {
|
|
'command': 'container_destroy',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath'
|
|
],
|
|
},
|
|
'clone': {
|
|
'command': 'container_clone',
|
|
'variables': [
|
|
'keepname',
|
|
'snapshot',
|
|
'fssize',
|
|
'lxcpath',
|
|
'newpath',
|
|
'backingstore',
|
|
'orig',
|
|
'new',
|
|
'state'
|
|
]
|
|
},
|
|
'info': {
|
|
'command': 'container_info',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath'
|
|
],
|
|
},
|
|
'attach': {
|
|
'command': 'container_attach',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath',
|
|
'container_command',
|
|
'return_facts'
|
|
],
|
|
},
|
|
'start': {
|
|
'command': 'container_start',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath'
|
|
],
|
|
},
|
|
'stop': {
|
|
'command': 'container_stop',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath'
|
|
],
|
|
},
|
|
'restart': {
|
|
'command': 'container_restart',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath'
|
|
],
|
|
},
|
|
'config': {
|
|
'command': 'container_config',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath',
|
|
'options',
|
|
'state'
|
|
],
|
|
},
|
|
'createtar': {
|
|
'command': 'container_create_tar',
|
|
'variables': [
|
|
'name',
|
|
'lxcpath',
|
|
'tarpath'
|
|
],
|
|
}
|
|
}
|
|
|
|
|
|
# This is used to attach to a running container and execute commands from
|
|
# within the container on the host. This will provide local access to a
|
|
# container without using SSH. The template will attempt to work within the
|
|
# home directory of the user that was attached to the conatiner and source
|
|
# that users environment variables by default.
|
|
ATTACH_TEMPLATE = """
|
|
%(command)s <<EOL
|
|
pushd \$(grep \$(whoami) /etc/passwd | awk -F':' '{print \$6}')
|
|
if [[ -f ".bashrc" ]];then
|
|
source .bashrc
|
|
fi
|
|
%(container_command)s
|
|
popd
|
|
EOL
|
|
"""
|
|
|
|
|
|
class LxcManagement(object):
|
|
"""Manage LXC containers with ansible."""
|
|
|
|
def __init__(self, module):
|
|
"""Management of LXC containers via Ansible.
|
|
|
|
:param module: ``object`` Processed Ansible Module.
|
|
"""
|
|
self.module = module
|
|
self.rc = [int(i) for i in self.module.params.get('return_code')]
|
|
self.state_change = False
|
|
self.lxc_vg = None
|
|
|
|
def failure(self, error, rc, msg):
|
|
"""Return a Failure when running an Ansible command.
|
|
|
|
:param error: ``str`` Error that occurred.
|
|
:param rc: ``int`` Return code while executing an Ansible command.
|
|
:param msg: ``str`` Message to report.
|
|
"""
|
|
self.module.fail_json(msg=msg, rc=rc, err=error)
|
|
|
|
@staticmethod
|
|
def _lxc_facts(facts):
|
|
"""Return a dict for our Ansible facts.
|
|
|
|
:param facts: ``dict`` Dict with data to return
|
|
"""
|
|
return {'lxc_facts': facts}
|
|
|
|
def _ensure_state(self, state, name, variables_dict,
|
|
tries=0, max_tries=60, sleep_time=.5):
|
|
"""Ensure that the container is within a defined state.
|
|
|
|
:param state: ``str`` The desired state of the container.
|
|
:param name: ``str`` Name of the container.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
:param tries: ``int`` The number of attempts before quiting.
|
|
"""
|
|
container_data = self._get_container_info(
|
|
name=name, variables_dict=variables_dict
|
|
)
|
|
current_state = container_data['state']
|
|
|
|
state_map = {
|
|
'running': {
|
|
'frozen': self._freezer,
|
|
'stopped': self._stopper
|
|
},
|
|
'stopped': {
|
|
'running': self._starter,
|
|
'frozen': self._starter
|
|
},
|
|
'frozen': {
|
|
'running': self._unfreezer,
|
|
'stopped': self._stopper
|
|
}
|
|
}
|
|
|
|
if state != current_state:
|
|
if tries <= max_tries:
|
|
state_map[current_state][state](
|
|
name=name, variables_dict=variables_dict
|
|
)
|
|
self.state_change = True
|
|
time.sleep(sleep_time)
|
|
self._ensure_state(
|
|
state=state, name=name,
|
|
variables_dict=variables_dict, tries=tries + 1
|
|
)
|
|
|
|
else:
|
|
message = (
|
|
'State failed to change for container [ %s ] --'
|
|
' [ %s ] != [ %s ]' % (name, state, current_state)
|
|
)
|
|
self.failure(
|
|
error='Failed to change the state of the container.',
|
|
rc=2,
|
|
msg=message
|
|
)
|
|
|
|
def command_router(self):
|
|
"""Run the command as its provided to the module."""
|
|
command_name = self.module.params['command']
|
|
|
|
if command_name in COMMAND_MAP:
|
|
action_command = COMMAND_MAP[command_name]
|
|
action_name = action_command['command']
|
|
if hasattr(self, '_%s' % action_name):
|
|
action = getattr(self, '_%s' % action_name)
|
|
facts = action(variables=action_command['variables'])
|
|
if facts is None:
|
|
self.module.exit_json(changed=self.state_change)
|
|
else:
|
|
self.module.exit_json(
|
|
changed=self.state_change,
|
|
ansible_facts=facts
|
|
)
|
|
else:
|
|
self.failure(
|
|
error='Command not in LxcManagement class',
|
|
rc=2,
|
|
msg='Method [ %s ] was not found.' % action_name
|
|
)
|
|
else:
|
|
self.failure(
|
|
error='No Command Found',
|
|
rc=2,
|
|
msg='Command [ %s ] was not found.' % command_name
|
|
)
|
|
|
|
def _get_vars(self, variables, required=None):
|
|
"""Return a dict of all variables as found within the module.
|
|
|
|
:param variables: ``list`` List of all variables to find.
|
|
:param required: ``list`` Name of variables that are required.
|
|
"""
|
|
return_dict = {}
|
|
for variable in variables:
|
|
_var = self.module.params.get(variable)
|
|
if _var not in [None, '', False]:
|
|
return_dict[variable] = self.module.params[variable]
|
|
else:
|
|
if isinstance(required, list):
|
|
for var_name in required:
|
|
if var_name not in return_dict:
|
|
self.failure(
|
|
error='Missing [ %s ] from Task' % var_name,
|
|
rc=000,
|
|
msg='known variables %s - available params %s'
|
|
% (variables, self.module.params)
|
|
)
|
|
return return_dict
|
|
|
|
def _run_command(self, build_command, unsafe_shell=False, timeout=600):
|
|
"""Return information from running an Ansible Command.
|
|
|
|
This will squash the build command list into a string and then
|
|
execute the command via Ansible. The output is returned to the method.
|
|
This output is returned as `return_code`, `stdout`, `stderr`.
|
|
|
|
Prior to running the command the method will look to see if the LXC
|
|
lockfile is present. If the lockfile "/var/lock/subsys/lxc" the method
|
|
will wait upto 10 minutes for it to be gone; polling every 5 seconds.
|
|
|
|
:param build_command: ``list`` Used for the command and all options.
|
|
"""
|
|
lockfile = '/var/lock/subsys/lxc'
|
|
command = self._construct_command(build_list=build_command)
|
|
|
|
for _ in xrange(timeout):
|
|
if os.path.exists(lockfile):
|
|
time.sleep(1)
|
|
else:
|
|
return self.module.run_command(
|
|
command, use_unsafe_shell=unsafe_shell
|
|
)
|
|
else:
|
|
message = (
|
|
'The LXC subsystem is locked and after 5 minutes it never'
|
|
' became unlocked. Lockfile [ %s ]' % lockfile
|
|
)
|
|
self.failure(
|
|
error='LXC subsystem locked',
|
|
rc=0,
|
|
msg=message
|
|
)
|
|
|
|
def _get_container_info(self, name, cstate=None, variables_dict=None):
|
|
"""Return a dict of information pertaining to a known container.
|
|
|
|
:param name: ``str`` name of the container.
|
|
:param cstate: ``dict`` dict to build within while gathering
|
|
information. If `None` an empty dict will be
|
|
created.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
if cstate is None:
|
|
cstate = {}
|
|
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-info', True),
|
|
'--name %s' % name
|
|
]
|
|
|
|
if isinstance(variables_dict, dict):
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['info']['variables']
|
|
)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = 'Failed to get container Information'
|
|
self.failure(err, rc, msg)
|
|
|
|
ip_count = 0
|
|
for state in return_data.splitlines():
|
|
key, value = state.split(':')
|
|
if not key.startswith(' '):
|
|
if key.lower() == 'ip':
|
|
cstate['ip_%s' % ip_count] = value.lower().strip()
|
|
ip_count += 1
|
|
else:
|
|
cstate[key.lower().strip()] = value.lower().strip()
|
|
else:
|
|
return cstate
|
|
|
|
def _container_exists(self, name, variables_dict):
|
|
"""Return True or False based on whether or not the container exists
|
|
|
|
:param name: ``str`` name of the container
|
|
"""
|
|
containers = self._list(variables_dict=variables_dict)
|
|
|
|
for container in containers.splitlines():
|
|
if container == name:
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def _construct_command(build_list):
|
|
"""Return a string from a command and build list.
|
|
|
|
:param build_list: ``list`` List containing a command with options
|
|
"""
|
|
return ' '.join(build_list)
|
|
|
|
@staticmethod
|
|
def _add_variables(variables_dict, build_command, allowed_variables):
|
|
"""Return a command list with all found options.
|
|
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
:param build_command: ``list`` Command to run.
|
|
:param allowed_variables: ``list`` Variables that are allowed for use.
|
|
"""
|
|
for key, value in variables_dict.items():
|
|
if key in allowed_variables:
|
|
if isinstance(value, bool):
|
|
build_command.append('--%s' % value)
|
|
else:
|
|
build_command.append(
|
|
'--%s %s' % (key, value)
|
|
)
|
|
else:
|
|
return build_command
|
|
|
|
def _list(self, variables_dict):
|
|
"""Return a list of containers.
|
|
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
build_command = [self.module.get_bin_path('lxc-ls', True)]
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['list']['variables']
|
|
)
|
|
rc, return_data, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-ls."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
return return_data
|
|
|
|
def _container_list(self, variables):
|
|
"""Return a dict of all containers.
|
|
|
|
:param variables: ``list`` List of all variables used in this command
|
|
"""
|
|
variables_dict = self._get_vars(variables)
|
|
containers = self._list(variables_dict=variables_dict)
|
|
|
|
all_containers = {}
|
|
for container in containers.splitlines():
|
|
cstate = all_containers[container] = {}
|
|
self._get_container_info(name=container, cstate=cstate)
|
|
else:
|
|
return self._lxc_facts(facts=all_containers)
|
|
|
|
def _create(self, name, state, variables_dict):
|
|
"""Create a new LXC container.
|
|
|
|
:param name: ``str`` Name of the container.
|
|
:param state: ``str`` State of the container once its been built
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
if 'template_options' in variables_dict:
|
|
template_options = '-- %s' % variables_dict.pop('template_options')
|
|
else:
|
|
template_options = None
|
|
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-create', True),
|
|
'--logfile /tmp/lxc-ansible-%s-create.log' % name,
|
|
'--logpriority INFO',
|
|
'--name %s' % name
|
|
]
|
|
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['create']['variables']
|
|
)
|
|
|
|
if template_options is not None:
|
|
build_command.append(template_options)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-create."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self._ensure_state(
|
|
state=state,
|
|
name=name,
|
|
variables_dict=variables_dict
|
|
)
|
|
container_info = self._get_container_info(
|
|
name=name, variables_dict=variables_dict
|
|
)
|
|
self.state_change = True
|
|
return container_info
|
|
|
|
def _container_create(self, variables):
|
|
"""Create an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['name'])
|
|
name = variables_dict.pop('name')
|
|
state = variables_dict.pop('state', 'running')
|
|
|
|
if self._container_exists(name, variables_dict):
|
|
container_info = self._get_container_info(
|
|
name=name, variables_dict=variables_dict
|
|
)
|
|
else:
|
|
container_info = self._create(name, state, variables_dict)
|
|
|
|
return self._lxc_facts(facts={name: container_info})
|
|
|
|
def _destroy(self, name):
|
|
"""Destroy an LXC container.
|
|
|
|
:param name: ``str`` Name of the container to destroy
|
|
"""
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-destroy', True),
|
|
'--logfile /tmp/lxc-ansible-%s-destroy.log' % name,
|
|
'--logpriority INFO',
|
|
'--force',
|
|
'--name %s' % name
|
|
]
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-destroy."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self.state_change = True
|
|
|
|
def _container_destroy(self, variables):
|
|
"""Destroy an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command.
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['name'])
|
|
name = variables_dict.pop('name')
|
|
|
|
if self._container_exists(name, variables_dict):
|
|
self._destroy(name=name)
|
|
|
|
def _local_clone(self, orig, new, variables_dict):
|
|
"""Clone a local container.
|
|
|
|
:param orig: ``str`` Original container name
|
|
:param new: ``str`` New container name
|
|
:param variables_dict: ``dict`` Dictionary of options
|
|
"""
|
|
state = variables_dict.pop('state', 'running')
|
|
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-clone', True),
|
|
'--orig %s' % orig,
|
|
'--new %s' % new,
|
|
]
|
|
|
|
# The next set of if statements are special cases because the lxc-clone
|
|
# API is a bit different than the rest of the LXC commands line clients
|
|
# TODO(cloudnull) When the CLI gets better this should be updated.
|
|
if variables_dict.pop('keepname', 'false') in BOOLEANS_TRUE:
|
|
build_command.append('-K')
|
|
|
|
if variables_dict.pop('snapshot', 'false') in BOOLEANS_TRUE:
|
|
build_command.append('-s')
|
|
|
|
if variables_dict.pop('copyhooks', 'false') in BOOLEANS_TRUE:
|
|
build_command.append('-H')
|
|
|
|
if 'fssize' in variables_dict:
|
|
build_command.append('-L %s' % variables_dict.pop('fssize'))
|
|
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['clone']['variables']
|
|
)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-clone."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self._ensure_state(
|
|
state=state,
|
|
name=new,
|
|
variables_dict=variables_dict
|
|
)
|
|
self.state_change = True
|
|
container_info = self._get_container_info(
|
|
name=new, variables_dict=variables_dict
|
|
)
|
|
return container_info
|
|
|
|
def _container_clone(self, variables):
|
|
"""Clone an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['orig', 'new'])
|
|
orig = variables_dict.pop('orig')
|
|
new = variables_dict.pop('new')
|
|
|
|
# Check to see if new container already exists
|
|
if self._container_exists(new, variables_dict):
|
|
new_container_info = self._get_container_info(
|
|
name=new, variables_dict=variables_dict
|
|
)
|
|
return self._lxc_facts(facts={new: new_container_info})
|
|
|
|
container_data = self._get_container_info(
|
|
name=orig, variables_dict=variables_dict
|
|
)
|
|
|
|
# Stop the original container
|
|
if container_data.get('state') != 'stopped':
|
|
self._stopper(name=orig, variables_dict=variables_dict)
|
|
|
|
# Clone the container
|
|
container_info = self._local_clone(orig, new, variables_dict)
|
|
|
|
# Restart the original container
|
|
self._starter(name=orig, variables_dict=variables_dict)
|
|
|
|
return self._lxc_facts(facts={new: container_info})
|
|
|
|
def _container_info(self, variables):
|
|
"""Return Ansible facts on an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command.
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['name'])
|
|
name = variables_dict.pop('name')
|
|
self._get_container_info(name=name, variables_dict=variables_dict)
|
|
|
|
container_info = self._get_container_info(
|
|
name=name, variables_dict=variables_dict
|
|
)
|
|
return self._lxc_facts(facts={name: container_info})
|
|
|
|
@staticmethod
|
|
def _get_kernel_version():
|
|
"""Return the Kernel version number as a float."""
|
|
os_data = os.uname()
|
|
kernel_info = os_data[2].split('-')
|
|
kernel_release = kernel_info[0]
|
|
return [int(i) for i in kernel_release.split('.')]
|
|
|
|
def _run_attach(self, name, inner_command, variables_dict):
|
|
"""Return the results of executing an LXC Attach command.
|
|
|
|
:param name: ``str`` Name of the container.
|
|
:param inner_command: ``str`` Command to run within a container.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
def kernel_fail():
|
|
self.failure(
|
|
error='Unsupportable Kernel Version found.',
|
|
rc=2,
|
|
msg='The kernel version installed [ %s ] does not support'
|
|
' LXC attach. Please upgrade to at least Kernel 3.8'
|
|
% kernel_version
|
|
)
|
|
|
|
kernel_version = self._get_kernel_version()
|
|
if kernel_version[0] < 3:
|
|
kernel_fail()
|
|
elif kernel_version[1] < 6:
|
|
kernel_fail()
|
|
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-attach', True),
|
|
'--logfile /tmp/lxc-ansible-%s-attach.log' % name,
|
|
'--logpriority INFO',
|
|
'--name %s' % name,
|
|
]
|
|
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['attach']['variables']
|
|
)
|
|
|
|
command = self._construct_command(build_list=build_command)
|
|
|
|
attach_vars = {
|
|
'command': command,
|
|
'container_command': inner_command
|
|
}
|
|
|
|
rc, return_data, err = self._run_command(
|
|
[ATTACH_TEMPLATE % attach_vars], unsafe_shell=True
|
|
)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-attach."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self.state_change = True
|
|
return return_data
|
|
|
|
def _container_attach(self, variables):
|
|
"""Attach to an LXC container and execute a command.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
required_vars = ['name', 'container_command']
|
|
variables_dict = self._get_vars(variables, required=required_vars)
|
|
name = variables_dict.pop('name')
|
|
container_command = variables_dict.pop('container_command')
|
|
return_facts = variables_dict.pop('return_facts', 'false')
|
|
|
|
self._ensure_state(
|
|
state='running',
|
|
name=name,
|
|
variables_dict=variables_dict
|
|
)
|
|
|
|
attach_results = self._run_attach(
|
|
name=name,
|
|
inner_command=container_command,
|
|
variables_dict=variables_dict
|
|
)
|
|
|
|
if return_facts in BOOLEANS_TRUE:
|
|
if attach_results:
|
|
return self._lxc_facts(facts=attach_results.splitlines())
|
|
|
|
def _starter(self, name, variables_dict):
|
|
"""Start an LXC Container.
|
|
|
|
:param name: ``str`` Name of the container.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-start', True),
|
|
'--logfile /tmp/lxc-ansible-%s-start.log' % name,
|
|
'--logpriority INFO',
|
|
'--daemon',
|
|
'--name %s' % name
|
|
]
|
|
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['start']['variables']
|
|
)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-start."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self.state_change = True
|
|
|
|
def _stopper(self, name, variables_dict):
|
|
"""Stop an LXC Container.
|
|
|
|
:param name: ``str`` Name of the container.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-stop', True),
|
|
'--logfile /tmp/lxc-ansible-%s-stop.log' % name,
|
|
'--logpriority INFO',
|
|
'--timeout 10',
|
|
'--name %s' % name
|
|
]
|
|
|
|
if variables_dict is not None:
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=COMMAND_MAP['stop']['variables']
|
|
)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-stop."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self.state_change = True
|
|
|
|
def _container_restart(self, variables):
|
|
"""Restart an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['name'])
|
|
name = variables_dict.pop('name')
|
|
self._ensure_state(name=name, state='stopped',
|
|
variables_dict=variables_dict)
|
|
self._ensure_state(name=name, state='running',
|
|
variables_dict=variables_dict)
|
|
|
|
def _freezer(self, name, variables_dict):
|
|
"""Freeze an LXC Container.
|
|
|
|
:param name: ``str`` Name of the container.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-freeze', True),
|
|
'--logfile /tmp/lxc-ansible-%s-freeze.log' % name,
|
|
'--logpriority INFO',
|
|
'--name %s' % name
|
|
]
|
|
|
|
if variables_dict is not None:
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=['lxcpath']
|
|
)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-freeze."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self.state_change = True
|
|
|
|
def _unfreezer(self, name, variables_dict):
|
|
"""unfreeze an LXC Container.
|
|
|
|
:param name: ``str`` Name of the container.
|
|
:param variables_dict: ``dict`` Preparsed optional variables used from
|
|
a seed command.
|
|
"""
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-unfreeze', True),
|
|
'--logfile /tmp/lxc-ansible-%s-unfreeze.log' % name,
|
|
'--logpriority INFO',
|
|
'--name %s' % name
|
|
]
|
|
|
|
if variables_dict is not None:
|
|
self._add_variables(
|
|
variables_dict=variables_dict,
|
|
build_command=build_command,
|
|
allowed_variables=['lxcpath']
|
|
)
|
|
|
|
rc, return_data, err = self._run_command(build_command)
|
|
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-freeze."
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
self.state_change = True
|
|
|
|
def _container_start(self, variables):
|
|
"""Start an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['name'])
|
|
name = variables_dict.pop('name')
|
|
self._ensure_state(
|
|
name=name, state='running', variables_dict=variables_dict
|
|
)
|
|
|
|
def _container_stop(self, variables):
|
|
"""Stop an LXC container.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
variables_dict = self._get_vars(variables, required=['name'])
|
|
name = variables_dict.pop('name')
|
|
self._ensure_state(name=name, state='stopped',
|
|
variables_dict=variables_dict)
|
|
|
|
@staticmethod
|
|
def _load_config(lxcpath, name):
|
|
"""Return a list of lines from an LXC config file.
|
|
|
|
:param lxcpath: ``str`` path to lxc container directory
|
|
:param name: ``str`` name of LXC container
|
|
"""
|
|
container_config_file = os.path.join(lxcpath, name, 'config')
|
|
with open(container_config_file, 'rb') as f:
|
|
return container_config_file, f.readlines()
|
|
|
|
def _load_lxcpath(self, variables_dict, pop=False):
|
|
if 'lxcpath' not in variables_dict:
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-config', True),
|
|
'lxc.lxcpath'
|
|
]
|
|
rc, return_data, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = "Failed executing lxc-config."
|
|
self.failure(err, rc, msg)
|
|
|
|
lxcpath = return_data
|
|
|
|
else:
|
|
if pop is True:
|
|
lxcpath = variables_dict.pop('lxcpath')
|
|
else:
|
|
lxcpath = variables_dict.get('lxcpath')
|
|
|
|
return lxcpath.strip()
|
|
|
|
def _container_config(self, variables):
|
|
"""Configure an LXC container.
|
|
|
|
Write new configuration values to the lxc config file. This will
|
|
stop the container if its running write the new options and then
|
|
restart the container upon completion if the state is set to running,
|
|
which is the default.
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
required_vars = ['name', 'options']
|
|
variables_dict = self._get_vars(variables, required=required_vars)
|
|
name = variables_dict.pop('name')
|
|
|
|
lxcpath = self._load_lxcpath(variables_dict, pop=True)
|
|
container_config_file, container_config = self._load_config(
|
|
lxcpath, name
|
|
)
|
|
|
|
# Note used ast literal_eval because AnsibleModule does not provide for
|
|
# adequate dictionary parsing.
|
|
# Issue: https://github.com/ansible/ansible/issues/7679
|
|
# TODO(cloudnull) adjust import when issue has been resolved.
|
|
options_dict = variables_dict.pop('options')
|
|
options_dict = ast.literal_eval(options_dict)
|
|
parsed_options = [i.split('=') for i in options_dict]
|
|
|
|
for key, value in parsed_options:
|
|
new_entry = '%s = %s\n' % (key, value)
|
|
for option_line in container_config:
|
|
# Look for key in config
|
|
if option_line.startswith(key):
|
|
_, _value = option_line.split('=')
|
|
config_value = ' '.join(_value.split())
|
|
line_index = container_config.index(option_line)
|
|
# If the sanitized values don't match replace them
|
|
if value != config_value:
|
|
line_index += 1
|
|
if new_entry not in container_config:
|
|
self.state_change = True
|
|
container_config.insert(line_index, new_entry)
|
|
# Break the flow as values are written or not at this point
|
|
break
|
|
else:
|
|
self.state_change = True
|
|
container_config.append(new_entry)
|
|
|
|
# If the state changed restart the container.
|
|
if self.state_change is True:
|
|
self._ensure_state(
|
|
name=name, state='stopped', variables_dict=variables_dict
|
|
)
|
|
|
|
with open(container_config_file, 'wb') as f:
|
|
f.writelines(container_config)
|
|
|
|
self._ensure_state(
|
|
name=name, state='running', variables_dict=variables_dict
|
|
)
|
|
|
|
def _get_lxc_vg(self):
|
|
"""Return the name of the Volume Group used in LXC."""
|
|
build_command = [
|
|
self.module.get_bin_path('lxc-config', True),
|
|
"lxc.bdev.lvm.vg"
|
|
]
|
|
rc, vg, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = " Failed to read LVM VG from LXC config"
|
|
self.failure(err, rc, msg)
|
|
else:
|
|
return vg.strip()
|
|
|
|
def _lvm_lv_list(self):
|
|
"""Return a list of all lv in a current vg."""
|
|
vg = self._get_lxc_vg()
|
|
build_command = [
|
|
self.module.get_bin_path('lvs', True),
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = "Failed to get list of LVs"
|
|
self.failure(err, rc, msg)
|
|
|
|
all_lvms = [i.split() for i in stdout.splitlines()][1:]
|
|
return [lv_entry[0] for lv_entry in all_lvms if lv_entry[1] == vg]
|
|
|
|
def _get_vg_free_pe(self, name):
|
|
"""Return the available size of a given VG.
|
|
|
|
Returns ``size``, ``measurement``
|
|
|
|
:param name: ``str`` Name of volume group.
|
|
"""
|
|
build_command = [
|
|
'vgdisplay',
|
|
name
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
self.failure(err, rc, msg='failed to read vg %s' % name)
|
|
|
|
vg_info = [i.strip() for i in stdout.splitlines()][1:]
|
|
free_pe = [i for i in vg_info if i.startswith('Free')]
|
|
_free_pe = free_pe[0].split()
|
|
return float(_free_pe[-2]), _free_pe[-1]
|
|
|
|
def _get_lv_size(self, name):
|
|
"""Return the available size of a given LV.
|
|
|
|
Returns ``size``, ``measurement``
|
|
|
|
:param name: ``str`` Name of volume.
|
|
"""
|
|
vg = self._get_lxc_vg()
|
|
lv = os.path.join(vg, name)
|
|
build_command = [
|
|
'lvdisplay',
|
|
lv
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
self.failure(err, rc, msg='failed to read lv %s' % lv)
|
|
|
|
lv_info = [i.strip() for i in stdout.splitlines()][1:]
|
|
free_pe = [i for i in lv_info if i.startswith('LV Size')]
|
|
_free_pe = free_pe[0].split()
|
|
return float(_free_pe[-2]), _free_pe[-1]
|
|
|
|
def _lvm_snapshot_create(self, source_lv, snapshot_name,
|
|
snapshot_size_gb=5):
|
|
"""Create an LVM snapshot.
|
|
|
|
:param source_lv: ``str`` Name of lv to snapshot
|
|
:param snapshot_name: ``str`` Name of lv snapshot
|
|
:param snapshot_size_gb: ``int`` Size of snapshot to create
|
|
"""
|
|
vg = self._get_lxc_vg()
|
|
free_space, messurement = self._get_vg_free_pe(name=vg)
|
|
|
|
if free_space < float(snapshot_size_gb):
|
|
message = (
|
|
'Snapshot size [ %s ] is > greater than [ %s ] on volume group'
|
|
' [ %s ]' % (snapshot_size_gb, free_space, vg)
|
|
)
|
|
self.failure(
|
|
error='Not enough space to create snapshot',
|
|
rc=2,
|
|
msg=message
|
|
)
|
|
|
|
# Create LVM Snapshot
|
|
build_command = [
|
|
self.module.get_bin_path('lvcreate', True),
|
|
"-n",
|
|
snapshot_name,
|
|
"-s",
|
|
os.path.join(vg, source_lv),
|
|
"-L%sg" % snapshot_size_gb
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = (
|
|
'Failed to Create LVM snapshot %(vg)s/%(source_lv)s'
|
|
' --> %(snapshot_name)s'
|
|
% {'vg': vg,
|
|
'source_lv': source_lv,
|
|
'snapshot_name': snapshot_name}
|
|
)
|
|
self.failure(err, rc, msg)
|
|
|
|
def _lvm_lv_remove(self, name):
|
|
vg = self._get_lxc_vg()
|
|
# Create LVM Snapshot
|
|
build_command = [
|
|
self.module.get_bin_path('lvremove', True),
|
|
"-f",
|
|
"%(vg)s/%(name)s" % dict(vg=vg, name=name),
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = ("Failed to remove LVM LV %(vg)s/%(name)s "
|
|
% {'vg': vg,
|
|
'name': name})
|
|
self.failure(err, rc, msg)
|
|
|
|
def _lvm_lv_mount(self, lv_name, mount_point):
|
|
# mount an lv
|
|
vg = self._get_lxc_vg()
|
|
build_command = [
|
|
self.module.get_bin_path('mount', True),
|
|
"/dev/%(vg)s/%(lv_name)s" % dict(vg=vg, lv_name=lv_name),
|
|
mount_point,
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = ("failed to mountlvm lv %(vg)s/%(lv_name)s to %(mp)s"
|
|
% {'vg': vg,
|
|
'lv_name': lv_name,
|
|
'mp': mount_point})
|
|
self.failure(err, rc, msg)
|
|
|
|
def _unmount(self, mount_point):
|
|
# Unmount a file system
|
|
build_command = [
|
|
self.module.get_bin_path('umount', True),
|
|
mount_point,
|
|
]
|
|
rc, stdout, err = self._run_command(build_command)
|
|
if rc not in self.rc:
|
|
msg = ("failed to unmount %(mp)s" % {'mp': mount_point})
|
|
self.failure(err, rc, msg)
|
|
|
|
def _create_tar(self, source_dir, archive_name):
|
|
"""Create an archive of a given ``source_dir`` to ``output_path``.
|
|
|
|
:param source_dir: ``str`` Path to the directory to be archived.
|
|
:param archive_name: ``str`` Name of the archive file.
|
|
"""
|
|
# remove trailing / if present.
|
|
output_path = archive_name.rstrip(os.sep)
|
|
if not output_path.endswith('tar.bz2'):
|
|
output_path = '%s.tar.bz2' % output_path
|
|
|
|
source_path = os.path.expanduser(source_dir)
|
|
build_command = [
|
|
self.module.get_bin_path('tar', True),
|
|
'--directory=%s' % source_path,
|
|
'-cjf',
|
|
output_path,
|
|
'.'
|
|
]
|
|
|
|
rc, stdout, err = self._run_command(
|
|
build_command=build_command, unsafe_shell=True
|
|
)
|
|
|
|
if rc not in self.rc:
|
|
msg = "failed to create tar archive [ %s ]" % build_command
|
|
self.failure(err, rc, msg)
|
|
|
|
return output_path
|
|
|
|
@staticmethod
|
|
def _roundup(num):
|
|
"""Return a rounded floating point number.
|
|
|
|
:param num: ``float`` Number to round up.
|
|
"""
|
|
num, part = str(num).split('.')
|
|
num = int(num)
|
|
if int(part) != 0:
|
|
num += 1
|
|
return num
|
|
|
|
def _container_create_tar(self, variables):
|
|
"""Create a tar archive from an LXC container.
|
|
|
|
The process is as follows:
|
|
* Freeze the container (pause processes)
|
|
* Create temporary dir
|
|
* Copy container config to tmpdir/
|
|
* Unfreeze the container
|
|
* If LVM backed:
|
|
* Create LVM snapshot of LV backing the container
|
|
* Mount the snapshot to tmpdir/rootfs
|
|
* Create tar of tmpdir
|
|
* Clean up
|
|
|
|
:param variables: ``list`` List of all variables that are available to
|
|
use within the LXC Command
|
|
"""
|
|
required_vars = ['name', 'tarpath']
|
|
variables_dict = self._get_vars(variables, required=required_vars)
|
|
name = variables_dict.pop('name')
|
|
|
|
lxc_config_path = self._load_lxcpath(variables_dict)
|
|
|
|
config_file, options = self._load_config(lxc_config_path, name)
|
|
lxc_rootfs = [i for i in options if i.startswith('lxc.rootfs')]
|
|
if lxc_rootfs:
|
|
root_path = [i.strip() for i in lxc_rootfs[0].split('=')][1]
|
|
else:
|
|
message = (
|
|
'Check the config file for container [ %s ] @ [ %s ]'
|
|
% (name, config_file)
|
|
)
|
|
return self.failure(
|
|
error='No rootfs entry found in config.',
|
|
rc=2,
|
|
msg=message
|
|
)
|
|
|
|
# Create a temp dir
|
|
temp_dir = tempfile.mkdtemp()
|
|
|
|
# Set the name of the working dir, temp + container_name
|
|
work_dir = os.path.join(temp_dir, name)
|
|
|
|
# Set the path to the container data
|
|
container_path = os.path.join(lxc_config_path, name)
|
|
|
|
# Get current container info
|
|
container_data = self._get_container_info(
|
|
name=name, variables_dict=variables_dict
|
|
)
|
|
|
|
# set current state
|
|
state = container_data.get('state')
|
|
|
|
# Ensure the original container is stopped or frozen
|
|
if state not in ['stopped', 'frozen']:
|
|
# Freeze Container
|
|
self._ensure_state(
|
|
state='frozen', name=name, variables_dict=variables_dict
|
|
)
|
|
|
|
# Prepare tmp dir
|
|
build_command = [
|
|
self.module.get_bin_path('rsync', True),
|
|
'-aHAX',
|
|
container_path,
|
|
temp_dir
|
|
]
|
|
rc, stdout, err = self._run_command(build_command, unsafe_shell=True)
|
|
if rc not in self.rc:
|
|
self.failure(err, rc, msg='failed to perform backup')
|
|
|
|
mount_point = os.path.join(work_dir, 'rootfs')
|
|
if not os.path.exists(mount_point):
|
|
os.makedirs(mount_point)
|
|
|
|
# Restore original state of container
|
|
self._ensure_state(
|
|
state=state, name=name, variables_dict=variables_dict
|
|
)
|
|
|
|
# Test if the containers rootfs is a block device
|
|
block_backed = root_path.startswith(os.path.join(os.sep, 'dev'))
|
|
snapshot_name = '%s_rpc_ansible_snapshot' % name
|
|
|
|
if block_backed:
|
|
if snapshot_name not in self._lvm_lv_list():
|
|
# Take snapshot
|
|
size, measurement = self._get_lv_size(name=name)
|
|
self._lvm_snapshot_create(
|
|
source_lv=name,
|
|
snapshot_name=snapshot_name,
|
|
snapshot_size_gb=self._roundup(num=size)
|
|
)
|
|
|
|
# Mount snapshot
|
|
self._lvm_lv_mount(
|
|
lv_name=snapshot_name, mount_point=mount_point
|
|
)
|
|
|
|
try:
|
|
# Create Tar
|
|
archive_file = self._create_tar(
|
|
source_dir=work_dir, archive_name=variables_dict['tarpath']
|
|
)
|
|
except Exception as exp:
|
|
self.failure(error=exp, rc=2, msg='Failed to create the archive')
|
|
else:
|
|
# Set the state as changed and set a new fact
|
|
self.state_change = True
|
|
archive_fact = {
|
|
name: {
|
|
'archive': archive_file
|
|
}
|
|
}
|
|
return self._lxc_facts(facts=archive_fact)
|
|
finally:
|
|
if block_backed:
|
|
# unmount snapshot
|
|
self._unmount(mount_point)
|
|
|
|
# Remove snapshot
|
|
self._lvm_lv_remove(snapshot_name)
|
|
|
|
# Remove tmpdir
|
|
shutil.rmtree(os.path.dirname(work_dir))
|
|
|
|
|
|
def main():
|
|
"""Ansible Main module."""
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
name=dict(
|
|
type='str'
|
|
),
|
|
return_code=dict(
|
|
type='str',
|
|
default='0'
|
|
),
|
|
template=dict(
|
|
type='str',
|
|
default='ubuntu'
|
|
),
|
|
backingstore=dict(
|
|
type='str'
|
|
),
|
|
template_options=dict(
|
|
type='str'
|
|
),
|
|
config=dict(
|
|
type='str',
|
|
default='/etc/lxc/default.conf'
|
|
),
|
|
bdev=dict(
|
|
type='str'
|
|
),
|
|
lvname=dict(
|
|
type='str'
|
|
),
|
|
vgname=dict(
|
|
type='str'
|
|
),
|
|
thinpool=dict(
|
|
type='str'
|
|
),
|
|
fstype=dict(
|
|
type='str'
|
|
),
|
|
fssize=dict(
|
|
type='str'
|
|
),
|
|
dir=dict(
|
|
type='str'
|
|
),
|
|
zfsroot=dict(
|
|
type='str'
|
|
),
|
|
lxcpath=dict(
|
|
type='str'
|
|
),
|
|
keepname=dict(
|
|
choices=BOOLEANS,
|
|
default='false'
|
|
),
|
|
snapshot=dict(
|
|
choices=BOOLEANS,
|
|
default='false'
|
|
),
|
|
newpath=dict(
|
|
type='str'
|
|
),
|
|
orig=dict(
|
|
type='str'
|
|
),
|
|
new=dict(
|
|
type='str'
|
|
),
|
|
state=dict(
|
|
choices=[
|
|
'running',
|
|
'stopped'
|
|
],
|
|
default='running'
|
|
),
|
|
command=dict(
|
|
required=True,
|
|
choices=COMMAND_MAP.keys()
|
|
),
|
|
container_command=dict(
|
|
type='str'
|
|
),
|
|
options=dict(
|
|
type='str'
|
|
),
|
|
return_facts=dict(
|
|
choices=BOOLEANS,
|
|
default=False
|
|
),
|
|
tarpath=dict(
|
|
type='str'
|
|
)
|
|
),
|
|
supports_check_mode=False,
|
|
)
|
|
|
|
return_code = module.params.get('return_code', '').split(',')
|
|
module.params['return_code'] = return_code
|
|
|
|
lm = LxcManagement(module=module)
|
|
lm.command_router()
|
|
|
|
|
|
# import module bits
|
|
from ansible.module_utils.basic import *
|
|
main()
|