chiark / gitweb /
Merge branch 'no_sleep' into 'master'
[fdroidserver.git] / fdroidserver / vmtools.py
index a47b4c9ee69316552c2d2f62916c09f372250bc6..2142beb7db0d532bedc24e8ce7fb33b8e17fadeb 100644 (file)
 
 from os import remove as rmfile
 from os.path import isdir, isfile, join as joinpath, basename, abspath, expanduser
+import os
 import math
 import json
 import tarfile
-import time
 import shutil
 import subprocess
+import textwrap
 from .common import FDroidException
 from logging import getLogger
 
+from fdroidserver import _
+
 logger = getLogger('fdroidserver-vmtools')
 
 
+def get_clean_builder(serverdir, reset=False):
+    if not os.path.isdir(serverdir):
+        if os.path.islink(serverdir):
+            os.unlink(serverdir)
+        logger.info("buildserver path does not exists, creating %s", serverdir)
+        os.makedirs(serverdir)
+    vagrantfile = os.path.join(serverdir, 'Vagrantfile')
+    if not os.path.isfile(vagrantfile):
+        with open(os.path.join('builder', 'Vagrantfile'), 'w') as f:
+            f.write(textwrap.dedent("""\
+                # generated file, do not change.
+
+                Vagrant.configure("2") do |config|
+                    config.vm.box = "buildserver"
+                    config.vm.synced_folder ".", "/vagrant", disabled: true
+                end
+                """))
+    vm = get_build_vm(serverdir)
+    if reset:
+        logger.info('resetting buildserver by request')
+    elif not vm.vagrant_uuid_okay():
+        logger.info('resetting buildserver, bceause vagrant vm is not okay.')
+        reset = True
+    elif not vm.snapshot_exists('fdroidclean'):
+        logger.info("resetting buildserver, because snapshot 'fdroidclean' is not present.")
+        reset = True
+
+    if reset:
+        vm.destroy()
+    vm.up()
+    vm.suspend()
+
+    if reset:
+        logger.info('buildserver recreated: taking a clean snapshot')
+        vm.snapshot_create('fdroidclean')
+    else:
+        logger.info('builserver ok: reverting to clean snapshot')
+        vm.snapshot_revert('fdroidclean')
+    vm.up()
+
+    try:
+        sshinfo = vm.sshinfo()
+    except FDroidBuildVmException:
+        # workaround because libvirt sometimes likes to forget
+        # about ssh connection info even thou the vm is running
+        vm.halt()
+        vm.up()
+        sshinfo = vm.sshinfo()
+
+    return sshinfo
+
+
 def _check_call(cmd, shell=False, cwd=None):
     logger.debug(' '.join(cmd))
     return subprocess.check_call(cmd, shell=shell, cwd=cwd)
@@ -92,7 +147,7 @@ def get_build_vm(srvdir, provider=None):
     has_libvirt_machine = isdir(joinpath(abssrvdir, '.vagrant',
                                          'machines', 'default', 'libvirt'))
     has_vbox_machine = isdir(joinpath(abssrvdir, '.vagrant',
-                                      'machines', 'default', 'libvirt'))
+                                      'machines', 'default', 'virtualbox'))
     if has_libvirt_machine and has_vbox_machine:
         logger.info('build vm provider lookup found virtualbox and libvirt, defaulting to \'virtualbox\'')
         return VirtualboxBuildVm(abssrvdir)
@@ -138,8 +193,6 @@ class FDroidBuildVm():
     def up(self, provision=True):
         try:
             self.vgrnt.up(provision=provision)
-            logger.info('...waiting a sec...')
-            time.sleep(10)
             self.srvuuid = self._vagrant_fetch_uuid()
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException("could not bring up vm '%s'" % self.srvname) from e
@@ -148,8 +201,6 @@ class FDroidBuildVm():
         logger.info('suspending buildserver')
         try:
             self.vgrnt.suspend()
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException("could not suspend vm '%s'" % self.srvname) from e
 
@@ -181,8 +232,14 @@ class FDroidBuildVm():
         except subprocess.CalledProcessError as e:
             logger.debug('pruning global vagrant status failed: %s', e)
 
-    def package(self, output=None, vagrantfile=None, keep_box_file=None):
-        self.vgrnt.package(output=output, vagrantfile=vagrantfile)
+    def package(self, output=None):
+        self.vgrnt.package(output=output)
+
+    def vagrant_uuid_okay(self):
+        '''Having an uuid means that vagrant up has run successfully.'''
+        if self.srvuuid is None:
+            return False
+        return True
 
     def _vagrant_file_name(self, name):
         return name.replace('/', '-VAGRANTSLASH-')
@@ -230,6 +287,33 @@ class FDroidBuildVm():
                         boxname, boxpath)
             shutil.rmtree(boxpath)
 
+    def sshinfo(self):
+        """Get ssh connection info for a vagrant VM
+
+        :returns: A dictionary containing 'hostname', 'port', 'user'
+            and 'idfile'
+        """
+        import paramiko
+        try:
+            _check_call(['vagrant ssh-config > sshconfig'],
+                        cwd=self.srvdir, shell=True)
+            vagranthost = 'default'  # Host in ssh config file
+            sshconfig = paramiko.SSHConfig()
+            with open(joinpath(self.srvdir, 'sshconfig'), 'r') as f:
+                sshconfig.parse(f)
+            sshconfig = sshconfig.lookup(vagranthost)
+            idfile = sshconfig['identityfile']
+            if isinstance(idfile, list):
+                idfile = idfile[0]
+            elif idfile.startswith('"') and idfile.endswith('"'):
+                idfile = idfile[1:-1]
+            return {'hostname': sshconfig['hostname'],
+                    'port': int(sshconfig['port']),
+                    'user': sshconfig['user'],
+                    'idfile': idfile}
+        except subprocess.CalledProcessError as e:
+            raise FDroidBuildVmException("Error getting ssh config") from e
+
     def snapshot_create(self, snapshot_name):
         raise NotImplementedError('not implemented, please use a sub-type instance')
 
