summaryrefslogtreecommitdiff
path: root/cloud/lxc
diff options
context:
space:
mode:
authorBrian Coca <bcoca@ansible.com>2015-05-13 14:01:23 -0400
committerBrian Coca <bcoca@ansible.com>2015-05-13 14:01:23 -0400
commit56f466c70d2c15f17e02c841c924087d37b206bf (patch)
tree4f07df75f4c2685a35032fb421e3123849b0ea44 /cloud/lxc
parentbfb8868d8b82f88eaae33956fd70f9a63624257f (diff)
parent2f188600a8239ddd31407cae01b76579280d627e (diff)
downloadansible-modules-extras-56f466c70d2c15f17e02c841c924087d37b206bf.tar.gz
Merge pull request #328 from cloudnull/lxc-overlayfs-feature
Added overlayfs backend type to the lxc_container module
Diffstat (limited to 'cloud/lxc')
-rw-r--r--cloud/lxc/lxc_container.py315
1 files changed, 263 insertions, 52 deletions
diff --git a/cloud/lxc/lxc_container.py b/cloud/lxc/lxc_container.py
index a7c768c2..b36b60e5 100644
--- a/cloud/lxc/lxc_container.py
+++ b/cloud/lxc/lxc_container.py
@@ -38,6 +38,7 @@ options:
- lvm
- loop
- btrfs
+ - overlayfs
description:
- Backend storage type for the container.
required: false
@@ -112,6 +113,20 @@ options:
- Set the log level for a container where *container_log* was set.
required: false
default: INFO
+ clone_name:
+ description:
+ - Name of the new cloned server. This is only used when state is
+ clone.
+ required: false
+ clone_snapshot:
+ choices:
+ - true
+ - false
+ description:
+ - Create a snapshot a container when cloning. This is not supported
+ by all container storage backends. Enabling this may fail if the
+ backing store does not support snapshots.
+ default: false
archive:
choices:
- true
@@ -141,8 +156,12 @@ options:
- restarted
- absent
- frozen
+ - clone
description:
- - Start a container right after it's created.
+ - Define the state of a container. If you use clone the container
+ will be stopped while the clone operation is happening and upon
+ completion of the clone the original container state will be
+ restored.
required: false
default: started
container_config:
@@ -298,6 +317,47 @@ EXAMPLES = """
archive: true
archive_path: /opt/archives
+- name: Create an overlayfs container
+ lxc_container:
+ name: test-container-overlayfs
+ container_log: true
+ template: ubuntu
+ state: started
+ backing_store: overlayfs
+ template_options: --release trusty
+
+- name: Clone a container
+ lxc_container:
+ name: test-container-overlayfs
+ clone_name: test-container-clone
+ state: clone
+
+- name: Clone a container using snapshot.
+ lxc_container:
+ name: test-container-overlayfs
+ clone_name: test-container-overlayfs-clone
+ backing_store: overlayfs
+ clone_snapshot: true
+ state: clone
+
+- name: Create a new container and clone it
+ lxc_container:
+ name: test-container-new-overlayfs
+ clone_name: test-container-new-overlayfs-clone
+ backing_store: overlayfs
+ clone_snapshot: true
+ state: clone
+
+- name: Create a new container, clone it, and archive
+ lxc_container:
+ name: test-container-new-overlayfs
+ clone_name: test-container-new-overlayfs-clone
+ backing_store: overlayfs
+ clone_snapshot: true
+ state: clone
+ archive: true
+ archive_compression: gzip
+
- name: Destroy a container.
lxc_container:
name: "{{ item }}"
@@ -308,6 +368,9 @@ EXAMPLES = """
- test-container-frozen
- test-container-lvm
- test-container-config
+ - test-container-overlayfs
+ - test-container-clone
+ - test-container-overlayfs-clone
"""
@@ -354,6 +417,15 @@ LXC_COMMAND_MAP = {
'directory': '--dir',
'zfs_root': '--zfsroot'
}
+ },
+ 'clone': {
+ 'variables': {
+ 'backing_store': '--backingstore',
+ 'lxc_path': '--lxcpath',
+ 'fs_size': '--fssize',
+ 'name': '--orig',
+ 'clone_name': '--new'
+ }
}
}
@@ -372,6 +444,9 @@ LXC_BACKING_STORE = {
],
'loop': [
'lv_name', 'vg_name', 'thinpool', 'zfs_root'
+ ],
+ 'overlayfs': [
+ 'lv_name', 'vg_name', 'fs_type', 'fs_size', 'thinpool', 'zfs_root'
]
}
@@ -391,7 +466,8 @@ LXC_ANSIBLE_STATES = {
'stopped': '_stopped',
'restarted': '_restarted',
'absent': '_destroyed',
- 'frozen': '_frozen'
+ 'frozen': '_frozen',
+ 'clone': '_clone'
}
@@ -505,15 +581,15 @@ class LxcContainerManagement(object):
return num
@staticmethod
- def _container_exists(name):
+ def _container_exists(container_name):
"""Check if a container exists.
- :param name: Name of the container.
+ :param container_name: Name of the container.
:type: ``str``
:returns: True or False if the container is found.
:rtype: ``bol``
"""
- if [i for i in lxc.list_containers() if i == name]:
+ if [i for i in lxc.list_containers() if i == container_name]:
return True
else:
return False
@@ -546,6 +622,7 @@ class LxcContainerManagement(object):
"""
# Remove incompatible storage backend options.
+ variables = variables.copy()
for v in LXC_BACKING_STORE[self.module.params['backing_store']]:
variables.pop(v, None)
@@ -658,6 +735,83 @@ class LxcContainerManagement(object):
self._container_startup()
self.container.freeze()
+ def _clone(self, count=0):
+ """Clone a new LXC container from an existing container.
+
+ This method will clone an existing container to a new container using
+ the `clone_name` variable as the new container name. The method will
+ create a container if the container `name` does not exist.
+
+ Note that cloning a container will ensure that the original container
+ is "stopped" before the clone can be done. Because this operation can
+ require a state change the method will return the original container
+ to its prior state upon completion of the clone.
+
+ Once the clone is complete the new container will be left in a stopped
+ state.
+ """
+
+ self.check_count(count=count, method='clone')
+ if self._container_exists(container_name=self.container_name):
+ # Ensure that the state of the original container is stopped
+ container_state = self._get_state()
+ if container_state != 'stopped':
+ self.state_change = True
+ self.container.stop()
+
+ build_command = [
+ self.module.get_bin_path('lxc-clone', True),
+ ]
+
+ build_command = self._add_variables(
+ variables_dict=self._get_vars(
+ variables=LXC_COMMAND_MAP['clone']['variables']
+ ),
+ build_command=build_command
+ )
+
+ # Load logging for the instance when creating it.
+ if self.module.params.get('clone_snapshot') in BOOLEANS_TRUE:
+ build_command.append('--snapshot')
+
+ rc, return_data, err = self._run_command(build_command)
+ if rc != 0:
+ message = "Failed executing lxc-clone."
+ self.failure(
+ err=err, rc=rc, msg=message, command=' '.join(
+ build_command
+ )
+ )
+ else:
+ self.state_change = True
+ # Restore the original state of the origin container if it was
+ # not in a stopped state.
+ if container_state == 'running':
+ self.container.start()
+ elif container_state == 'frozen':
+ self.container.start()
+ self.container.freeze()
+
+ # Change the container name context to the new cloned container
+ # This enforces that the state of the new cloned container will be
+ # "stopped".
+ self.state = 'stopped'
+ self.container_name = self.module.params['clone_name']
+ self.container = self.get_container_bind()
+
+ # Return data
+ self._execute_command()
+
+ # Perform any configuration updates
+ self._config()
+
+ # Check if the container needs to have an archive created.
+ self._check_archive()
+ else:
+ self._create()
+ count += 1
+ self._clone(count)
+
def _create(self):
"""Create a new LXC container.
@@ -712,9 +866,9 @@ class LxcContainerManagement(object):
rc, return_data, err = self._run_command(build_command)
if rc != 0:
- msg = "Failed executing lxc-create."
+ message = "Failed executing lxc-create."
self.failure(
- err=err, rc=rc, msg=msg, command=' '.join(build_command)
+ err=err, rc=rc, msg=message, command=' '.join(build_command)
)
else:
self.state_change = True
@@ -754,7 +908,7 @@ class LxcContainerManagement(object):
:rtype: ``str``
"""
- if self._container_exists(name=self.container_name):
+ if self._container_exists(container_name=self.container_name):
return str(self.container.state).lower()
else:
return str('absent')
@@ -819,7 +973,7 @@ class LxcContainerManagement(object):
"""
for _ in xrange(timeout):
- if not self._container_exists(name=self.container_name):
+ if not self._container_exists(container_name=self.container_name):
break
# Check if the container needs to have an archive created.
@@ -855,7 +1009,7 @@ class LxcContainerManagement(object):
"""
self.check_count(count=count, method='frozen')
- if self._container_exists(name=self.container_name):
+ if self._container_exists(container_name=self.container_name):
self._execute_command()
# Perform any configuration updates
@@ -889,7 +1043,7 @@ class LxcContainerManagement(object):
"""
self.check_count(count=count, method='restart')
- if self._container_exists(name=self.container_name):
+ if self._container_exists(container_name=self.container_name):
self._execute_command()
# Perform any configuration updates
@@ -916,7 +1070,7 @@ class LxcContainerManagement(object):
"""
self.check_count(count=count, method='stop')
- if self._container_exists(name=self.container_name):
+ if self._container_exists(container_name=self.container_name):
self._execute_command()
# Perform any configuration updates
@@ -943,7 +1097,7 @@ class LxcContainerManagement(object):
"""
self.check_count(count=count, method='start')
- if self._container_exists(name=self.container_name):
+ if self._container_exists(container_name=self.container_name):
container_state = self._get_state()
if container_state == 'running':
pass
@@ -1010,18 +1164,18 @@ class LxcContainerManagement(object):
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):
+ def _get_vg_free_pe(self, vg_name):
"""Return the available size of a given VG.
- :param name: Name of volume.
- :type name: ``str``
+ :param vg_name: Name of volume.
+ :type vg_name: ``str``
:returns: size and measurement of an LV
:type: ``tuple``
"""
build_command = [
'vgdisplay',
- name,
+ vg_name,
'--units',
'g'
]
@@ -1030,7 +1184,7 @@ class LxcContainerManagement(object):
self.failure(
err=err,
rc=rc,
- msg='failed to read vg %s' % name,
+ msg='failed to read vg %s' % vg_name,
command=' '.join(build_command)
)
@@ -1039,17 +1193,17 @@ class LxcContainerManagement(object):
_free_pe = free_pe[0].split()
return float(_free_pe[-2]), _free_pe[-1]
- def _get_lv_size(self, name):
+ def _get_lv_size(self, lv_name):
"""Return the available size of a given LV.
- :param name: Name of volume.
- :type name: ``str``
+ :param lv_name: Name of volume.
+ :type lv_name: ``str``
:returns: size and measurement of an LV
:type: ``tuple``
"""
vg = self._get_lxc_vg()
- lv = os.path.join(vg, name)
+ lv = os.path.join(vg, lv_name)
build_command = [
'lvdisplay',
lv,
@@ -1083,7 +1237,7 @@ class LxcContainerManagement(object):
"""
vg = self._get_lxc_vg()
- free_space, messurement = self._get_vg_free_pe(name=vg)
+ free_space, messurement = self._get_vg_free_pe(vg_name=vg)
if free_space < float(snapshot_size_gb):
message = (
@@ -1186,25 +1340,25 @@ class LxcContainerManagement(object):
return archive_name
- def _lvm_lv_remove(self, name):
+ def _lvm_lv_remove(self, lv_name):
"""Remove an LV.
- :param name: The name of the logical volume
- :type name: ``str``
+ :param lv_name: The name of the logical volume
+ :type lv_name: ``str``
"""
vg = self._get_lxc_vg()
build_command = [
self.module.get_bin_path('lvremove', True),
"-f",
- "%s/%s" % (vg, name),
+ "%s/%s" % (vg, lv_name),
]
rc, stdout, err = self._run_command(build_command)
if rc != 0:
self.failure(
err=err,
rc=rc,
- msg='Failed to remove LVM LV %s/%s' % (vg, name),
+ msg='Failed to remove LVM LV %s/%s' % (vg, lv_name),
command=' '.join(build_command)
)
@@ -1216,31 +1370,71 @@ class LxcContainerManagement(object):
:param temp_dir: path to the temporary local working directory
:type temp_dir: ``str``
"""
+ # This loop is created to support overlayfs archives. This should
+ # squash all of the layers into a single archive.
+ fs_paths = container_path.split(':')
+ if 'overlayfs' in fs_paths:
+ fs_paths.pop(fs_paths.index('overlayfs'))
+
+ for fs_path in fs_paths:
+ # Set the path to the container data
+ fs_path = os.path.dirname(fs_path)
+
+ # Run the sync command
+ build_command = [
+ self.module.get_bin_path('rsync', True),
+ '-aHAX',
+ fs_path,
+ temp_dir
+ ]
+ rc, stdout, err = self._run_command(
+ build_command,
+ unsafe_shell=True
+ )
+ if rc != 0:
+ self.failure(
+ err=err,
+ rc=rc,
+ msg='failed to perform archive',
+ command=' '.join(build_command)
+ )
+
+ def _unmount(self, mount_point):
+ """Unmount a file system.
+
+ :param mount_point: path on the file system that is mounted.
+ :type mount_point: ``str``
+ """
build_command = [
- self.module.get_bin_path('rsync', True),
- '-aHAX',
- container_path,
- temp_dir
+ self.module.get_bin_path('umount', True),
+ mount_point,
]
- rc, stdout, err = self._run_command(build_command, unsafe_shell=True)
+ rc, stdout, err = self._run_command(build_command)
if rc != 0:
self.failure(
err=err,
rc=rc,
- msg='failed to perform archive',
+ msg='failed to unmount [ %s ]' % mount_point,
command=' '.join(build_command)
)
- def _unmount(self, mount_point):
- """Unmount a file system.
+ def _overlayfs_mount(self, lowerdir, upperdir, mount_point):
+ """mount an lv.
+ :param lowerdir: name/path of the lower directory
+ :type lowerdir: ``str``
+ :param upperdir: name/path of the upper directory
+ :type upperdir: ``str``
:param mount_point: path on the file system that is mounted.
:type mount_point: ``str``
"""
build_command = [
- self.module.get_bin_path('umount', True),
+ self.module.get_bin_path('mount', True),
+ '-t overlayfs',
+ '-o lowerdir=%s,upperdir=%s' % (lowerdir, upperdir),
+ 'overlayfs',
mount_point,
]
rc, stdout, err = self._run_command(build_command)
@@ -1248,8 +1442,8 @@ class LxcContainerManagement(object):
self.failure(
err=err,
rc=rc,
- msg='failed to unmount [ %s ]' % mount_point,
- command=' '.join(build_command)
+ msg='failed to mount overlayfs:%s:%s to %s -- Command: %s'
+ % (lowerdir, upperdir, mount_point, build_command)
)
def _container_create_tar(self):
@@ -1278,13 +1472,15 @@ class LxcContainerManagement(object):
# Test if the containers rootfs is a block device
block_backed = lxc_rootfs.startswith(os.path.join(os.sep, 'dev'))
+
+ # Test if the container is using overlayfs
+ overlayfs_backed = lxc_rootfs.startswith('overlayfs')
+
mount_point = os.path.join(work_dir, 'rootfs')
# Set the snapshot name if needed
snapshot_name = '%s_lxc_snapshot' % self.container_name
- # Set the path to the container data
- container_path = os.path.dirname(lxc_rootfs)
container_state = self._get_state()
try:
# Ensure the original container is stopped or frozen
@@ -1295,7 +1491,7 @@ class LxcContainerManagement(object):
self.container.stop()
# Sync the container data from the container_path to work_dir
- self._rsync_data(container_path, temp_dir)
+ self._rsync_data(lxc_rootfs, temp_dir)
if block_backed:
if snapshot_name not in self._lvm_lv_list():
@@ -1304,7 +1500,7 @@ class LxcContainerManagement(object):
# Take snapshot
size, measurement = self._get_lv_size(
- name=self.container_name
+ lv_name=self.container_name
)
self._lvm_snapshot_create(
source_lv=self.container_name,
@@ -1325,25 +1521,33 @@ class LxcContainerManagement(object):
' up old snapshot of containers before continuing.'
% snapshot_name
)
-
- # Restore original state of container
- if container_state == 'running':
- if self._get_state() == 'frozen':
- self.container.unfreeze()
- else:
- self.container.start()
+ elif overlayfs_backed:
+ lowerdir, upperdir = lxc_rootfs.split(':')[1:]
+ self._overlayfs_mount(
+ lowerdir=lowerdir,
+ upperdir=upperdir,
+ mount_point=mount_point
+ )
# Set the state as changed and set a new fact
self.state_change = True
return self._create_tar(source_dir=work_dir)
finally:
- if block_backed:
+ if block_backed or overlayfs_backed:
# unmount snapshot
self._unmount(mount_point)
+ if block_backed:
# Remove snapshot
self._lvm_lv_remove(snapshot_name)
+ # Restore original state of container
+ if container_state == 'running':
+ if self._get_state() == 'frozen':
+ self.container.unfreeze()
+ else:
+ self.container.start()
+
# Remove tmpdir
shutil.rmtree(temp_dir)
@@ -1453,6 +1657,14 @@ def main():
choices=[n for i in LXC_LOGGING_LEVELS.values() for n in i],
default='INFO'
),
+ clone_name=dict(
+ type='str',
+ required=False
+ ),
+ clone_snapshot=dict(
+ choices=BOOLEANS,
+ default='false'
+ ),
archive=dict(
choices=BOOLEANS,
default='false'
@@ -1480,4 +1692,3 @@ def main():
# import module bits
from ansible.module_utils.basic import *
main()
-