chiark / gitweb /
vmtools: use standard imports: os.remove()
[fdroidserver.git] / fdroidserver / vmtools.py
index 97e1c52ce6b20a9d8558fb1393f3d1d54307ae04..568da58a7a7897ec2667cf6c7a242fbadf4a2eff 100644 (file)
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from os import remove as rmfile
-from os.path import isdir, isfile, join as joinpath, basename, abspath, expanduser
+from os.path import isdir, isfile, 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 _
+import threading
+
+lock = threading.Lock()
+
 logger = getLogger('fdroidserver-vmtools')
 
 
@@ -53,7 +56,7 @@ def get_clean_builder(serverdir, reset=False):
     if reset:
         logger.info('resetting buildserver by request')
     elif not vm.vagrant_uuid_okay():
-        logger.info('resetting buildserver, bceause vagrant vm is not okay.')
+        logger.info('resetting buildserver, because vagrant vm is not okay.')
         reset = True
     elif not vm.snapshot_exists('fdroidclean'):
         logger.info("resetting buildserver, because snapshot 'fdroidclean' is not present.")
@@ -84,14 +87,14 @@ def get_clean_builder(serverdir, reset=False):
     return sshinfo
 
 
-def _check_call(cmd, shell=False, cwd=None):
+def _check_call(cmd, cwd=None):
     logger.debug(' '.join(cmd))
-    return subprocess.check_call(cmd, shell=shell, cwd=cwd)
+    return subprocess.check_call(cmd, shell=False, cwd=cwd)
 
 
-def _check_output(cmd, shell=False, cwd=None):
+def _check_output(cmd, cwd=None):
     logger.debug(' '.join(cmd))
-    return subprocess.check_output(cmd, shell=shell, cwd=cwd)
+    return subprocess.check_output(cmd, shell=False, cwd=cwd)
 
 
 def get_build_vm(srvdir, provider=None):
@@ -143,10 +146,10 @@ def get_build_vm(srvdir, provider=None):
         logger.debug('could not confirm that either virtualbox or kvm/libvirt are installed')
 
     # try guessing provider from .../srvdir/.vagrant internals
