#!/usr/bin/python2
# 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
# the Free Software Foundation; version 2 of the License.
#
# This program 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 this program. If not, see .
'''Preparatory checks for Morph 'kvm' write extension'''
import os
import re
import urlparse
import writeexts
class KvmPlusSshCheckExtension(writeexts.WriteExtension):
location_pattern = '^/(?P[^/]+)(?P/.+)$'
def process_args(self, args):
if len(args) != 1:
raise writeexts.ExtensionError(
'Wrong number of command line args')
self.require_btrfs_in_deployment_host_kernel()
upgrade = self.get_environment_boolean('UPGRADE')
if upgrade:
raise writeexts.ExtensionError(
'Use the `ssh-rsync` write extension to deploy upgrades to an '
'existing remote system.')
location = args[0]
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)
self.check_host_has_virtinstall(ssh_host)
def check_and_parse_location(self, location):
'''Check and parse the location argument to get relevant data.'''
x = urlparse.urlparse(location)
if x.scheme != 'kvm+ssh':
raise writeexts.ExtensionError(
'URL schema must be kvm+ssh in %s' % location)
m = re.match(self.location_pattern, x.path)
if not m:
raise writeexts.ExtensionError(
'Cannot parse location %s' % location)
return x.netloc, m.group('guest'), m.group('path')
def check_no_existing_libvirt_vm(self, ssh_host, vm_name):
try:
writeexts.ssh_runcmd(ssh_host,
['virsh', '--connect', 'qemu:///system', 'domstate', vm_name])
except writeexts.ExtensionError as e:
pass
else:
raise writeexts.ExtensionError(
'Host %s already has a VM named %s. You can use the ssh-rsync '
'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:
writeexts.ssh_runcmd(ssh_host, ['touch', vm_path])
except writeexts.ExtensionError as e:
raise writeexts.ExtensionError(
"Can't write to location %s on %s" % (vm_path, ssh_host))
else:
writeexts.ssh_runcmd(ssh_host, ['rm', vm_path])
try:
writeexts.ssh_runcmd(ssh_host, ['test', '-e', vm_path])
except writeexts.ExtensionError as e:
# vm_path doesn't already exist, so let's test we can write
check_can_write_to_given_path()
else:
raise writeexts.ExtensionError('%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:
writeexts.ssh_runcmd(ssh_host, ['ls', filename])
except writeexts.ExtensionError as e:
raise writeexts.ExtensionError(
'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 = writeexts.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 writeexts.ExtensionError(
"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 writeexts.ExtensionError("Network '%s' is not started"
% network_name)
def name(nic_entry):
if ',' in nic_entry:
# network=NETWORK_NAME,mac=12:34,model=e1000...
return nic_entry[:nic_entry.find(',')].lstrip('network=')
else:
return nic_entry.lstrip('network=') # NETWORK_NAME
if 'NIC_CONFIG' in os.environ:
nics = os.environ['NIC_CONFIG'].split()
for n in nics:
if not (n.startswith('network=')
or n.startswith('bridge=')
or n == 'user'):
raise writeexts.ExtensionError(
"malformed NIC_CONFIG: %s\n"
" (expected 'bridge=BRIDGE' 'network=NAME'"
" or 'user')" % n)
# --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)
def check_host_has_virtinstall(self, ssh_host):
try:
writeexts.ssh_runcmd(ssh_host, ['which', 'virt-install'])
except writeexts.ExtensionError:
raise writeexts.ExtensionError(
'virt-install does not seem to be installed on host %s'
% ssh_host)
KvmPlusSshCheckExtension().run()