Skip to content

Commit

Permalink
Generate an init file that can be used with exported images
Browse files Browse the repository at this point in the history
The generated init file will represent the env and will contain
relative paths to the exported images.

Signed-off-by: gbenhaim <[email protected]>
  • Loading branch information
gbenhaim committed Mar 14, 2017
1 parent a1c4a07 commit 5fd1373
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 7 deletions.
6 changes: 4 additions & 2 deletions lago/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,8 +315,10 @@ def do_stop(prefix, vm_names, **kwargs):
)
@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, out_format, **kwargs
):
prefix.export_vms(vm_names, standalone, dst_dir, compress, 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
12 changes: 8 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,12 @@ 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, out_format
):
self.virt_env.export_vms(
vms_names, standalone, export_dir, compress, out_format
)

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


def filter_spec(spec, paths):
"""
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 file to remove keys from
paths (list): list of paths to the keys that should be removed
Returns:
None
"""

def remove_key(path, spec):
if len(path) == 0:
return
elif len(path) == 1:
key = path.pop()
if not isinstance(spec, dict):
raise LagoUserException(
'Can not remove key "{}" from "{}" {}'.
format(key, spec, type(spec))
)
spec.pop(key, None)
else:
current = path[0]
if current == '*':
if isinstance(spec, list):
iterator = iter(spec)
elif isinstance(spec, dict):
iterator = spec.itervalues()
else:
raise LagoUserException(
'Glob char * should refer only to dict or list, '
'not to {}'.format(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 "{}", key "{}" '
'does not exist'.format('/'.join(path), current)
)
except TypeError:
raise LagoUserException(
'Malformed path "{}", can not get '
'by key from type {}'.
format('/'.join(path), type(spec))
)

for path in paths:
remove_key(path.split('/'), spec)


class LagoException(Exception):
pass

Expand Down
84 changes: 83 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,7 @@ 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, out_format):
if not vms_names:
vms_names = self._vms.keys()

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

self.generate_init(dst_dir, out_format)

def generate_init(self, dst_dir, 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_dir (str): path to the new init file
out_format (plugins.output.OutFormatPlugin):
formatter for the output
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_dir)):
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(os.path.join(dst_dir, 'LagoInitFile'), '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 +537,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

0 comments on commit 5fd1373

Please sign in to comment.