Skip to content

Commit

Permalink
Merge pull request #477 from lago-project/export_init_file
Browse files Browse the repository at this point in the history
Generate an init file that can be used with exported images
  • Loading branch information
lago-bot authored Mar 16, 2017
2 parents d12ed68 + 3441728 commit 0ea1688
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 7 deletions.
14 changes: 12 additions & 2 deletions lago/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,20 @@ def do_stop(prefix, vm_names, **kwargs):
default='.',
help='Dir to place the exported images in',
)
@lago.plugins.cli.cli_plugin_add_argument(
'--init-file-name',
default='LagoInitFile',
help='The name of the exported init file',
)
@in_lago_prefix
@with_logging
def do_export(prefix, vm_names, standalone, dst_dir, compress, **kwargs):
prefix.export_vms(vm_names, standalone, dst_dir, compress)
def do_export(
prefix, vm_names, standalone, dst_dir, compress, init_file_name,
out_format, **kwargs
):
prefix.export_vms(
vm_names, standalone, dst_dir, compress, init_file_name, out_format
)


@lago.plugins.cli.cli_plugin(
Expand Down
5 changes: 5 additions & 0 deletions lago/plugins/vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
create an alternative implementation of the provisioning details for the VM,
for example, using a remote libvirt instance or similar.
"""
from copy import deepcopy
import contextlib
import functools
import logging
Expand Down Expand Up @@ -364,6 +365,10 @@ def metadata(self):
def disks(self):
return self._spec['disks'][:]

@property
def spec(self):
return deepcopy(self._spec)

def name(self):
return str(self._spec['name'])

Expand Down
14 changes: 10 additions & 4 deletions lago/prefix.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,8 @@ def _create_disks(
'dev': disk['dev'],
'format': disk['format'],
'metadata': metadata,
'type': disk['type']
'type': disk['type'],
'name': disk['name']
},
)

Expand Down Expand Up @@ -1009,9 +1010,14 @@ def virt_conf(
self.save()
rollback.clear()

def export_vms(self, vms_names, standalone, export_dir, compress):

self.virt_env.export_vms(vms_names, standalone, export_dir, compress)
def export_vms(
self, vms_names, standalone, export_dir, compress, init_file_name,
out_format
):
self.virt_env.export_vms(
vms_names, standalone, export_dir, compress, init_file_name,
out_format
)

def start(self, vm_names=None):
"""
Expand Down
77 changes: 77 additions & 0 deletions lago/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,83 @@ def get_hash(file_path, checksum='sha1'):
return sha.hexdigest()


def filter_spec(spec, paths, wildcard='*', separator='/'):
"""
Remove keys from a spec file.
For example, with the following path: domains/*/disks/*/metadata
all the metadata dicts from all domains disks will be removed.
Args:
spec (dict): spec to remove keys from
paths (list): list of paths to the keys that should be removed
wildcard (str): wildcard character
separator (str): path separator
Returns:
None
Raises:
utils.LagoUserException: If a malformed path was detected
"""

def remove_key(path, spec):
if len(path) == 0:
return
elif len(path) == 1:
key = path.pop()
if not isinstance(spec, collections.Mapping):
raise LagoUserException(
'You have tried to remove the following key - "{key}".\n'
'Keys can not be removed from type {spec_type}\n'
'Please verify that path - "{{path}}" is valid'.format(
key=key, spec_type=type(spec)
)
)
if key == wildcard:
spec.clear()
else:
spec.pop(key, None)
else:
current = path[0]
if current == wildcard:
if isinstance(spec, list):
iterator = iter(spec)
elif isinstance(spec, collections.Mapping):
iterator = spec.itervalues()
else:
raise LagoUserException(
'Glob char {char} should refer only to dict or list, '
'not to {spec_type}\n'
'Please fix path - "{{path}}"'.format(
char=wildcard, spec_type=type(spec)
)
)

for i in iterator:
remove_key(path[1:], i)
else:
try:
remove_key(path[1:], spec[current])
except KeyError:
raise LagoUserException(
'Malformed path "{{path}}", key "{key}" '
'does not exist'.format(key=current)
)
except TypeError:
raise LagoUserException(
'Malformed path "{{path}}", can not get '
'by key from type {spec_type}'.
format(spec_type=type(spec))
)

for path in paths:
try:
remove_key(path.split(separator), spec)
except LagoUserException as e:
e.message = e.message.format(path=path)
raise


class LagoException(Exception):
pass

Expand Down
93 changes: 92 additions & 1 deletion lago/virt.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#
# Refer to the README and COPYING files for full details of the license
#
from copy import deepcopy
import functools
import hashlib
import json
Expand All @@ -25,6 +26,7 @@
import uuid
import time
import lxml.etree
import yaml

from . import (
brctl,
Expand Down Expand Up @@ -191,7 +193,10 @@ def virt_path(self, *args):
def bootstrap(self):
utils.invoke_in_parallel(lambda vm: vm.bootstrap(), self._vms.values())

def export_vms(self, vms_names, standalone, dst_dir, compress):
def export_vms(
self, vms_names, standalone, dst_dir, compress, init_file_name,
out_format
):
if not vms_names:
vms_names = self._vms.keys()

Expand Down Expand Up @@ -219,6 +224,88 @@ def export_vms(self, vms_names, standalone, dst_dir, compress):
for _vm in vms:
_vm.export_disks(standalone, dst_dir, compress)

self.generate_init(os.path.join(dst_dir, init_file_name), out_format)

def generate_init(self, dst, out_format, filters=None):
"""
Generate an init file which represents this env and can
be used with the images created by self.export_vms
Args:
dst (str): path and name of the new init file
out_format (plugins.output.OutFormatPlugin):
formatter for the output (the default is yaml)
filters (list): list of paths to keys that should be removed from
the init file
Returns:
None
"""
with LogTask('Exporting init file to: {}'.format(dst)):
# Set the default formatter to yaml. The default formatter
# doesn't generate a valid init file, so it's not reasonable
# to use it
if isinstance(out_format, plugins.output.DefaultOutFormatPlugin):
out_format = plugins.output.YAMLOutFormatPlugin()

if not filters:
filters = [
'domains/*/disks/*/metadata',
'domains/*/metadata/deploy-scripts', 'domains/*/snapshots',
'domains/*/name', 'nets/*/mapping'
]
spec = self.get_env_spec(filters)

for _, domain in spec['domains'].viewitems():
for disk in domain['disks']:
if disk['type'] == 'template':
disk['template_type'] = 'qcow2'
elif disk['type'] == 'empty':
disk['type'] = 'file'
disk['make_a_copy'] = 'True'

# Insert the relative path to the exported images
disk['path'] = os.path.join(
'$LAGO_INITFILE_PATH', os.path.basename(disk['path'])
)

with open(dst, 'wt') as f:
if isinstance(out_format, plugins.output.YAMLOutFormatPlugin):
# Dump the yaml file without type tags
# TODO: Allow passing parameters to output plugins
f.write(yaml.safe_dump(spec))
else:
f.write(out_format.format(spec))

def get_env_spec(self, filters=None):
"""
Get the spec of the current env.
The spec will hold the info about all the domains and
networks associated with this env.
Args:
filters (list): list of paths to keys that should be removed from
the init file
Returns:
dict: the spec of the current env
"""
spec = {
'domains':
{
vm_name: vm_object.spec
for vm_name, vm_object in self._vms.viewitems()
},
'nets':
{
net_name: net_object.spec
for net_name, net_object in self._nets.viewitems()
}
}

if filters:
utils.filter_spec(spec, filters)

return spec

def start(self, vm_names=None):
if not vm_names:
log_msg = 'Start Prefix'
Expand Down Expand Up @@ -459,6 +546,10 @@ def save(self):
with open(self._env.virt_path('net-%s' % self.name()), 'w') as f:
utils.json_dump(self._spec, f)

@property
def spec(self):
return deepcopy(self._spec)


class NATNetwork(Network):
def _libvirt_xml(self):
Expand Down
1 change: 1 addition & 0 deletions tests/functional/fixtures/snapshot/1host_1disk_list
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"size": 1780088832,
"version": "v1"
},
"name": "root",
"path": "@@PREFIX_PATH@@/images/lago_functional_tests_vm01_root.qcow2",
"type": "template"
}
Expand Down

0 comments on commit 0ea1688

Please sign in to comment.