chiark / gitweb /
fix virsh destroy parameters
[fdroidserver.git] / fdroidserver / vmtools.py
1 #!/usr/bin/env python3
2 #
3 # vmtools.py - part of the FDroid server tools
4 # Copyright (C) 2017 Michael Poehn <michael.poehn@fsfe.org>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU Affero General Public License for more details.
15 #
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19 from os.path import isdir, isfile, join as joinpath, basename, abspath
20 import time
21 import shutil
22 import vagrant
23 import subprocess
24 from .common import FDroidException
25 from logging import getLogger
26
27 logger = getLogger('fdroidserver-vmtools')
28
29
30 def get_build_vm(srvdir, provider=None):
31     """Factory function for getting FDroidBuildVm instances.
32
33     This function tries to figure out what hypervisor should be used
34     and creates an object for controlling a build VM.
35
36     :param srvdir: path to a directory which contains a Vagrantfile
37     :param provider: optionally this parameter allows specifiying an
38         spesific vagrant provider.
39     :returns: FDroidBuildVm instance.
40     """
41     abssrvdir = abspath(srvdir)
42     if provider:
43         if provider == 'libvirt':
44             logger.debug('build vm provider \'libvirt\' selected')
45             return LibvirtBuildVm(abssrvdir)
46         elif provider == 'virtualbox':
47             logger.debug('build vm provider \'virtualbox\' selected')
48             return VirtualboxBuildVm(abssrvdir)
49         else:
50             logger.warn('build vm provider not supported: \'%s\'', provider)
51     has_libvirt_machine = isdir(joinpath(abssrvdir, '.vagrant',
52                                          'machines', 'default', 'libvirt'))
53     has_vbox_machine = isdir(joinpath(abssrvdir, '.vagrant',
54                                       'machines', 'default', 'libvirt'))
55     if has_libvirt_machine and has_vbox_machine:
56         logger.info('build vm provider lookup found virtualbox and libvirt, defaulting to \'virtualbox\'')
57         return VirtualboxBuildVm(abssrvdir)
58     elif has_libvirt_machine:
59         logger.debug('build vm provider lookup found \'libvirt\'')
60         return LibvirtBuildVm(abssrvdir)
61     elif has_vbox_machine:
62         logger.debug('build vm provider lookup found \'virtualbox\'')
63         return VirtualboxBuildVm(abssrvdir)
64
65     logger.info('build vm provider lookup could not determine provider, defaulting to \'virtualbox\'')
66     return VirtualboxBuildVm(abssrvdir)
67
68
69 class FDroidBuildVmException(FDroidException):
70     pass
71
72
73 class FDroidBuildVm():
74     """Abstract base class for working with FDroids build-servers.
75
76     Use the factory method `fdroidserver.vmtools.get_build_vm()` for
77     getting correct instances of this class.
78
79     This is intended to be a hypervisor independant, fault tolerant
80     wrapper around the vagrant functions we use.
81     """
82
83     def __init__(self, srvdir):
84         """Create new server class.
85         """
86         self.srvdir = srvdir
87         self.srvname = basename(srvdir) + '_default'
88         self.vgrntfile = joinpath(srvdir, 'Vagrantfile')
89         if not isdir(srvdir):
90             raise FDroidBuildVmException("Can not init vagrant, directory %s not present" % (srvdir))
91         if not isfile(self.vgrntfile):
92             raise FDroidBuildVmException("Can not init vagrant, '%s' not present" % (self.vgrntfile))
93         self.vgrnt = vagrant.Vagrant(root=srvdir, out_cm=vagrant.stdout_cm, err_cm=vagrant.stdout_cm)
94
95     def isUpAndRunning(self):
96         raise NotImplementedError('TODO implement this')
97
98     def up(self, provision=True):
99         try:
100             self.vgrnt.up(provision=provision)
101         except subprocess.CalledProcessError as e:
102             logger.info('could not bring vm up: %s', e)
103
104     def destroy(self):
105         """Remove every trace of this VM from the system.
106
107         This includes deleting:
108         * hypervisor specific definitions
109         * vagrant state informations (eg. `.vagrant` folder)
110         * images related to this vm
111         """
112         try:
113             self.vgrnt.destroy()
114             logger.debug('vagrant destroy completed')
115         except subprocess.CalledProcessError as e:
116             logger.debug('vagrant destroy failed: %s', e)
117         vgrntdir = joinpath(self.srvdir, '.vagrant')
118         try:
119             shutil.rmtree(vgrntdir)
120             logger.debug('deleted vagrant dir: %s', vgrntdir)
121         except Exception as e:
122             logger.debug("could not delete vagrant dir: %s, %s", vgrntdir, e)
123         try:
124             subprocess.check_call(['vagrant', 'global-status', '--prune'])
125         except subprocess.CalledProcessError as e:
126             logger.debug('pruning global vagrant status failed: %s', e)
127
128
129 class LibvirtBuildVm(FDroidBuildVm):
130     def __init__(self, srvdir):
131         super().__init__(srvdir)
132         import libvirt
133
134         try:
135             self.conn = libvirt.open('qemu:///system')
136         except libvirt.libvirtError as e:
137             logger.critical('could not connect to libvirtd: %s', e)
138
139     def destroy(self):
140
141         super().destroy()
142
143         # resorting to virsh instead of libvirt python bindings, because
144         # this is way more easy and therefore fault tolerant.
145         # (eg. lookupByName only works on running VMs)
146         try:
147             logger.debug('virsh -c qemu:///system destroy %s', self.srvname)
148             subprocess.check_call(('virsh', '-c', 'qemu:///system', 'destroy', self.srvname))
149             logger.info("...waiting a sec...")
150             time.sleep(10)
151         except subprocess.CalledProcessError as e:
152             logger.info("could not force libvirt domain '%s' off: %s", self.srvname, e)
153         try:
154             # libvirt python bindings do not support all flags required
155             # for undefining domains correctly.
156             logger.debug('virsh -c qemu:///system undefine %s --nvram --managed-save --remove-all-storage --snapshots-metadata', self.srvname)
157             subprocess.check_call(('virsh', '-c', 'qemu:///system', 'undefine', self.srvname, '--nvram', '--managed-save', '--remove-all-storage', '--snapshots-metadata'))
158             logger.info("...waiting a sec...")
159             time.sleep(10)
160         except subprocess.CalledProcessError as e:
161             logger.info("could not undefine libvirt domain '%s': %s", self.srvname, e)
162
163
164 class VirtualboxBuildVm(FDroidBuildVm):
165     pass