summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xmorphlib/exts/kvm.check71
-rw-r--r--morphlib/morphloader.py8
-rw-r--r--morphlib/sourceresolver.py88
3 files changed, 135 insertions, 32 deletions
diff --git a/morphlib/exts/kvm.check b/morphlib/exts/kvm.check
index 1bb4007a..b8877a89 100755
--- a/morphlib/exts/kvm.check
+++ b/morphlib/exts/kvm.check
@@ -1,5 +1,5 @@
#!/usr/bin/python
-# Copyright (C) 2014 Codethink Limited
+# Copyright (C) 2014-2015 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -17,6 +17,7 @@
'''Preparatory checks for Morph 'kvm' write extension'''
import cliapp
+import os
import re
import urlparse
@@ -43,8 +44,10 @@ class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
ssh_host, vm_name, vm_path = self.check_and_parse_location(location)
self.check_ssh_connectivity(ssh_host)
+ self.check_can_create_file_at_given_path(ssh_host, vm_path)
self.check_no_existing_libvirt_vm(ssh_host, vm_name)
self.check_extra_disks_exist(ssh_host, self.parse_attach_disks())
+ self.check_virtual_networks_are_started(ssh_host)
def check_and_parse_location(self, location):
'''Check and parse the location argument to get relevant data.'''
@@ -73,6 +76,26 @@ class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
'write extension to deploy upgrades to existing machines.' %
(ssh_host, vm_name))
+ def check_can_create_file_at_given_path(self, ssh_host, vm_path):
+
+ def check_can_write_to_given_path():
+ try:
+ cliapp.ssh_runcmd(ssh_host, ['touch', vm_path])
+ except cliapp.AppException as e:
+ raise cliapp.AppException("Can't write to location %s on %s"
+ % (vm_path, ssh_host))
+ else:
+ cliapp.ssh_runcmd(ssh_host, ['rm', vm_path])
+
+ try:
+ cliapp.ssh_runcmd(ssh_host, ['test', '-e', vm_path])
+ except cliapp.AppException as e:
+ # vm_path doesn't already exist, so let's test we can write
+ check_can_write_to_given_path()
+ else:
+ raise cliapp.AppException('%s already exists on %s'
+ % (vm_path, ssh_host))
+
def check_extra_disks_exist(self, ssh_host, filename_list):
for filename in filename_list:
try:
@@ -81,4 +104,50 @@ class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
raise cliapp.AppException('Did not find file %s on host %s' %
(filename, ssh_host))
+ def check_virtual_networks_are_started(self, ssh_host):
+
+ def check_virtual_network_is_started(network_name):
+ cmd = ['virsh', '-c', 'qemu:///system', 'net-info', network_name]
+ net_info = cliapp.ssh_runcmd(ssh_host, cmd).split('\n')
+
+ def pretty_concat(lines):
+ return '\n'.join(['\t%s' % line for line in lines])
+
+ for line in net_info:
+ m = re.match('^Active:\W*(\w+)\W*', line)
+ if m:
+ break
+ else:
+ raise cliapp.AppException(
+ "Got unexpected output parsing output of `%s':\n%s"
+ % (' '.join(cmd), pretty_concat(net_info)))
+
+ network_active = m.group(1) == 'yes'
+
+ if not network_active:
+ raise cliapp.AppException("Network '%s' is not started"
+ % network_name)
+
+ def name(nic_entry):
+ if ',' in nic_entry:
+ # NETWORK_NAME,mac=12:34,model=e1000...
+ return nic_entry[:nic_entry.find(',')]
+ else:
+ return nic_entry # NETWORK_NAME
+
+ if 'NIC_CONFIG' in os.environ:
+ nics = os.environ['NIC_CONFIG'].split()
+
+ # --network bridge= is used to specify a bridge
+ # --network user is used to specify a form of NAT
+ # (see the virt-install(1) man page)
+ networks = [name(n) for n in nics if not n.startswith('bridge=')
+ and not n.startswith('user')]
+ else:
+ networks = ['default']
+
+ for network in networks:
+ check_virtual_network_is_started(network)
+
+
KvmPlusSshCheckExtension().run()
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py
index 8289b01e..e0f0952b 100644
--- a/morphlib/morphloader.py
+++ b/morphlib/morphloader.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013-2014 Codethink Limited
+# Copyright (C) 2013-2015 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -398,13 +398,17 @@ class MorphologyLoader(object):
return morphlib.morphology.Morphology(obj)
- def load_from_string(self, string, filename='string'):
+ def load_from_string(self, string,
+ filename='string'): # pragma: no cover
'''Load a morphology from a string.
Return the Morphology object.
'''
+ if string is None:
+ return None
+
m = self.parse_morphology_text(string, filename)
m.filename = filename
self.validate(m)
diff --git a/morphlib/sourceresolver.py b/morphlib/sourceresolver.py
index 22e643d2..387d2e0d 100644
--- a/morphlib/sourceresolver.py
+++ b/morphlib/sourceresolver.py
@@ -21,6 +21,7 @@ import os
import pylru
import shutil
import tempfile
+import yaml
import cliapp
@@ -31,6 +32,7 @@ tree_cache_filename = 'trees.cache.pickle'
buildsystem_cache_size = 10000
buildsystem_cache_filename = 'detected-chunk-buildsystems.cache.pickle'
+not_supported_versions = []
class PickleCacheManager(object): # pragma: no cover
'''Cache manager for PyLRU that reads and writes to Pickle files.
@@ -89,6 +91,11 @@ class MorphologyNotFoundError(SourceResolverError): # pragma: no cover
SourceResolverError.__init__(
self, "Couldn't find morphology: %s" % filename)
+class UnknownVersionError(SourceResolverError): # pragma: no cover
+ def __init__(self, version):
+ SourceResolverError.__init__(
+ self, "Definitions format version %s is not supported" % version)
+
class SourceResolver(object):
'''Provides a way of resolving the set of sources for a given system.
@@ -212,14 +219,16 @@ class SourceResolver(object):
return absref, tree
- def _get_morphology_from_definitions(self, loader,
- filename): # pragma: no cover
+ def _get_file_contents_from_definitions(self,
+ filename): # pragma: no cover
if os.path.exists(filename):
- return loader.load_from_file(filename)
+ with open(filename) as f:
+ return f.read()
else:
return None
- def _get_morphology_from_repo(self, loader, reponame, sha1, filename):
+ def _get_file_contents_from_repo(self, reponame,
+ sha1, filename): # pragma: no cover
if self.lrc.has_repo(reponame):
self.status(msg="Looking for %(reponame)s:%(filename)s in the "
"local repo cache.",
@@ -227,37 +236,29 @@ class SourceResolver(object):
try:
repo = self.lrc.get_repo(reponame)
text = repo.read_file(filename, sha1)
- morph = loader.load_from_string(text)
except IOError:
- morph = None
+ text = None
elif self.rrc is not None:
self.status(msg="Looking for %(reponame)s:%(filename)s in the "
"remote repo cache.",
reponame=reponame, filename=filename, chatty=True)
try:
text = self.rrc.cat_file(reponame, sha1, filename)
- morph = loader.load_from_string(text)
except morphlib.remoterepocache.CatFileError:
- morph = None
+ text = None
else: # pragma: no cover
repo = self.cache_repo_locally(reponame)
text = repo.read_file(filename, sha1)
- morph = loader.load_from_string(text)
- return morph
+ return text
- def _get_morphology(self, reponame, sha1, filename):
- '''Read the morphology at the specified location.
+ def _get_file_contents(self, reponame, sha1, filename): # pragma: no cover
+ '''Read the file at the specified location.
Returns None if the file does not exist in the specified commit.
'''
- key = (reponame, sha1, filename)
- if key in self._resolved_morphologies:
- return self._resolved_morphologies[key]
-
- loader = morphlib.morphloader.MorphologyLoader()
- morph = None
+ text = None
if reponame == self._definitions_repo and \
sha1 == self._definitions_absref: # pragma: no cover
@@ -265,20 +266,31 @@ class SourceResolver(object):
# we can quickly read definitions files from.
defs_filename = os.path.join(self._definitions_checkout_dir,
filename)
- morph = self._get_morphology_from_definitions(loader,
- defs_filename)
+ text = self._get_file_contents_from_definitions(defs_filename)
else:
- morph = self._get_morphology_from_repo(loader, reponame, sha1,
- filename)
+ text = self._get_file_contents_from_repo(reponame, sha1, filename)
- if morph is None:
- return None
- else:
- loader.validate(morph)
- loader.set_commands(morph)
- loader.set_defaults(morph)
+ return text
+
+ def _get_morphology(self, reponame, sha1, filename): # pragma: no cover
+ '''Read the morphology at the specified location.
+
+ Returns None if the file does not exist in the specified commit.
+
+ '''
+ key = (reponame, sha1, filename)
+ if key in self._resolved_morphologies:
+ return self._resolved_morphologies[key]
+
+ loader = morphlib.morphloader.MorphologyLoader()
+
+ text = self._get_file_contents(reponame, sha1, filename)
+ morph = loader.load_from_string(text)
+
+ if morph is not None:
self._resolved_morphologies[key] = morph
- return morph
+
+ return morph
def _detect_build_system(self, reponame, sha1, expected_filename):
'''Attempt to detect buildsystem of the given commit.
@@ -333,6 +345,22 @@ class SourceResolver(object):
loader.set_defaults(morph)
return morph
+ def _check_version_file(self,definitions_repo,
+ definitions_absref): # pragma: no cover
+ version_file = self._get_file_contents(
+ definitions_repo, definitions_absref, 'VERSION')
+
+ if version_file is None:
+ return
+
+ try:
+ version = yaml.safe_load(version_file)['version']
+ except (yaml.error.YAMLError, KeyError, TypeError):
+ version = 0
+
+ if version in not_supported_versions:
+ raise UnknownVersionError(version)
+
def _process_definitions_with_children(self, system_filenames,
definitions_repo,
definitions_ref,
@@ -342,6 +370,8 @@ class SourceResolver(object):
definitions_queue = collections.deque(system_filenames)
chunk_queue = set()
+ self._check_version_file(definitions_repo, definitions_absref)
+
while definitions_queue:
filename = definitions_queue.popleft()