-    has_libvirt_machine = isdir(joinpath(abssrvdir, '.vagrant',
-                                         'machines', 'default', 'libvirt'))
-    has_vbox_machine = isdir(joinpath(abssrvdir, '.vagrant',
-                                      'machines', 'default', 'libvirt'))
+    has_libvirt_machine = isdir(os.path.join(abssrvdir, '.vagrant',
+                                             'machines', 'default', 'libvirt'))
+    has_vbox_machine = isdir(os.path.join(abssrvdir, '.vagrant',
+                                          '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)
@@ -174,13 +177,12 @@ class FDroidBuildVm():
     This is intended to be a hypervisor independant, fault tolerant
     wrapper around the vagrant functions we use.
     """
-
     def __init__(self, srvdir):
         """Create new server class.
         """
         self.srvdir = srvdir
         self.srvname = basename(srvdir) + '_default'
-        self.vgrntfile = joinpath(srvdir, 'Vagrantfile')
+        self.vgrntfile = os.path.join(srvdir, 'Vagrantfile')
         self.srvuuid = self._vagrant_fetch_uuid()
         if not isdir(srvdir):
             raise FDroidBuildVmException("Can not init vagrant, directory %s not present" % (srvdir))
@@ -190,25 +192,27 @@ class FDroidBuildVm():
         self.vgrnt = vagrant.Vagrant(root=srvdir, out_cm=vagrant.stdout_cm, err_cm=vagrant.stdout_cm)
 
     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
+        global lock
+        with lock:
+            try:
+                self.vgrnt.up(provision=provision)
+                self.srvuuid = self._vagrant_fetch_uuid()
+            except subprocess.CalledProcessError as e:
+                raise FDroidBuildVmException("could not bring up vm '%s'" % self.srvname) from e
 
     def suspend(self):
-        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
+        global lock
+        with lock:
+            logger.info('suspending buildserver')
+            try:
+                self.vgrnt.suspend()
+            except subprocess.CalledProcessError as e:
+                raise FDroidBuildVmException("could not suspend vm '%s'" % self.srvname) from e
 
     def halt(self):
-        self.vgrnt.halt(force=True)
+        global lock
+        with lock:
+            self.vgrnt.halt(force=True)
 
     def destroy(self):
         """Remove every trace of this VM from the system.
@@ -224,7 +228,7 @@ class FDroidBuildVm():
             logger.debug('vagrant destroy completed')
         except subprocess.CalledProcessError as e:
             logger.exception('vagrant destroy failed: %s', e)
-        vgrntdir = joinpath(self.srvdir, '.vagrant')
+        vgrntdir = os.path.join(self.srvdir, '.vagrant')
         try:
             shutil.rmtree(vgrntdir)
             logger.debug('deleted vagrant dir: %s', vgrntdir)
@@ -248,17 +252,17 @@ class FDroidBuildVm():
         return name.replace('/', '-VAGRANTSLASH-')
 
     def _vagrant_fetch_uuid(self):
-        if isfile(joinpath(self.srvdir, '.vagrant')):
+        if isfile(os.path.join(self.srvdir, '.vagrant')):
             # Vagrant 1.0 - it's a json file...
-            with open(joinpath(self.srvdir, '.vagrant')) as f:
+            with open(os.path.join(self.srvdir, '.vagrant')) as f:
                 id = json.load(f)['active']['default']
                 logger.debug('vm uuid: %s', id)
             return id
-        elif isfile(joinpath(self.srvdir, '.vagrant', 'machines',
-                             'default', self.provider, 'id')):
+        elif isfile(os.path.join(self.srvdir, '.vagrant', 'machines',
+                                 'default', self.provider, 'id')):
             # Vagrant 1.2 (and maybe 1.1?) it's a directory tree...
-            with open(joinpath(self.srvdir, '.vagrant', 'machines',
-                               'default', self.provider, 'id')) as f:
+            with open(os.path.join(self.srvdir, '.vagrant', 'machines',
+                                   'default', self.provider, 'id')) as f:
                 id = f.read()
                 logger.debug('vm uuid: %s', id)
             return id
@@ -283,8 +287,8 @@ class FDroidBuildVm():
             _check_call(['vagrant', 'box', 'remove', '--all', '--force', boxname])
         except subprocess.CalledProcessError as e:
             logger.debug('tried removing box %s, but is did not exist: %s', boxname, e)
-        boxpath = joinpath(expanduser('~'), '.vagrant',
-                           self._vagrant_file_name(boxname))
+        boxpath = os.path.join(expanduser('~'), '.vagrant',
+                               self._vagrant_file_name(boxname))
         if isdir(boxpath):
             logger.info("attempting to remove box '%s' by deleting: %s",
                         boxname, boxpath)
@@ -298,11 +302,13 @@ class FDroidBuildVm():
         """
         import paramiko
         try:
-            _check_call(['vagrant ssh-config > sshconfig'],
-                        cwd=self.srvdir, shell=True)
+            sshconfig_path = os.path.join(self.srvdir, 'sshconfig')
+            with open(sshconfig_path, 'wb') as fp:
+                fp.write(_check_output(['vagrant', 'ssh-config'],
+                                       cwd=self.srvdir))
             vagranthost = 'default'  # Host in ssh config file
             sshconfig = paramiko.SSHConfig()
-            with open(joinpath(self.srvdir, 'sshconfig'), 'r') as f:
+            with open(sshconfig_path, 'r') as f:
                 sshconfig.parse(f)
             sshconfig = sshconfig.lookup(vagranthost)
             idfile = sshconfig['identityfile']
@@ -350,16 +356,12 @@ 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)
 
@@ -373,17 +375,19 @@ class LibvirtBuildVm(FDroidBuildVm):
         if storagePool:
 
             if isfile('metadata.json'):
-                rmfile('metadata.json')
+                os.remove('metadata.json')
             if isfile('Vagrantfile'):
-                rmfile('Vagrantfile')
+                os.remove('Vagrantfile')
             if isfile('box.img'):
-                rmfile('box.img')
+                os.remove('box.img')
 
             logger.debug('preparing box.img for box %s', output)
             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'])
@@ -425,9 +429,9 @@ class LibvirtBuildVm(FDroidBuildVm):
 
             if not keep_box_file:
                 logger.debug('box packaging complete, removing temporary files.')
-                rmfile('metadata.json')
-                rmfile('Vagrantfile')
-                rmfile('box.img')
+                os.remove('metadata.json')
+                os.remove('Vagrantfile')
+                os.remove('box.img')
 
         else:
             logger.warn('could not connect to storage-pool \'default\',' +
@@ -454,8 +458,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'"
@@ -484,8 +486,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
@@ -501,8 +501,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'