1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
|
#!/usr/bin/python
# 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 <http://www.gnu.org/licenses/>.
'''Preparatory checks for Morph 'kvm' write extension'''
import cliapp
import os
import re
import urlparse
import morphlib.writeexts
class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
location_pattern = '^/(?P<guest>[^/]+)(?P<path>/.+)$'
def process_args(self, args):
if len(args) != 1:
raise cliapp.AppException('Wrong number of command line args')
self.require_btrfs_in_deployment_host_kernel()
upgrade = self.get_environment_boolean('UPGRADE')
if upgrade:
raise cliapp.AppException(
'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 cliapp.AppException(
'URL schema must be kvm+ssh in %s' % location)
m = re.match(self.location_pattern, x.path)
if not m:
raise cliapp.AppException('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:
cliapp.ssh_runcmd(ssh_host,
['virsh', '--connect', 'qemu:///system', 'domstate', vm_name])
except cliapp.AppException as e:
pass
else:
raise cliapp.AppException(
'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:
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:
cliapp.ssh_runcmd(ssh_host, ['ls', filename])
except cliapp.AppException as e:
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=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 cliapp.AppException('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:
cliapp.ssh_runcmd(ssh_host, ['which', 'virt-install'])
except cliapp.AppException:
raise cliapp.AppException(
'virt-install does not seem to be installed on host %s'
% ssh_host)
KvmPlusSshCheckExtension().run()
|