summaryrefslogtreecommitdiff
path: root/morphlib/exts/kvm.check
blob: b8877a8997cfbf9cd08bdd8ae09b965184c671d7 (plain)
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
#!/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, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

'''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)

    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_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()