3 # vmtools.py - part of the FDroid server tools
4 # Copyright (C) 2017 Michael Poehn <michael.poehn@fsfe.org>
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.
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.
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/>.
19 from os.path import isdir, isfile, join as joinpath, basename, abspath
24 from .common import FDroidException
25 from logging import getLogger
27 logger = getLogger('fdroidserver-vmtools')
30 def get_build_vm(srvdir, provider=None):
31 """Factory function for getting FDroidBuildVm instances.
33 This function tries to figure out what hypervisor should be used
34 and creates an object for controlling a build VM.
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.
41 abssrvdir = abspath(srvdir)
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)
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)
65 logger.info('build vm provider lookup could not determine provider, defaulting to \'virtualbox\'')
66 return VirtualboxBuildVm(abssrvdir)
69 class FDroidBuildVmException(FDroidException):
73 class FDroidBuildVm():
74 """Abstract base class for working with FDroids build-servers.
76 Use the factory method `fdroidserver.vmtools.get_build_vm()` for
77 getting correct instances of this class.
79 This is intended to be a hypervisor independant, fault tolerant
80 wrapper around the vagrant functions we use.
83 def __init__(self, srvdir):
84 """Create new server class.
87 self.srvname = basename(srvdir) + '_default'
88 self.vgrntfile = joinpath(srvdir, 'Vagrantfile')
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)
95 def isUpAndRunning(self):
96 raise NotImplementedError('TODO implement this')
98 def up(self, provision=True):
100 self.vgrnt.up(provision=provision)
101 except subprocess.CalledProcessError as e:
102 logger.info('could not bring vm up: %s', e)
105 """Remove every trace of this VM from the system.
107 This includes deleting:
108 * hypervisor specific definitions
109 * vagrant state informations (eg. `.vagrant` folder)
110 * images related to this vm
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')
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)
124 subprocess.check_call(['vagrant', 'global-status', '--prune'])
125 except subprocess.CalledProcessError as e:
126 logger.debug('pruning global vagrant status failed: %s', e)
129 class LibvirtBuildVm(FDroidBuildVm):
130 def __init__(self, srvdir):
131 super().__init__(srvdir)
135 self.conn = libvirt.open('qemu:///system')
136 except libvirt.libvirtError as e:
137 logger.critical('could not connect to libvirtd: %s', e)
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)
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...")
151 except subprocess.CalledProcessError as e:
152 logger.info("could not force libvirt domain '%s' off: %s", self.srvname, e)
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...")
160 except subprocess.CalledProcessError as e:
161 logger.info("could not undefine libvirt domain '%s': %s", self.srvname, e)
164 class VirtualboxBuildVm(FDroidBuildVm):