@@ -263,25 +347,22 @@ class LibvirtBuildVm(FDroidBuildVm):
         # (eg. lookupByName only works on running VMs)
         try:
             _check_call(('virsh', '-c', 'qemu:///system', 'destroy', self.srvname))
-            logger.info("...waiting a sec...")
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             logger.info("could not force libvirt domain '%s' off: %s", self.srvname, e)
         try:
             # libvirt python bindings do not support all flags required
             # for undefining domains correctly.
             _check_call(('virsh', '-c', 'qemu:///system', 'undefine', self.srvname, '--nvram', '--managed-save', '--remove-all-storage', '--snapshots-metadata'))
-            logger.info("...waiting a sec...")
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             logger.info("could not undefine libvirt domain '%s': %s", self.srvname, e)
 
-    def package(self, output=None, vagrantfile=None, keep_box_file=False):
+    def package(self, output=None, keep_box_file=False):
         if not output:
             output = "buildserver.box"
             logger.debug('no output name set for packaging \'%s\',' +
                          'defaulting to %s', self.srvname, output)
         storagePool = self.conn.storagePoolLookupByName('default')
+        domainInfo = self.conn.lookupByName(self.srvname).info()
         if storagePool:
 
             if isfile('metadata.json'):
@@ -295,7 +376,9 @@ class LibvirtBuildVm(FDroidBuildVm):
             vol = storagePool.storageVolLookupByName(self.srvname + '.img')
             imagepath = vol.path()
             # TODO use a libvirt storage pool to ensure the img file is readable
-            _check_call(['sudo', '/bin/chmod', '-R', 'a+rX', '/var/lib/libvirt/images'])
+            if not os.access(imagepath, os.R_OK):
+                logger.warning(_('Cannot read "{path}"!').format(path=imagepath))
+                _check_call(['sudo', '/bin/chmod', '-R', 'a+rX', '/var/lib/libvirt/images'])
             shutil.copy2(imagepath, 'box.img')
             _check_call(['qemu-img', 'rebase', '-p', '-b', '', 'box.img'])
             img_info_raw = _check_output(['qemu-img', 'info', '--output=json', 'box.img'])
@@ -305,14 +388,26 @@ class LibvirtBuildVm(FDroidBuildVm):
                         "virtual_size": math.ceil(img_info['virtual-size'] / (1024. ** 3)),
                         }
 
-            if not vagrantfile:
-                logger.debug('no Vagrantfile supplied for box, generating a minimal one...')
-                vagrantfile = 'Vagrant.configure("2") do |config|\nend'
-
             logger.debug('preparing metadata.json for box %s', output)
             with open('metadata.json', 'w') as fp:
                 fp.write(json.dumps(metadata))
             logger.debug('preparing Vagrantfile for box %s', output)
+            vagrantfile = textwrap.dedent("""\
+                  Vagrant.configure("2") do |config|
+                    config.ssh.username = "vagrant"
+                    config.ssh.password = "vagrant"
+
+                    config.vm.provider :libvirt do |libvirt|
+
+                      libvirt.driver = "kvm"
+                      libvirt.host = ""
+                      libvirt.connect_via_ssh = false
+                      libvirt.storage_pool_name = "default"
+                      libvirt.cpus = {cpus}
+                      libvirt.memory = {memory}
+
+                    end
+                  end""".format_map({'memory': str(int(domainInfo[1] / 1024)), 'cpus': str(domainInfo[3])}))
             with open('Vagrantfile', 'w') as fp:
                 fp.write(vagrantfile)
             with tarfile.open(output, 'w:gz') as tar:
@@ -354,8 +449,6 @@ class LibvirtBuildVm(FDroidBuildVm):
         logger.info("creating snapshot '%s' for vm '%s'", snapshot_name, self.srvname)
         try:
             _check_call(['virsh', '-c', 'qemu:///system', 'snapshot-create-as', self.srvname, snapshot_name])
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException("could not cerate snapshot '%s' "
                                          "of libvirt vm '%s'"
@@ -384,8 +477,6 @@ class LibvirtBuildVm(FDroidBuildVm):
             dom = self.conn.lookupByName(self.srvname)
             snap = dom.snapshotLookupByName(snapshot_name)
             dom.revertToSnapshot(snap)
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except libvirt.libvirtError as e:
             raise FDroidBuildVmException('could not revert domain \'%s\' to snapshot \'%s\''
                                          % (self.srvname, snapshot_name)) from e
@@ -401,8 +492,6 @@ class VirtualboxBuildVm(FDroidBuildVm):
         logger.info("creating snapshot '%s' for vm '%s'", snapshot_name, self.srvname)
         try:
             _check_call(['VBoxManage', 'snapshot', self.srvuuid, 'take', 'fdroidclean'], cwd=self.srvdir)
-            logger.info('...waiting a sec...')
-            time.sleep(10)
         except subprocess.CalledProcessError as e:
             raise FDroidBuildVmException('could not cerate snapshot '
                                          'of virtualbox vm %s'