cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync# -*- coding: utf-8 -*-
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncTestBox Script - main().
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncCopyright (C) 2012-2014 Oracle Corporation
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncThis file is part of VirtualBox Open Source Edition (OSE), as
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncavailable from http://www.virtualbox.org. This file is free software;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncyou can redistribute it and/or modify it under the terms of the GNU
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncGeneral Public License (GPL) as published by the Free Software
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncFoundation, in version 2 as it comes in the "COPYING" file of the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncVirtualBox OSE distribution. VirtualBox OSE is distributed in the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsynchope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncThe contents of this file may alternatively be used under the terms
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncof the Common Development and Distribution License Version 1.0
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync(CDDL) only, as it comes in the "COPYING.CDDL" file of the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncVirtualBox OSE distribution, in which case the provisions of the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncCDDL are applicable instead of those of the GPL.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncYou may elect to license modified versions of this file under the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncterms and conditions of either the GPL or the CDDL or both.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync# Standard python imports.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync# Only the main script needs to modify the path.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncg_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncg_ksValidationKitDir = os.path.dirname(g_ksTestScriptDir);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncsys.path.extend([g_ksTestScriptDir, g_ksValidationKitDir]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync# Validation Kit imports.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsyncfrom testboxscript import TBS_EXITCODE_SYNTAX, TBS_EXITCODE_FAILURE;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync """ For raising exceptions during TestBoxScript.__init__. """
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Implementation of the test box script.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Communicate with test manager and perform offered actions.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ## @name Class Constants.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Scratch space round value (MB).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Memory size round value (MB).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # A NULL UUID in string form.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ksNullUuid = '00000000-0000-0000-0000-000000000000';
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # The minimum dispatch loop delay.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # The maximum dispatch loop delay (inclusive).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # The minimum sign-on delay.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # The maximum sign-on delay (inclusive).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Keys for config params
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Initialize internals
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Signed-on state
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sTestBoxUuid = self.ksNullUuid; # convenience, assigned below.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Command processor.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Scratch dir setup. Use /var/tmp instead of /tmp because we may need
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # many many GBs for some test scenarios and /tmp can be backed by swap
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # or be a fast+small disk of some kind, while /var/tmp is normally
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # larger, if slower. /var/tmp is generally not cleaned up on reboot,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # /tmp often is, this would break host panic / triple-fault detection.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if utils.getHostOs() in ('win', 'os2', 'haiku', 'dos'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # We need *lots* of space, so avoid /tmp as it may be a memory
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # file system backed by the swap file, or worse.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.sScratchRoot = tempfile.gettempdir();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sSubDir = '%s-%u' % (sSubDir, os.getuid()); # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.sScratchRoot = os.path.join(self._oOptions.sScratchRoot, sSubDir);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sScratchSpill = os.path.join(self._oOptions.sScratchRoot, 'scratch');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sScratchScripts = os.path.join(self._oOptions.sScratchRoot, 'scripts');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sScratchState = os.path.join(self._oOptions.sScratchRoot, 'state'); # persistant storage.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # We count consecutive reinitScratch failures and will reboot the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # testbox after a while in the hope that it will correct the issue.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Mount builds and test resources if requested.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Sign-on parameters: Packed into list of records of format:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # { <Parameter ID>: { <Current value>, <Check function> } }
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.ALL_PARAM_TESTBOX_UUID: { self.VALUE: self._getHostSystemUuid(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_OS: { self.VALUE: utils.getHostOs(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_OS_VERSION: { self.VALUE: utils.getHostOsVersion(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_CPU_ARCH: { self.VALUE: utils.getHostArch(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_CPU_VENDOR: { self.VALUE: self._getHostCpuVendor(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_CPU_NAME: { self.VALUE: self._getHostCpuName(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_CPU_REVISION: { self.VALUE: self._getHostCpuRevision(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT: { self.VALUE: self._hasHostHwVirt(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING:{ self.VALUE: self._hasHostNestedPaging(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_HAS_64_BIT_GUEST: { self.VALUE: self._can64BitGuest(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_HAS_IOMMU: { self.VALUE: self._hasHostIoMmu(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_SCRIPT_REV: { self.VALUE: self._getScriptRev(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_REPORT: { self.VALUE: self._getHostReport(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_PYTHON_VERSION: { self.VALUE: self._getPythonHexVersion(), self.FN: None },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_CPU_COUNT: { self.VALUE: None, self.FN: multiprocessing.cpu_count },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_MEM_SIZE: { self.VALUE: None, self.FN: self._getHostMemSize },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE: { self.VALUE: None, self.FN: self._getFreeScratchSpace },
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if self._ddSignOnParams[sItem][self.FN] is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._ddSignOnParams[sItem][self.VALUE] = self._ddSignOnParams[sItem][self.FN]()
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Starting Test Box script (%s)' % __version__)
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Test Manager URL: %s' % self._oOptions.sTestManagerUrl,)
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Scratch root path: %s' % self._oOptions.sScratchRoot,)
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Sign-On value %18s: %s' % (sItem, self._ddSignOnParams[sItem][self.VALUE]));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # The System UUID is the primary identification of the machine, so
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # refuse to cooperate if it's NULL.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sTestBoxUuid = self.getSignOnParam(constants.tbreq.ALL_PARAM_TESTBOX_UUID);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Couldn\'t determine the System UUID, please use --system-uuid to specify it.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Export environment variables, clearing any we don't know yet.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else: # Starts with '=', bad user.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Invalid -E argument: "%s"' % (sEnvVar,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_PATH_BUILDS'] = self._oOptions.sBuildsPath;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_PATH_RESOURCES'] = self._oOptions.sTestRsrcPath;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_PATH_SCRATCH'] = self._sScratchSpill;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_PATH_SCRIPTS'] = self._sScratchScripts;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_PATH_UPLOAD'] = self._sScratchSpill; ## @todo drop the UPLOAD dir?
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_HAS_HW_VIRT'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_HW_VIRT);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_HAS_NESTED_PAGING'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_NESTED_PAGING);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_HAS_IOMMU'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_HAS_IOMMU);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_SCRIPT_REV'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRIPT_REV);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_CPU_COUNT'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_CPU_COUNT);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_MEM_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_MEM_SIZE);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_SCRATCH_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_MANAGER_URL'] = self._oOptions.sTestManagerUrl;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['COMSPEC'] = os.path.join(os.environ['SystemRoot'], 'System32', 'cmd.exe');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Currently omitting any kBuild tools.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Mounts the shares.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Raises exception on failure.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._mountShare(self._oOptions.sBuildsPath, self._oOptions.sBuildsServerType, self._oOptions.sBuildsServerName,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.sBuildsServerUser, self._oOptions.sBuildsServerPasswd, 'builds');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._mountShare(self._oOptions.sTestRsrcPath, self._oOptions.sTestRsrcServerType, self._oOptions.sTestRsrcServerName,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.sTestRsrcServerUser, self._oOptions.sTestRsrcServerPasswd, 'testrsrc');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def _mountShare(self, sMountPoint, sType, sServer, sShare, sUser, sPassword, sWhat):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Mounts the specified share if needed.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Raises exception on failure.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Only mount if the type is specified.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Test if already mounted.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sTestFile = os.path.join(sMountPoint + os.path.sep, sShare + '.txt');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Platform specific mount code.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/sbin/umount', sMountPoint]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/usr/sbin/chown', str(os.getuid()), sMountPoint]); # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Note! no smb://server/share stuff here, 10.6.8 didn't like it.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.processOutputChecked(['/sbin/mount_smbfs', '-o', 'automounted,nostreams,soft,noowners,noatime,rdonly',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync '//%s:%s@%s/%s' % (sUser, sPassword, sServer, sShare),
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Unsupported server type %s.' % (sType,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/bin/umount', sMountPoint]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'cifs',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',sec=ntlmv2'
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',uid=' + str(os.getuid()) # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',gid=' + str(os.getgid()) # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',nounix,file_mode=0555,dir_mode=0555,soft,ro',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessOutputChecked(['/bin/mount', '-t', 'nfs',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)),
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Unsupported server type %s.' % (sType,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/sbin/umount', sMountPoint]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessCall(['/bin/mkdir', '-p', sMountPoint]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ## @todo This stuff doesn't work on wei01-x4600b.de.oracle.com running 11.1. FIXME!
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'smbfs',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',uid=' + str(os.getuid()) # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',gid=' + str(os.getgid()) # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + ',fileperms=0555,dirperms=0555,noxattr,ro',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.sudoProcessOutputChecked(['/sbin/mount', '-F', 'nfs',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync '%s:%s' % (sServer, sShare if sShare.find('/') >= 0 else ('/export/' + sShare)),
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Unsupported server type %s.' % (sType,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Only CIFS mounts are supported on Windows.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.processCall(['net', 'use', sMountPoint, '/d']);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.processOutputChecked(['net', 'use', sMountPoint,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxScriptException('Unsupported host %s' % (sHostOs,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxException('Failed to mount %s (%s[%s]) at %s: %s not found'
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync % (sWhat, sServer, sShare, sMountPoint, sTestFile));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ## @name Signon property releated methods.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Invokes TestBoxHelper to obtain information hard to access from python.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # See VBoxTestBoxScript.zip for layout.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sTestBoxHelper = os.path.join(g_ksValidationKitDir, utils.getHostOs(), utils.getHostArch(), \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync 'TestBoxHelper');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync else: # Only for in-tree testing, so don't bother be too accurate right now.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sType = os.environ.get('KBUILD_TYPE', os.environ.get('BUILD_TYPE', 'debug'));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._sTestBoxHelper = os.path.join(g_ksValidationKitDir, os.pardir, os.pardir, os.pardir, 'out', \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync utils.getHostOsDotArch(), sType, 'testboxscript', \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync 'TestBoxHelper');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return utils.processOutputChecked([self._sTestBoxHelper, sCmd]).strip();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def _getHelperOutputTristate(self, sCmd, fDunnoValue):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Invokes TestBoxHelper to obtain information hard to access from python.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxException('Unexpected response "%s" to helper command "%s"' % (sValue, sCmd));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Checks if the UUID looks good.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync There are systems with really bad UUIDs, for instance
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync "03000200-0400-0500-0006-000700080009".
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for sDigit in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the system UUID string from the System, return null-uuid if
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync unable to get retrieve it.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Try get at the firmware UUID.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # NOTE: This requires to have kernel option enabled:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Firmware Drivers -> Export DMI identification via sysfs to userspace
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if os.path.exists('/sys/devices/virtual/dmi/id/product_uuid'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sVar = utils.sudoProcessOutputChecked(['cat', '/sys/devices/virtual/dmi/id/product_uuid']);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ## @todo consider dmidecoder? What about EFI systems?
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Windows: WMI
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync oWmi = win32com.client.Dispatch('WbemScripting.SWbemLocator');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for oItem in oWebm.ExecQuery('SELECT * FROM Win32_ComputerSystemProduct'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sVar = utils.processOutputChecked(['/bin/sh', '-c',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync '/usr/sbin/ioreg -k IOPlatformUUID' \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + '| /usr/bin/grep IOPlatformUUID' \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + '| /usr/bin/head -1']);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sVar = sVar.strip()[-(len(self.ksNullUuid) + 1):-1];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Solaris: The smbios util.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sVar = utils.processOutputChecked(['/bin/sh', '-c',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync + '| /usr/bin/head -1']);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Try add the MAC address.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # uuid.getnode may provide it, or it may return a random number...
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if lMacAddr == uuid.getnode() and lMacAddr != 0 and len(sNode) == 12:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the CPUID vendor string on intel HW.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the CPU name/description string.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the CPU revision (family/model/stepping) value.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Check if the host supports AMD-V or VT-x
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.fHasHwVirt = self._getHelperOutput('cpuhwvirt');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Check if the host supports nested paging.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.fHasNestedPaging = self._getHelperOutputTristate('nestedpaging', False);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Check if the we (VBox) can run 64-bit guests.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oOptions.fCan64BitGuest = self._getHelperOutputTristate('longmode', True);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Check if the host has an I/O MMU of the VT-d kind.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ## @todo Any way to figure this one out on any host OS?
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Generate a report about the host hardware and software.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Gets the amount of physical memory on the host (and accessible to the
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync OS, i.e. don't report stuff over 4GB if Windows doesn't wanna use it).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync cMbMemory = long(self._getHelperOutput('memsize').strip()) / (1024 * 1024);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Round it.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync cMbMemory = long(math.floor(cMbMemory / self.kcMbMemoryRounding)) * self.kcMbMemoryRounding;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get free space on the volume where scratch directory is located and
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return it in bytes rounded down to nearest 64MB
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync (currently works on Linux only)
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(self._oOptions.sScratchRoot), None, None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync stats = os.statvfs(self._oOptions.sScratchRoot); # pylint: disable=E1101
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Convert to MB
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Round free space size
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync cMbFreeSpace = long(math.floor(cMbFreeSpace / self.kcMbScratchSpaceRounding)) * self.kcMbScratchSpaceRounding;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync The script (subversion) revision number.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync The python hex version number.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync uHexVersion = (sys.version_info[0] << 24) | (sys.version_info[1] << 16) | (sys.version_info[2] << 8);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Opens up a connection to the test manager.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Raises exception on failure.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return TestBoxConnection(self._oOptions.sTestManagerUrl, self._idTestBox, self._sTestBoxUuid);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Returns a sign-on parameter value as string.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Raises exception if the name is incorrect.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return str(self._ddSignOnParams[sName][self.VALUE]);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the path to the state dir in the scratch area.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the path to the scripts dir (TESTBOX_PATH_SCRIPTS) in the scratch area.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the path to the spill dir (TESTBOX_PATH_SCRATCH) in the scratch area.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the path to the builds.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the TestBox ID for state saving purposes.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Get the TestBox name for state saving purposes.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def reinitScratch(self, fnLog = testboxcommons.log, fUseTheForce = None):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Wipes the scratch directories and re-initializes them.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync No exceptions raise, returns success indicator instead.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync class ErrorCallback(object): # pylint: disable=R0903
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Callbacks + state for the cleanup.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync def onErrorCallback(self, sFnName, sPath, aXcptInfo):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync """ Logs error during shutil.rmtree operation. """
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync fnLog('Error removing "%s": fn=%s %s' % (sPath, sFnName, aXcptInfo[1]));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for sName in os.listdir(self._oOptions.sScratchRoot):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sFullName = os.path.join(self._oOptions.sScratchRoot, sName);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync shutil.rmtree(sFullName, False, oRc.onErrorCallback);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise Exception('Still exists after deletion, weird.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync fnLog('Problems deleting "%s" (%s) using the force...' % (sFullName, oXcpt));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync iRc = utils.sudoProcessCall(['/bin/rm', '-Rf', sFullName])
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync iRc = utils.sudoProcessCall(['/bin/rm', '-f', sFullName])
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise Exception('Still exists after forced deletion, weird^2.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync fnLog('Error sudo deleting "%s": %s' % (sFullName, oXcpt));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync fnLog('Error deleting "%s": %s' % (sFullName, oXcpt));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Re-create the directories.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for sDir in [self._oOptions.sScratchRoot, self._sScratchSpill, self._sScratchScripts, self._sScratchState]:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Worker for _maybeSignOn that does the actual signing on.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Reset the siged-on state.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Assemble SIGN-ON request parameters and send the request.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dParams[sParam] = self._ddSignOnParams[sParam][self.VALUE];
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync oResponse = TestBoxConnection.sendSignOn(self._oOptions.sTestManagerUrl, dParams);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Check response.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sResult = oResponse.getStringChecked(constants.tbresp.ALL_PARAM_RESULT);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync raise TestBoxException('Result is %s' % (sResult,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync idTestBox = oResponse.getIntChecked(constants.tbresp.SIGNON_PARAM_ID, 1, 0x7ffffffe);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync sTestBoxName = oResponse.getStringChecked(constants.tbresp.SIGNON_PARAM_NAME);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Failed to sign-on: %s' % (str(err),))
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Server response: %s' % (oResponse.toString(),));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Successfully signed on, update the state.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Update the environment.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_CPU_COUNT'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_CPU_COUNT);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_MEM_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_MEM_SIZE);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync os.environ['TESTBOX_SCRATCH_SIZE'] = self.getSignOnParam(constants.tbreq.SIGNON_PARAM_SCRATCH_SIZE);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Successfully signed-on with Test Box ID #%s and given the name "%s"' \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Set up the scratch area.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self.reinitScratch(fUseTheForce = self._fFirstSignOn);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Check if Test Box parameters were changed
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and do sign-in in case of positive result
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Skip sign-on check if background command is currently in
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # running state (avoid infinite signing on).
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return None;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Refresh sign-on parameters, changes triggers sign-on.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync fNeedSignOn = (True if not self._fSignedOn or self._fNeedReSignOn else False)
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._ddSignOnParams[item][self.VALUE] = self._ddSignOnParams[item][self.FN]()
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if sOldValue != self._ddSignOnParams[item][self.VALUE]:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Detected %s parameter change: %s -> %s' %
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync (item, sOldValue, self._ddSignOnParams[item][self.VALUE]))
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync return None;
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Receive orders from Test Manager and execute them
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync (self._idTestBox, self._sTestBoxName, self._fSignedOn) = self._oCommand.resumeIncompleteCommand();
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Make sure we're signed on before trying to do anything.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync time.sleep(random.randint(self.kcSecMinSignOnDelay * iFactor, self.kcSecMaxSignOnDelay * iFactor));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Retrieve and handle command from the TM.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync (oResponse, oConnection) = TestBoxConnection.requestCommandWithConnection(self._oOptions.sTestManagerUrl,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if oResponse is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync self._oCommand.handleCommand(oResponse, oConnection);
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if oConnection is not None:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Automatically reboot if scratch init fails.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if self._cReinitScratchErrors > 8 and self.reinitScratch() is False:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync testboxcommons.log('Scratch does not initialize cleanly after %d attempts, rebooting...'
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # delay a wee bit before looping.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync ## @todo We shouldn't bother the server too frequently. We should try combine the test reporting done elsewhere
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # with the command retrieval done here. I believe tinderclient.pl is capable of doing that.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync time.sleep(random.randint(self.kcSecMinDelay * iFactor, self.kcSecMaxDelay * iFactor));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Not reached.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync Main function a la C/C++. Returns exit code.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Parse arguments.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync """ We need to override the exit code on --help, error and so on. """
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync parser = MyOptionParser(version=__version__[11:-1].strip());
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync for sMixed, sDefault, sDesc in [('Builds', sDefBuilds, 'builds'), ('TestRsrc', sDefTestRsrc, 'test resources') ]:
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest=sPrefix + 'Path', metavar='<abs-path>', default=sDefault,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest=sPrefix + 'ServerType', metavar='<nfs|cifs>', default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help='The type of server, cifs or nfs. If empty (default), we won\'t try mount anything.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest=sPrefix + 'ServerName', metavar='<server>', default='solserv.de.oracle.com',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest=sPrefix + 'ServerShare', metavar='<share>', default=sLower,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest=sPrefix + 'ServerUser', metavar='<user>', default='guestr',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help='The user name to use when accessing the ' + sDesc + ' share.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync parser.add_option('--' + sLower + '-server-passwd', '--' + sLower + '-server-password',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest=sPrefix + 'ServerPasswd', metavar='<password>', default='guestr',
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help='The password to use when accessing the ' + sDesc + ' share.');
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync parser.add_option("--test-manager", metavar="<url>",
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync default="http://tindertux.de.oracle.com/testmanager")
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync parser.add_option("--scratch-root", metavar="<abs-path>",
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync parser.add_option("--system-uuid", metavar="<uuid>",
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help="The system UUID of the testbox, used for uniquely identifiying the machine",
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fHasHwVirt", action="store_true", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help="Hardware virtualization available in the CPU");
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fHasHwVirt", action="store_false", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help="Hardware virtualization not available in the CPU");
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fHasNestedPaging", action="store_true", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fHasNestedPaging", action="store_false", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fCan64BitGuest", action="store_true", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fCan64BitGuest", action="store_false", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fHasIoMmu", action="store_true", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync dest="fHasIoMmu", action="store_false", default=None,
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync parser.add_option("-E", "--putenv", metavar = "<variable>=<value>", action = "append",
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync help = "Sets an environment variable. Can be repeated.");
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Check command line
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync if not oOptions.sTestManagerUrl.startswith('http://') \
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync and not oOptions.sTestManagerUrl.startswith('https://'):
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync print('Syntax error: Invalid test manager URL "%s"' % (oOptions.sTestManagerUrl,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync print('Syntax error: Invalid server type "%s"' % (sType,));
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Instantiate the testbox script and start dispatching work.
cf22150eaeeb72431bf1cf65c309a431454fb22bvboxsync # Not supposed to get here...