VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testdriver/vbox.py@ 84581

Last change on this file since 84581 was 84581, checked in by vboxsync, 5 years ago

Validation Kit/testdriver: Use forward slashes in getGuestWinDir().

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 186.0 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: vbox.py 84581 2020-05-28 11:11:14Z vboxsync $
3# pylint: disable=too-many-lines
4
5"""
6VirtualBox Specific base testdriver.
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2010-2020 Oracle Corporation
12
13This file is part of VirtualBox Open Source Edition (OSE), as
14available from http://www.virtualbox.org. This file is free software;
15you can redistribute it and/or modify it under the terms of the GNU
16General Public License (GPL) as published by the Free Software
17Foundation, in version 2 as it comes in the "COPYING" file of the
18VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20
21The contents of this file may alternatively be used under the terms
22of the Common Development and Distribution License Version 1.0
23(CDDL) only, as it comes in the "COPYING.CDDL" file of the
24VirtualBox OSE distribution, in which case the provisions of the
25CDDL are applicable instead of those of the GPL.
26
27You may elect to license modified versions of this file under the
28terms and conditions of either the GPL or the CDDL or both.
29"""
30__version__ = "$Revision: 84581 $"
31
32# pylint: disable=unnecessary-semicolon
33
34# Standard Python imports.
35import datetime
36import os
37import platform
38import re;
39import sys
40import threading
41import time
42import traceback
43
44# Figure out where the validation kit lives and make sure it's in the path.
45try: __file__
46except: __file__ = sys.argv[0];
47g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)));
48if g_ksValidationKitDir not in sys.path:
49 sys.path.append(g_ksValidationKitDir);
50
51# Validation Kit imports.
52from common import utils;
53from testdriver import base;
54from testdriver import btresolver;
55from testdriver import reporter;
56from testdriver import vboxcon;
57from testdriver import vboxtestvms;
58
59# Python 3 hacks:
60if sys.version_info[0] >= 3:
61 xrange = range; # pylint: disable=redefined-builtin,invalid-name
62 long = int; # pylint: disable=redefined-builtin,invalid-name
63
64#
65# Exception and Error Unification Hacks.
66# Note! This is pretty gross stuff. Be warned!
67# TODO: Find better ways of doing these things, preferrably in vboxapi.
68#
69
70ComException = None; # pylint: disable=invalid-name
71__fnComExceptionGetAttr__ = None; # pylint: disable=invalid-name
72
73def __MyDefaultGetAttr(oSelf, sName):
74 """ __getattribute__/__getattr__ default fake."""
75 try:
76 oAttr = oSelf.__dict__[sName];
77 except:
78 oAttr = dir(oSelf)[sName];
79 return oAttr;
80
81def __MyComExceptionGetAttr(oSelf, sName):
82 """ ComException.__getattr__ wrapper - both XPCOM and COM. """
83 try:
84 oAttr = __fnComExceptionGetAttr__(oSelf, sName);
85 except AttributeError:
86 if platform.system() == 'Windows':
87 if sName == 'errno':
88 oAttr = __fnComExceptionGetAttr__(oSelf, 'hresult');
89 elif sName == 'msg':
90 oAttr = __fnComExceptionGetAttr__(oSelf, 'strerror');
91 else:
92 raise;
93 else:
94 if sName == 'hresult':
95 oAttr = __fnComExceptionGetAttr__(oSelf, 'errno');
96 elif sName == 'strerror':
97 oAttr = __fnComExceptionGetAttr__(oSelf, 'msg');
98 elif sName == 'excepinfo':
99 oAttr = None;
100 elif sName == 'argerror':
101 oAttr = None;
102 else:
103 raise;
104 #print '__MyComExceptionGetAttr(,%s) -> "%s"' % (sName, oAttr);
105 return oAttr;
106
107def __deployExceptionHacks__(oNativeComExceptionClass):
108 """
109 Deploys the exception and error hacks that helps unifying COM and XPCOM
110 exceptions and errors.
111 """
112 global ComException # pylint: disable=invalid-name
113 global __fnComExceptionGetAttr__ # pylint: disable=invalid-name
114
115 # Hook up our attribute getter for the exception class (ASSUMES new-style).
116 if __fnComExceptionGetAttr__ is None:
117 try:
118 __fnComExceptionGetAttr__ = getattr(oNativeComExceptionClass, '__getattr__');
119 except:
120 try:
121 __fnComExceptionGetAttr__ = getattr(oNativeComExceptionClass, '__getattribute__');
122 except:
123 __fnComExceptionGetAttr__ = __MyDefaultGetAttr;
124 setattr(oNativeComExceptionClass, '__getattr__', __MyComExceptionGetAttr)
125
126 # Make the modified classes accessible (are there better ways to do this?)
127 ComException = oNativeComExceptionClass
128 return None;
129
130
131
132#
133# Utility functions.
134#
135
136def isIpAddrValid(sIpAddr):
137 """
138 Checks if a IPv4 address looks valid. This will return false for
139 localhost and similar.
140 Returns True / False.
141 """
142 if sIpAddr is None: return False;
143 if len(sIpAddr.split('.')) != 4: return False;
144 if sIpAddr.endswith('.0'): return False;
145 if sIpAddr.endswith('.255'): return False;
146 if sIpAddr.startswith('127.'): return False;
147 if sIpAddr.startswith('169.254.'): return False;
148 if sIpAddr.startswith('192.0.2.'): return False;
149 if sIpAddr.startswith('224.0.0.'): return False;
150 return True;
151
152def stringifyErrorInfo(oErrInfo):
153 """
154 Stringifies the error information in a IVirtualBoxErrorInfo object.
155
156 Returns string with error info.
157 """
158 try:
159 rc = oErrInfo.resultCode;
160 sText = oErrInfo.text;
161 sIid = oErrInfo.interfaceID;
162 sComponent = oErrInfo.component;
163 except:
164 sRet = 'bad error object (%s)?' % (oErrInfo,);
165 traceback.print_exc();
166 else:
167 sRet = 'rc=%s text="%s" IID=%s component=%s' % (ComError.toString(rc), sText, sIid, sComponent);
168 return sRet;
169
170def reportError(oErr, sText):
171 """
172 Report a VirtualBox error on oErr. oErr can be IVirtualBoxErrorInfo
173 or IProgress. Anything else is ignored.
174
175 Returns the same a reporter.error().
176 """
177 try:
178 oErrObj = oErr.errorInfo; # IProgress.
179 except:
180 oErrObj = oErr;
181 reporter.error(sText);
182 return reporter.error(stringifyErrorInfo(oErrObj));
183
184def formatComOrXpComException(oType, oXcpt):
185 """
186 Callback installed with the reporter to better format COM exceptions.
187 Similar to format_exception_only, only it returns None if not interested.
188 """
189 _ = oType;
190 oVBoxMgr = vboxcon.goHackModuleClass.oVBoxMgr;
191 if oVBoxMgr is None:
192 return None;
193 if not oVBoxMgr.xcptIsOurXcptKind(oXcpt): # pylint: disable=not-callable
194 return None;
195
196 if platform.system() == 'Windows':
197 hrc = oXcpt.hresult;
198 if hrc == ComError.DISP_E_EXCEPTION and oXcpt.excepinfo is not None and len(oXcpt.excepinfo) > 5:
199 hrc = oXcpt.excepinfo[5];
200 sWhere = oXcpt.excepinfo[1];
201 sMsg = oXcpt.excepinfo[2];
202 else:
203 sWhere = None;
204 sMsg = oXcpt.strerror;
205 else:
206 hrc = oXcpt.errno;
207 sWhere = None;
208 sMsg = oXcpt.msg;
209
210 sHrc = oVBoxMgr.xcptToString(hrc); # pylint: disable=not-callable
211 if sHrc.find('(') < 0:
212 sHrc = '%s (%#x)' % (sHrc, hrc & 0xffffffff,);
213
214 asRet = ['COM-Xcpt: %s' % (sHrc,)];
215 if sMsg and sWhere:
216 asRet.append('--------- %s: %s' % (sWhere, sMsg,));
217 elif sMsg:
218 asRet.append('--------- %s' % (sMsg,));
219 return asRet;
220 #if sMsg and sWhere:
221 # return ['COM-Xcpt: %s - %s: %s' % (sHrc, sWhere, sMsg,)];
222 #if sMsg:
223 # return ['COM-Xcpt: %s - %s' % (sHrc, sMsg,)];
224 #return ['COM-Xcpt: %s' % (sHrc,)];
225
226#
227# Classes
228#
229
230class ComError(object):
231 """
232 Unified COM and XPCOM status code repository.
233 This works more like a module than a class since it's replacing a module.
234 """
235
236 # The VBOX_E_XXX bits:
237 __VBOX_E_BASE = -2135228416;
238 VBOX_E_OBJECT_NOT_FOUND = __VBOX_E_BASE + 1;
239 VBOX_E_INVALID_VM_STATE = __VBOX_E_BASE + 2;
240 VBOX_E_VM_ERROR = __VBOX_E_BASE + 3;
241 VBOX_E_FILE_ERROR = __VBOX_E_BASE + 4;
242 VBOX_E_IPRT_ERROR = __VBOX_E_BASE + 5;
243 VBOX_E_PDM_ERROR = __VBOX_E_BASE + 6;
244 VBOX_E_INVALID_OBJECT_STATE = __VBOX_E_BASE + 7;
245 VBOX_E_HOST_ERROR = __VBOX_E_BASE + 8;
246 VBOX_E_NOT_SUPPORTED = __VBOX_E_BASE + 9;
247 VBOX_E_XML_ERROR = __VBOX_E_BASE + 10;
248 VBOX_E_INVALID_SESSION_STATE = __VBOX_E_BASE + 11;
249 VBOX_E_OBJECT_IN_USE = __VBOX_E_BASE + 12;
250 VBOX_E_DONT_CALL_AGAIN = __VBOX_E_BASE + 13;
251
252 # Reverse lookup table.
253 dDecimalToConst = {}; # pylint: disable=invalid-name
254
255 def __init__(self):
256 raise base.GenError('No instances, please');
257
258 @staticmethod
259 def copyErrors(oNativeComErrorClass):
260 """
261 Copy all error codes from oNativeComErrorClass to this class and
262 install compatability mappings.
263 """
264
265 # First, add the VBOX_E_XXX constants to dDecimalToConst.
266 for sAttr in dir(ComError):
267 if sAttr.startswith('VBOX_E'):
268 oAttr = getattr(ComError, sAttr);
269 ComError.dDecimalToConst[oAttr] = sAttr;
270
271 # Copy all error codes from oNativeComErrorClass to this class.
272 for sAttr in dir(oNativeComErrorClass):
273 if sAttr[0].isupper():
274 oAttr = getattr(oNativeComErrorClass, sAttr);
275 setattr(ComError, sAttr, oAttr);
276 if isinstance(oAttr, int):
277 ComError.dDecimalToConst[oAttr] = sAttr;
278
279 # Install mappings to the other platform.
280 if platform.system() == 'Windows':
281 ComError.NS_OK = ComError.S_OK;
282 ComError.NS_ERROR_FAILURE = ComError.E_FAIL;
283 ComError.NS_ERROR_ABORT = ComError.E_ABORT;
284 ComError.NS_ERROR_NULL_POINTER = ComError.E_POINTER;
285 ComError.NS_ERROR_NO_INTERFACE = ComError.E_NOINTERFACE;
286 ComError.NS_ERROR_INVALID_ARG = ComError.E_INVALIDARG;
287 ComError.NS_ERROR_OUT_OF_MEMORY = ComError.E_OUTOFMEMORY;
288 ComError.NS_ERROR_NOT_IMPLEMENTED = ComError.E_NOTIMPL;
289 ComError.NS_ERROR_UNEXPECTED = ComError.E_UNEXPECTED;
290 else:
291 ComError.E_ACCESSDENIED = -2147024891; # see VBox/com/defs.h
292 ComError.S_OK = ComError.NS_OK;
293 ComError.E_FAIL = ComError.NS_ERROR_FAILURE;
294 ComError.E_ABORT = ComError.NS_ERROR_ABORT;
295 ComError.E_POINTER = ComError.NS_ERROR_NULL_POINTER;
296 ComError.E_NOINTERFACE = ComError.NS_ERROR_NO_INTERFACE;
297 ComError.E_INVALIDARG = ComError.NS_ERROR_INVALID_ARG;
298 ComError.E_OUTOFMEMORY = ComError.NS_ERROR_OUT_OF_MEMORY;
299 ComError.E_NOTIMPL = ComError.NS_ERROR_NOT_IMPLEMENTED;
300 ComError.E_UNEXPECTED = ComError.NS_ERROR_UNEXPECTED;
301 ComError.DISP_E_EXCEPTION = -2147352567; # For COM compatability only.
302 return True;
303
304 @staticmethod
305 def getXcptResult(oXcpt):
306 """
307 Gets the result code for an exception.
308 Returns COM status code (or E_UNEXPECTED).
309 """
310 if platform.system() == 'Windows':
311 # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
312 # empirical info on it so far.
313 try:
314 hrXcpt = oXcpt.hresult;
315 except AttributeError:
316 hrXcpt = ComError.E_UNEXPECTED;
317 if hrXcpt == ComError.DISP_E_EXCEPTION and oXcpt.excepinfo is not None:
318 hrXcpt = oXcpt.excepinfo[5];
319 else:
320 try:
321 hrXcpt = oXcpt.errno;
322 except AttributeError:
323 hrXcpt = ComError.E_UNEXPECTED;
324 return hrXcpt;
325
326 @staticmethod
327 def equal(oXcpt, hr):
328 """
329 Checks if the ComException e is not equal to the COM status code hr.
330 This takes DISP_E_EXCEPTION & excepinfo into account.
331
332 This method can be used with any Exception derivate, however it will
333 only return True for classes similar to the two ComException variants.
334 """
335 if platform.system() == 'Windows':
336 # The DISP_E_EXCEPTION + excptinfo fun needs checking up, only
337 # empirical info on it so far.
338 try:
339 hrXcpt = oXcpt.hresult;
340 except AttributeError:
341 return False;
342 if hrXcpt == ComError.DISP_E_EXCEPTION and oXcpt.excepinfo is not None:
343 hrXcpt = oXcpt.excepinfo[5];
344 else:
345 try:
346 hrXcpt = oXcpt.errno;
347 except AttributeError:
348 return False;
349 return hrXcpt == hr;
350
351 @staticmethod
352 def notEqual(oXcpt, hr):
353 """
354 Checks if the ComException e is not equal to the COM status code hr.
355 See equal() for more details.
356 """
357 return not ComError.equal(oXcpt, hr)
358
359 @staticmethod
360 def toString(hr):
361 """
362 Converts the specified COM status code to a string.
363 """
364 try:
365 sStr = ComError.dDecimalToConst[int(hr)];
366 except KeyError:
367 hrLong = long(hr);
368 sStr = '%#x (%d)' % (hrLong, hrLong);
369 return sStr;
370
371
372class Build(object): # pylint: disable=too-few-public-methods
373 """
374 A VirtualBox build.
375
376 Note! After dropping the installation of VBox from this code and instead
377 realizing that with the vboxinstall.py wrapper driver, this class is
378 of much less importance and contains unnecessary bits and pieces.
379 """
380
381 def __init__(self, oDriver, strInstallPath):
382 """
383 Construct a build object from a build file name and/or install path.
384 """
385 # Initialize all members first.
386 self.oDriver = oDriver;
387 self.sInstallPath = strInstallPath;
388 self.sSdkPath = None;
389 self.sSrcRoot = None;
390 self.sKind = None;
391 self.sDesignation = None;
392 self.sType = None;
393 self.sOs = None;
394 self.sArch = None;
395 self.sGuestAdditionsIso = None;
396
397 # Figure out the values as best we can.
398 if strInstallPath is None:
399 #
400 # Both parameters are None, which means we're falling back on a
401 # build in the development tree.
402 #
403 self.sKind = "development";
404
405 if self.sType is None:
406 self.sType = os.environ.get("KBUILD_TYPE", os.environ.get("BUILD_TYPE", "release"));
407 if self.sOs is None:
408 self.sOs = os.environ.get("KBUILD_TARGET", os.environ.get("BUILD_TARGET", oDriver.sHost));
409 if self.sArch is None:
410 self.sArch = os.environ.get("KBUILD_TARGET_ARCH", os.environ.get("BUILD_TARGET_ARCH", oDriver.sHostArch));
411
412 sOut = os.path.join('out', self.sOs + '.' + self.sArch, self.sType);
413 sSearch = os.environ.get('VBOX_TD_DEV_TREE', os.path.dirname(__file__)); # Env.var. for older trees or testboxscript.
414 sCandidat = None;
415 for i in range(0, 10): # pylint: disable=unused-variable
416 sBldDir = os.path.join(sSearch, sOut);
417 if os.path.isdir(sBldDir):
418 sCandidat = os.path.join(sBldDir, 'bin', 'VBoxSVC' + base.exeSuff());
419 if os.path.isfile(sCandidat):
420 self.sSdkPath = os.path.join(sBldDir, 'bin/sdk');
421 break;
422 sCandidat = os.path.join(sBldDir, 'dist/VirtualBox.app/Contents/MacOS/VBoxSVC');
423 if os.path.isfile(sCandidat):
424 self.sSdkPath = os.path.join(sBldDir, 'dist/sdk');
425 break;
426 sSearch = os.path.abspath(os.path.join(sSearch, '..'));
427 if sCandidat is None or not os.path.isfile(sCandidat):
428 raise base.GenError();
429 self.sInstallPath = os.path.abspath(os.path.dirname(sCandidat));
430 self.sSrcRoot = os.path.abspath(sSearch);
431
432 self.sDesignation = os.environ.get('TEST_BUILD_DESIGNATION', None);
433 if self.sDesignation is None:
434 try:
435 oFile = utils.openNoInherit(os.path.join(self.sSrcRoot, sOut, 'revision.kmk'), 'r');
436 except:
437 pass;
438 else:
439 s = oFile.readline();
440 oFile.close();
441 oMatch = re.search("VBOX_SVN_REV=(\\d+)", s);
442 if oMatch is not None:
443 self.sDesignation = oMatch.group(1);
444
445 if self.sDesignation is None:
446 self.sDesignation = 'XXXXX'
447 else:
448 #
449 # We've been pointed to an existing installation, this could be
450 # in the out dir of a svn checkout, untarred VBoxAll or a real
451 # installation directory.
452 #
453 self.sKind = "preinstalled";
454 self.sType = "release";
455 self.sOs = oDriver.sHost;
456 self.sArch = oDriver.sHostArch;
457 self.sInstallPath = os.path.abspath(strInstallPath);
458 self.sSdkPath = os.path.join(self.sInstallPath, 'sdk');
459 self.sSrcRoot = None;
460 self.sDesignation = os.environ.get('TEST_BUILD_DESIGNATION', 'XXXXX');
461 ## @todo Much more work is required here.
462
463 # Do some checks.
464 sVMMR0 = os.path.join(self.sInstallPath, 'VMMR0.r0');
465 if not os.path.isfile(sVMMR0) and utils.getHostOs() == 'solaris': # solaris is special.
466 sVMMR0 = os.path.join(self.sInstallPath, 'amd64' if utils.getHostArch() == 'amd64' else 'i386', 'VMMR0.r0');
467 if not os.path.isfile(sVMMR0):
468 raise base.GenError('%s is missing' % (sVMMR0,));
469
470 # Guest additions location is different on windows for some _stupid_ reason.
471 if self.sOs == 'win' and self.sKind != 'development':
472 self.sGuestAdditionsIso = '%s/VBoxGuestAdditions.iso' % (self.sInstallPath,);
473 elif self.sOs == 'darwin':
474 self.sGuestAdditionsIso = '%s/VBoxGuestAdditions.iso' % (self.sInstallPath,);
475 elif self.sOs == 'solaris':
476 self.sGuestAdditionsIso = '%s/VBoxGuestAdditions.iso' % (self.sInstallPath,);
477 else:
478 self.sGuestAdditionsIso = '%s/additions/VBoxGuestAdditions.iso' % (self.sInstallPath,);
479
480 # __init__ end;
481
482 def isDevBuild(self):
483 """ Returns True if it's development build (kind), otherwise False. """
484 return self.sKind == 'development';
485
486
487class EventHandlerBase(object):
488 """
489 Base class for both Console and VirtualBox event handlers.
490 """
491
492 def __init__(self, dArgs, fpApiVer, sName = None):
493 self.oVBoxMgr = dArgs['oVBoxMgr'];
494 self.oEventSrc = dArgs['oEventSrc']; # Console/VirtualBox for < 3.3
495 self.oListener = dArgs['oListener'];
496 self.fPassive = self.oListener is not None;
497 self.sName = sName
498 self.fShutdown = False;
499 self.oThread = None;
500 self.fpApiVer = fpApiVer;
501 self.dEventNo2Name = {};
502 for sKey, iValue in self.oVBoxMgr.constants.all_values('VBoxEventType').items():
503 self.dEventNo2Name[iValue] = sKey;
504
505 def threadForPassiveMode(self):
506 """
507 The thread procedure for the event processing thread.
508 """
509 assert self.fPassive is not None;
510 while not self.fShutdown:
511 try:
512 oEvt = self.oEventSrc.getEvent(self.oListener, 500);
513 except:
514 if not self.oVBoxMgr.xcptIsDeadInterface(): reporter.logXcpt();
515 else: reporter.log('threadForPassiveMode/%s: interface croaked (ignored)' % (self.sName,));
516 break;
517 if oEvt:
518 self.handleEvent(oEvt);
519 if not self.fShutdown:
520 try:
521 self.oEventSrc.eventProcessed(self.oListener, oEvt);
522 except:
523 reporter.logXcpt();
524 break;
525 self.unregister(fWaitForThread = False);
526 return None;
527
528 def startThreadForPassiveMode(self):
529 """
530 Called when working in passive mode.
531 """
532 self.oThread = threading.Thread(target = self.threadForPassiveMode, \
533 args=(), name=('PAS-%s' % (self.sName,)));
534 self.oThread.setDaemon(True)
535 self.oThread.start();
536 return None;
537
538 def unregister(self, fWaitForThread = True):
539 """
540 Unregister the event handler.
541 """
542 fRc = False;
543 if not self.fShutdown:
544 self.fShutdown = True;
545
546 if self.oEventSrc is not None:
547 if self.fpApiVer < 3.3:
548 try:
549 self.oEventSrc.unregisterCallback(self.oListener);
550 fRc = True;
551 except:
552 reporter.errorXcpt('unregisterCallback failed on %s' % (self.oListener,));
553 else:
554 try:
555 self.oEventSrc.unregisterListener(self.oListener);
556 fRc = True;
557 except:
558 if self.oVBoxMgr.xcptIsDeadInterface():
559 reporter.log('unregisterListener failed on %s because of dead interface (%s)'
560 % (self.oListener, self.oVBoxMgr.xcptToString(),));
561 else:
562 reporter.errorXcpt('unregisterListener failed on %s' % (self.oListener,));
563
564 if self.oThread is not None \
565 and self.oThread != threading.current_thread():
566 self.oThread.join();
567 self.oThread = None;
568
569 _ = fWaitForThread;
570 return fRc;
571
572 def handleEvent(self, oEvt):
573 """
574 Compatibility wrapper that child classes implement.
575 """
576 _ = oEvt;
577 return None;
578
579 @staticmethod
580 def registerDerivedEventHandler(oVBoxMgr, fpApiVer, oSubClass, dArgsCopy, # pylint: disable=too-many-arguments
581 oSrcParent, sSrcParentNm, sICallbackNm,
582 fMustSucceed = True, sLogSuffix = '', aenmEvents = None):
583 """
584 Registers the callback / event listener.
585 """
586 dArgsCopy['oVBoxMgr'] = oVBoxMgr;
587 dArgsCopy['oListener'] = None;
588 if fpApiVer < 3.3:
589 dArgsCopy['oEventSrc'] = oSrcParent;
590 try:
591 oRet = oVBoxMgr.createCallback(sICallbackNm, oSubClass, dArgsCopy);
592 except:
593 reporter.errorXcpt('%s::registerCallback(%s) failed%s' % (sSrcParentNm, oRet, sLogSuffix));
594 else:
595 try:
596 oSrcParent.registerCallback(oRet);
597 return oRet;
598 except Exception as oXcpt:
599 if fMustSucceed or ComError.notEqual(oXcpt, ComError.E_UNEXPECTED):
600 reporter.errorXcpt('%s::registerCallback(%s)%s' % (sSrcParentNm, oRet, sLogSuffix));
601 else:
602 #
603 # Scalable event handling introduced in VBox 4.0.
604 #
605 fPassive = sys.platform == 'win32'; # or webservices.
606
607 if not aenmEvents:
608 aenmEvents = (vboxcon.VBoxEventType_Any,);
609
610 try:
611 oEventSrc = oSrcParent.eventSource;
612 dArgsCopy['oEventSrc'] = oEventSrc;
613 if not fPassive:
614 oListener = oRet = oVBoxMgr.createListener(oSubClass, dArgsCopy);
615 else:
616 oListener = oEventSrc.createListener();
617 dArgsCopy['oListener'] = oListener;
618 oRet = oSubClass(dArgsCopy);
619 except:
620 reporter.errorXcpt('%s::eventSource.createListener(%s) failed%s' % (sSrcParentNm, oListener, sLogSuffix));
621 else:
622 try:
623 oEventSrc.registerListener(oListener, aenmEvents, not fPassive);
624 except Exception as oXcpt:
625 if fMustSucceed or ComError.notEqual(oXcpt, ComError.E_UNEXPECTED):
626 reporter.errorXcpt('%s::eventSource.registerListener(%s) failed%s'
627 % (sSrcParentNm, oListener, sLogSuffix));
628 else:
629 if not fPassive:
630 if sys.platform == 'win32':
631 from win32com.server.util import unwrap # pylint: disable=import-error
632 oRet = unwrap(oRet);
633 oRet.oListener = oListener;
634 else:
635 oRet.startThreadForPassiveMode();
636 return oRet;
637 return None;
638
639
640
641
642class ConsoleEventHandlerBase(EventHandlerBase):
643 """
644 Base class for handling IConsole events.
645
646 The class has IConsoleCallback (<=3.2) compatible callback methods which
647 the user can override as needed.
648
649 Note! This class must not inherit from object or we'll get type errors in VBoxPython.
650 """
651 def __init__(self, dArgs, sName = None):
652 self.oSession = dArgs['oSession'];
653 self.oConsole = dArgs['oConsole'];
654 if sName is None:
655 sName = self.oSession.sName;
656 EventHandlerBase.__init__(self, dArgs, self.oSession.fpApiVer, sName);
657
658
659 # pylint: disable=missing-docstring,too-many-arguments,unused-argument
660 def onMousePointerShapeChange(self, fVisible, fAlpha, xHot, yHot, cx, cy, abShape):
661 reporter.log2('onMousePointerShapeChange/%s' % (self.sName));
662 def onMouseCapabilityChange(self, fSupportsAbsolute, *aArgs): # Extra argument was added in 3.2.
663 reporter.log2('onMouseCapabilityChange/%s' % (self.sName));
664 def onKeyboardLedsChange(self, fNumLock, fCapsLock, fScrollLock):
665 reporter.log2('onKeyboardLedsChange/%s' % (self.sName));
666 def onStateChange(self, eState):
667 reporter.log2('onStateChange/%s' % (self.sName));
668 def onAdditionsStateChange(self):
669 reporter.log2('onAdditionsStateChange/%s' % (self.sName));
670 def onNetworkAdapterChange(self, oNic):
671 reporter.log2('onNetworkAdapterChange/%s' % (self.sName));
672 def onSerialPortChange(self, oPort):
673 reporter.log2('onSerialPortChange/%s' % (self.sName));
674 def onParallelPortChange(self, oPort):
675 reporter.log2('onParallelPortChange/%s' % (self.sName));
676 def onStorageControllerChange(self):
677 reporter.log2('onStorageControllerChange/%s' % (self.sName));
678 def onMediumChange(self, attachment):
679 reporter.log2('onMediumChange/%s' % (self.sName));
680 def onCPUChange(self, iCpu, fAdd):
681 reporter.log2('onCPUChange/%s' % (self.sName));
682 def onVRDPServerChange(self):
683 reporter.log2('onVRDPServerChange/%s' % (self.sName));
684 def onRemoteDisplayInfoChange(self):
685 reporter.log2('onRemoteDisplayInfoChange/%s' % (self.sName));
686 def onUSBControllerChange(self):
687 reporter.log2('onUSBControllerChange/%s' % (self.sName));
688 def onUSBDeviceStateChange(self, oDevice, fAttached, oError):
689 reporter.log2('onUSBDeviceStateChange/%s' % (self.sName));
690 def onSharedFolderChange(self, fGlobal):
691 reporter.log2('onSharedFolderChange/%s' % (self.sName));
692 def onRuntimeError(self, fFatal, sErrId, sMessage):
693 reporter.log2('onRuntimeError/%s' % (self.sName));
694 def onCanShowWindow(self):
695 reporter.log2('onCanShowWindow/%s' % (self.sName));
696 return True
697 def onShowWindow(self):
698 reporter.log2('onShowWindow/%s' % (self.sName));
699 return None;
700 # pylint: enable=missing-docstring,too-many-arguments,unused-argument
701
702 def handleEvent(self, oEvt):
703 """
704 Compatibility wrapper.
705 """
706 try:
707 oEvtBase = self.oVBoxMgr.queryInterface(oEvt, 'IEvent');
708 eType = oEvtBase.type;
709 except:
710 reporter.logXcpt();
711 return None;
712 if eType == vboxcon.VBoxEventType_OnRuntimeError:
713 try:
714 oEvtIt = self.oVBoxMgr.queryInterface(oEvtBase, 'IRuntimeErrorEvent');
715 return self.onRuntimeError(oEvtIt.fatal, oEvtIt.id, oEvtIt.message)
716 except:
717 reporter.logXcpt();
718 ## @todo implement the other events.
719 try:
720 if eType not in (vboxcon.VBoxEventType_OnMousePointerShapeChanged,
721 vboxcon.VBoxEventType_OnCursorPositionChanged):
722 if eType in self.dEventNo2Name:
723 reporter.log2('%s(%s)/%s' % (self.dEventNo2Name[eType], str(eType), self.sName));
724 else:
725 reporter.log2('%s/%s' % (str(eType), self.sName));
726 except AttributeError: # Handle older VBox versions which don't have a specific event.
727 pass;
728 return None;
729
730
731class VirtualBoxEventHandlerBase(EventHandlerBase):
732 """
733 Base class for handling IVirtualBox events.
734
735 The class has IConsoleCallback (<=3.2) compatible callback methods which
736 the user can override as needed.
737
738 Note! This class must not inherit from object or we'll get type errors in VBoxPython.
739 """
740 def __init__(self, dArgs, sName = "emanon"):
741 self.oVBoxMgr = dArgs['oVBoxMgr'];
742 self.oVBox = dArgs['oVBox'];
743 EventHandlerBase.__init__(self, dArgs, self.oVBox.fpApiVer, sName);
744
745 # pylint: disable=missing-docstring,unused-argument
746 def onMachineStateChange(self, sMachineId, eState):
747 pass;
748 def onMachineDataChange(self, sMachineId):
749 pass;
750 def onExtraDataCanChange(self, sMachineId, sKey, sValue):
751 # The COM bridge does tuples differently. Not very funny if you ask me... ;-)
752 if self.oVBoxMgr.type == 'MSCOM':
753 return '', 0, True;
754 return True, ''
755 def onExtraDataChange(self, sMachineId, sKey, sValue):
756 pass;
757 def onMediumRegistered(self, sMediumId, eMediumType, fRegistered):
758 pass;
759 def onMachineRegistered(self, sMachineId, fRegistered):
760 pass;
761 def onSessionStateChange(self, sMachineId, eState):
762 pass;
763 def onSnapshotTaken(self, sMachineId, sSnapshotId):
764 pass;
765 def onSnapshotDiscarded(self, sMachineId, sSnapshotId):
766 pass;
767 def onSnapshotChange(self, sMachineId, sSnapshotId):
768 pass;
769 def onGuestPropertyChange(self, sMachineId, sName, sValue, sFlags):
770 pass;
771 # pylint: enable=missing-docstring,unused-argument
772
773 def handleEvent(self, oEvt):
774 """
775 Compatibility wrapper.
776 """
777 try:
778 oEvtBase = self.oVBoxMgr.queryInterface(oEvt, 'IEvent');
779 eType = oEvtBase.type;
780 except:
781 reporter.logXcpt();
782 return None;
783 if eType == vboxcon.VBoxEventType_OnMachineStateChanged:
784 try:
785 oEvtIt = self.oVBoxMgr.queryInterface(oEvtBase, 'IMachineStateChangedEvent');
786 return self.onMachineStateChange(oEvtIt.machineId, oEvtIt.state)
787 except:
788 reporter.logXcpt();
789 elif eType == vboxcon.VBoxEventType_OnGuestPropertyChanged:
790 try:
791 oEvtIt = self.oVBoxMgr.queryInterface(oEvtBase, 'IGuestPropertyChangedEvent');
792 return self.onGuestPropertyChange(oEvtIt.machineId, oEvtIt.name, oEvtIt.value, oEvtIt.flags);
793 except:
794 reporter.logXcpt();
795 ## @todo implement the other events.
796 if eType in self.dEventNo2Name:
797 reporter.log2('%s(%s)/%s' % (self.dEventNo2Name[eType], str(eType), self.sName));
798 else:
799 reporter.log2('%s/%s' % (str(eType), self.sName));
800 return None;
801
802
803class SessionConsoleEventHandler(ConsoleEventHandlerBase):
804 """
805 For catching machine state changes and waking up the task machinery at that point.
806 """
807 def __init__(self, dArgs):
808 ConsoleEventHandlerBase.__init__(self, dArgs);
809
810 def onMachineStateChange(self, sMachineId, eState): # pylint: disable=unused-argument
811 """ Just interrupt the wait loop here so it can check again. """
812 _ = sMachineId; _ = eState;
813 self.oVBoxMgr.interruptWaitEvents();
814
815 def onRuntimeError(self, fFatal, sErrId, sMessage):
816 reporter.log('onRuntimeError/%s: fFatal=%d sErrId=%s sMessage=%s' % (self.sName, fFatal, sErrId, sMessage));
817 oSession = self.oSession;
818 if oSession is not None: # paranoia
819 if sErrId == 'HostMemoryLow':
820 oSession.signalHostMemoryLow();
821 if sys.platform == 'win32':
822 from testdriver import winbase;
823 winbase.logMemoryStats();
824 oSession.signalTask();
825 self.oVBoxMgr.interruptWaitEvents();
826
827
828
829class TestDriver(base.TestDriver): # pylint: disable=too-many-instance-attributes
830 """
831 This is the VirtualBox test driver.
832 """
833
834 def __init__(self):
835 base.TestDriver.__init__(self);
836 self.fImportedVBoxApi = False;
837 self.fpApiVer = 3.2;
838 self.uRevision = 0;
839 self.uApiRevision = 0;
840 self.oBuild = None;
841 self.oVBoxMgr = None;
842 self.oVBox = None;
843 self.aoRemoteSessions = [];
844 self.aoVMs = []; ## @todo not sure if this list will be of any use.
845 self.oTestVmManager = vboxtestvms.TestVmManager(self.sResourcePath);
846 self.oTestVmSet = vboxtestvms.TestVmSet();
847 self.sSessionTypeDef = 'headless';
848 self.sSessionType = self.sSessionTypeDef;
849 self.fEnableVrdp = True;
850 self.uVrdpBasePortDef = 6000;
851 self.uVrdpBasePort = self.uVrdpBasePortDef;
852 self.sDefBridgedNic = None;
853 self.fUseDefaultSvc = False;
854 self.sLogSelfGroups = '';
855 self.sLogSelfFlags = 'time';
856 self.sLogSelfDest = '';
857 self.sLogSessionGroups = '';
858 self.sLogSessionFlags = 'time';
859 self.sLogSessionDest = '';
860 self.sLogSvcGroups = '';
861 self.sLogSvcFlags = 'time';
862 self.sLogSvcDest = '';
863 self.sSelfLogFile = None;
864 self.sVBoxSvcLogFile = None;
865 self.oVBoxSvcProcess = None;
866 self.sVBoxSvcPidFile = None;
867 self.fVBoxSvcInDebugger = False;
868 self.sVBoxValidationKit = None;
869 self.sVBoxValidationKitIso = None;
870 self.sVBoxBootSectors = None;
871 self.fAlwaysUploadLogs = False;
872 self.fAlwaysUploadScreenshots = False;
873 self.fEnableDebugger = True;
874
875 # Quietly detect build and validation kit.
876 self._detectBuild(False);
877 self._detectValidationKit(False);
878
879 # Make sure all debug logs goes to the scratch area unless
880 # specified otherwise (more of this later on).
881 if 'VBOX_LOG_DEST' not in os.environ:
882 os.environ['VBOX_LOG_DEST'] = 'nodeny dir=%s' % (self.sScratchPath);
883
884
885 def _detectBuild(self, fQuiet = False):
886 """
887 This is used internally to try figure a locally installed build when
888 running tests manually.
889 """
890 if self.oBuild is not None:
891 return True;
892
893 # Try dev build first since that's where I'll be using it first...
894 if True is True: # pylint: disable=comparison-with-itself
895 try:
896 self.oBuild = Build(self, None);
897 return True;
898 except base.GenError:
899 pass;
900
901 # Try default installation locations.
902 if self.sHost == 'win':
903 sProgFiles = os.environ.get('ProgramFiles', 'C:\\Program Files');
904 asLocs = [
905 os.path.join(sProgFiles, 'Oracle', 'VirtualBox'),
906 os.path.join(sProgFiles, 'OracleVM', 'VirtualBox'),
907 os.path.join(sProgFiles, 'Sun', 'VirtualBox'),
908 ];
909 elif self.sHost == 'solaris':
910 asLocs = [ '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0', '/opt/VirtualBox' ];
911 elif self.sHost == 'darwin':
912 asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ];
913 elif self.sHost == 'linux':
914 asLocs = [ '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0', '/opt/VirtualBox' ];
915 else:
916 asLocs = [ '/opt/VirtualBox' ];
917 if 'VBOX_INSTALL_PATH' in os.environ:
918 asLocs.insert(0, os.environ['VBOX_INSTALL_PATH']);
919
920 for sLoc in asLocs:
921 try:
922 self.oBuild = Build(self, sLoc);
923 return True;
924 except base.GenError:
925 pass;
926
927 if not fQuiet:
928 reporter.error('failed to find VirtualBox installation');
929 return False;
930
931 def _detectValidationKit(self, fQuiet = False):
932 """
933 This is used internally by the constructor to try locate an unzipped
934 VBox Validation Kit somewhere in the immediate proximity.
935 """
936 if self.sVBoxValidationKit is not None:
937 return True;
938
939 #
940 # Normally it's found where we're running from, which is the same as
941 # the script directly on the testboxes.
942 #
943 asCandidates = [self.sScriptPath, ];
944 if g_ksValidationKitDir not in asCandidates:
945 asCandidates.append(g_ksValidationKitDir);
946 if os.getcwd() not in asCandidates:
947 asCandidates.append(os.getcwd());
948 if self.oBuild is not None and self.oBuild.sInstallPath not in asCandidates:
949 asCandidates.append(self.oBuild.sInstallPath);
950
951 #
952 # When working out of the tree, we'll search the current directory
953 # as well as parent dirs.
954 #
955 for sDir in list(asCandidates):
956 for i in range(10):
957 sDir = os.path.dirname(sDir);
958 if sDir not in asCandidates:
959 asCandidates.append(sDir);
960
961 #
962 # Do the searching.
963 #
964 sCandidate = None;
965 for i, _ in enumerate(asCandidates):
966 sCandidate = asCandidates[i];
967 if os.path.isfile(os.path.join(sCandidate, 'VBoxValidationKit.iso')):
968 break;
969 sCandidate = os.path.join(sCandidate, 'validationkit');
970 if os.path.isfile(os.path.join(sCandidate, 'VBoxValidationKit.iso')):
971 break;
972 sCandidate = None;
973
974 fRc = sCandidate is not None;
975 if fRc is False:
976 if not fQuiet:
977 reporter.error('failed to find VBox Validation Kit installation (candidates: %s)' % (asCandidates,));
978 sCandidate = os.path.join(self.sScriptPath, 'validationkit'); # Don't leave the values as None.
979
980 #
981 # Set the member values.
982 #
983 self.sVBoxValidationKit = sCandidate;
984 self.sVBoxValidationKitIso = os.path.join(sCandidate, 'VBoxValidationKit.iso');
985 self.sVBoxBootSectors = os.path.join(sCandidate, 'bootsectors');
986 return fRc;
987
988 def _makeEnvironmentChanges(self):
989 """
990 Make the necessary VBox related environment changes.
991 Children not importing the VBox API should call this.
992 """
993 # Make sure we've got our own VirtualBox config and VBoxSVC (on XPCOM at least).
994 if not self.fUseDefaultSvc:
995 os.environ['VBOX_USER_HOME'] = os.path.join(self.sScratchPath, 'VBoxUserHome');
996 sUser = os.environ.get('USERNAME', os.environ.get('USER', os.environ.get('LOGNAME', 'unknown')));
997 os.environ['VBOX_IPC_SOCKETID'] = sUser + '-VBoxTest';
998 return True;
999
1000 @staticmethod
1001 def makeApiRevision(uMajor, uMinor, uBuild, uApiRevision):
1002 """ Calculates an API revision number. """
1003 return (long(uMajor) << 56) | (long(uMinor) << 48) | (long(uBuild) << 40) | uApiRevision;
1004
1005 def importVBoxApi(self):
1006 """
1007 Import the 'vboxapi' module from the VirtualBox build we're using and
1008 instantiate the two basic objects.
1009
1010 This will try detect an development or installed build if no build has
1011 been associated with the driver yet.
1012 """
1013 if self.fImportedVBoxApi:
1014 return True;
1015
1016 self._makeEnvironmentChanges();
1017
1018 # Do the detecting.
1019 self._detectBuild();
1020 if self.oBuild is None:
1021 return False;
1022
1023 # Avoid crashing when loading the 32-bit module (or whatever it is that goes bang).
1024 if self.oBuild.sArch == 'x86' \
1025 and self.sHost == 'darwin' \
1026 and platform.architecture()[0] == '64bit' \
1027 and self.oBuild.sKind == 'development' \
1028 and os.getenv('VERSIONER_PYTHON_PREFER_32_BIT') != 'yes':
1029 reporter.log("WARNING: 64-bit python on darwin, 32-bit VBox development build => crash");
1030 reporter.log("WARNING: bash-3.2$ /usr/bin/python2.5 ./testdriver");
1031 reporter.log("WARNING: or");
1032 reporter.log("WARNING: bash-3.2$ VERSIONER_PYTHON_PREFER_32_BIT=yes ./testdriver");
1033 return False;
1034
1035 # Start VBoxSVC and load the vboxapi bits.
1036 if self._startVBoxSVC() is True:
1037 assert(self.oVBoxSvcProcess is not None);
1038
1039 sSavedSysPath = sys.path;
1040 self._setupVBoxApi();
1041 sys.path = sSavedSysPath;
1042
1043 # Adjust the default machine folder.
1044 if self.fImportedVBoxApi and not self.fUseDefaultSvc and self.fpApiVer >= 4.0:
1045 sNewFolder = os.path.join(self.sScratchPath, 'VBoxUserHome', 'Machines');
1046 try:
1047 self.oVBox.systemProperties.defaultMachineFolder = sNewFolder;
1048 except:
1049 self.fImportedVBoxApi = False;
1050 self.oVBoxMgr = None;
1051 self.oVBox = None;
1052 reporter.logXcpt("defaultMachineFolder exception (sNewFolder=%s)" % (sNewFolder,));
1053
1054 # Kill VBoxSVC on failure.
1055 if self.oVBoxMgr is None:
1056 self._stopVBoxSVC();
1057 else:
1058 assert(self.oVBoxSvcProcess is None);
1059 return self.fImportedVBoxApi;
1060
1061 def _startVBoxSVC(self): # pylint: disable=too-many-statements
1062 """ Starts VBoxSVC. """
1063 assert(self.oVBoxSvcProcess is None);
1064
1065 # Setup vbox logging for VBoxSVC now and start it manually. This way
1066 # we can control both logging and shutdown.
1067 self.sVBoxSvcLogFile = '%s/VBoxSVC-debug.log' % (self.sScratchPath,);
1068 try: os.remove(self.sVBoxSvcLogFile);
1069 except: pass;
1070 os.environ['VBOX_LOG'] = self.sLogSvcGroups;
1071 os.environ['VBOX_LOG_FLAGS'] = '%s append' % (self.sLogSvcFlags,); # Append becuse of VBoxXPCOMIPCD.
1072 if self.sLogSvcDest:
1073 os.environ['VBOX_LOG_DEST'] = 'nodeny ' + self.sLogSvcDest;
1074 else:
1075 os.environ['VBOX_LOG_DEST'] = 'nodeny file=%s' % (self.sVBoxSvcLogFile,);
1076 os.environ['VBOXSVC_RELEASE_LOG_FLAGS'] = 'time append';
1077
1078 # Always leave a pid file behind so we can kill it during cleanup-before.
1079 self.sVBoxSvcPidFile = '%s/VBoxSVC.pid' % (self.sScratchPath,);
1080 fWritePidFile = True;
1081
1082 cMsFudge = 1;
1083 sVBoxSVC = '%s/VBoxSVC' % (self.oBuild.sInstallPath,); ## @todo .exe and stuff.
1084 if self.fVBoxSvcInDebugger:
1085 if self.sHost in ('darwin', 'freebsd', 'linux', 'solaris', ):
1086 # Start VBoxSVC in gdb in a new terminal.
1087 #sTerm = '/usr/bin/gnome-terminal'; - doesn't work, some fork+exec stuff confusing us.
1088 sTerm = '/usr/bin/xterm';
1089 if not os.path.isfile(sTerm): sTerm = '/usr/X11/bin/xterm';
1090 if not os.path.isfile(sTerm): sTerm = '/usr/X11R6/bin/xterm';
1091 if not os.path.isfile(sTerm): sTerm = '/usr/bin/xterm';
1092 if not os.path.isfile(sTerm): sTerm = 'xterm';
1093 sGdb = '/usr/bin/gdb';
1094 if not os.path.isfile(sGdb): sGdb = '/usr/local/bin/gdb';
1095 if not os.path.isfile(sGdb): sGdb = '/usr/sfw/bin/gdb';
1096 if not os.path.isfile(sGdb): sGdb = 'gdb';
1097 sGdbCmdLine = '%s --args %s --pidfile %s' % (sGdb, sVBoxSVC, self.sVBoxSvcPidFile);
1098 reporter.log('term="%s" gdb="%s"' % (sTerm, sGdbCmdLine));
1099 os.environ['SHELL'] = self.sOrgShell; # Non-working shell may cause gdb and/or the term problems.
1100 ## @todo -e is deprecated; use "-- <args>".
1101 self.oVBoxSvcProcess = base.Process.spawnp(sTerm, sTerm, '-e', sGdbCmdLine);
1102 os.environ['SHELL'] = self.sOurShell;
1103 if self.oVBoxSvcProcess is not None:
1104 reporter.log('Press enter or return after starting VBoxSVC in the debugger...');
1105 sys.stdin.read(1);
1106 fWritePidFile = False;
1107
1108 elif self.sHost == 'win':
1109 sWinDbg = 'c:\\Program Files\\Debugging Tools for Windows\\windbg.exe';
1110 if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Program Files\\Debugging Tools for Windows (x64)\\windbg.exe';
1111 if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Programme\\Debugging Tools for Windows\\windbg.exe'; # Localization rulez! pylint: disable=line-too-long
1112 if not os.path.isfile(sWinDbg): sWinDbg = 'c:\\Programme\\Debugging Tools for Windows (x64)\\windbg.exe';
1113 if not os.path.isfile(sWinDbg): sWinDbg = 'windbg'; # WinDbg must be in the path; better than nothing.
1114 # Assume that everything WinDbg needs is defined using the environment variables.
1115 # See WinDbg help for more information.
1116 reporter.log('windbg="%s"' % (sWinDbg));
1117 self.oVBoxSvcProcess = base.Process.spawn(sWinDbg, sWinDbg, sVBoxSVC + base.exeSuff());
1118 if self.oVBoxSvcProcess is not None:
1119 reporter.log('Press enter or return after starting VBoxSVC in the debugger...');
1120 sys.stdin.read(1);
1121 fWritePidFile = False;
1122 ## @todo add a pipe interface similar to xpcom if feasible, i.e. if
1123 # we can get actual handle values for pipes in python.
1124
1125 else:
1126 reporter.error('Port me!');
1127 else: # Run without a debugger attached.
1128 if self.sHost in ('darwin', 'freebsd', 'linux', 'solaris', ):
1129 #
1130 # XPCOM - We can use a pipe to let VBoxSVC notify us when it's ready.
1131 #
1132 iPipeR, iPipeW = os.pipe();
1133 if hasattr(os, 'set_inheritable'):
1134 os.set_inheritable(iPipeW, True); # pylint: disable=no-member
1135 os.environ['NSPR_INHERIT_FDS'] = 'vboxsvc:startup-pipe:5:0x%x' % (iPipeW,);
1136 reporter.log2("NSPR_INHERIT_FDS=%s" % (os.environ['NSPR_INHERIT_FDS']));
1137
1138 self.oVBoxSvcProcess = base.Process.spawn(sVBoxSVC, sVBoxSVC, '--auto-shutdown'); # SIGUSR1 requirement.
1139 try: # Try make sure we get the SIGINT and not VBoxSVC.
1140 os.setpgid(self.oVBoxSvcProcess.getPid(), 0); # pylint: disable=no-member
1141 os.setpgid(0, 0); # pylint: disable=no-member
1142 except:
1143 reporter.logXcpt();
1144
1145 os.close(iPipeW);
1146 try:
1147 sResponse = os.read(iPipeR, 32);
1148 except:
1149 reporter.logXcpt();
1150 sResponse = None;
1151 os.close(iPipeR);
1152
1153 if hasattr(sResponse, 'decode'):
1154 sResponse = sResponse.decode('utf-8', 'ignore');
1155
1156 if sResponse is None or sResponse.strip() != 'READY':
1157 reporter.error('VBoxSVC failed starting up... (sResponse=%s)' % (sResponse,));
1158 if not self.oVBoxSvcProcess.wait(5000):
1159 self.oVBoxSvcProcess.terminate();
1160 self.oVBoxSvcProcess.wait(5000);
1161 self.oVBoxSvcProcess = None;
1162
1163 elif self.sHost == 'win':
1164 #
1165 # Windows - Just fudge it for now.
1166 #
1167 cMsFudge = 2000;
1168 self.oVBoxSvcProcess = base.Process.spawn(sVBoxSVC, sVBoxSVC);
1169
1170 else:
1171 reporter.error('Port me!');
1172
1173 #
1174 # Enable automatic crash reporting if we succeeded.
1175 #
1176 if self.oVBoxSvcProcess is not None:
1177 self.oVBoxSvcProcess.enableCrashReporting('crash/report/svc', 'crash/dump/svc');
1178
1179 #
1180 # Fudge and pid file.
1181 #
1182 if self.oVBoxSvcProcess is not None and not self.oVBoxSvcProcess.wait(cMsFudge):
1183 if fWritePidFile:
1184 iPid = self.oVBoxSvcProcess.getPid();
1185 try:
1186 oFile = utils.openNoInherit(self.sVBoxSvcPidFile, "w+");
1187 oFile.write('%s' % (iPid,));
1188 oFile.close();
1189 except:
1190 reporter.logXcpt('sPidFile=%s' % (self.sVBoxSvcPidFile,));
1191 reporter.log('VBoxSVC PID=%u' % (iPid,));
1192
1193 #
1194 # Finally add the task so we'll notice when it dies in a relatively timely manner.
1195 #
1196 self.addTask(self.oVBoxSvcProcess);
1197 else:
1198 self.oVBoxSvcProcess = None;
1199 try: os.remove(self.sVBoxSvcPidFile);
1200 except: pass;
1201
1202 return self.oVBoxSvcProcess is not None;
1203
1204
1205 def _killVBoxSVCByPidFile(self, sPidFile):
1206 """ Kill a VBoxSVC given the pid from it's pid file. """
1207
1208 # Read the pid file.
1209 if not os.path.isfile(sPidFile):
1210 return False;
1211 try:
1212 oFile = utils.openNoInherit(sPidFile, "r");
1213 sPid = oFile.readline().strip();
1214 oFile.close();
1215 except:
1216 reporter.logXcpt('sPidfile=%s' % (sPidFile,));
1217 return False;
1218
1219 # Convert the pid to an integer and validate the range a little bit.
1220 try:
1221 iPid = long(sPid);
1222 except:
1223 reporter.logXcpt('sPidfile=%s sPid="%s"' % (sPidFile, sPid));
1224 return False;
1225 if iPid <= 0:
1226 reporter.log('negative pid - sPidfile=%s sPid="%s" iPid=%d' % (sPidFile, sPid, iPid));
1227 return False;
1228
1229 # Take care checking that it's VBoxSVC we're about to inhume.
1230 if base.processCheckPidAndName(iPid, "VBoxSVC") is not True:
1231 reporter.log('Ignoring stale VBoxSVC pid file (pid=%s)' % (iPid,));
1232 return False;
1233
1234 # Loop thru our different ways of getting VBoxSVC to terminate.
1235 for aHow in [ [ base.sendUserSignal1, 5000, 'Dropping VBoxSVC a SIGUSR1 hint...'], \
1236 [ base.processInterrupt, 5000, 'Dropping VBoxSVC a SIGINT hint...'], \
1237 [ base.processTerminate, 7500, 'VBoxSVC is still around, killing it...'] ]:
1238 reporter.log(aHow[2]);
1239 if aHow[0](iPid) is True:
1240 msStart = base.timestampMilli();
1241 while base.timestampMilli() - msStart < 5000 \
1242 and base.processExists(iPid):
1243 time.sleep(0.2);
1244
1245 fRc = not base.processExists(iPid);
1246 if fRc is True:
1247 break;
1248 if fRc:
1249 reporter.log('Successfully killed VBoxSVC (pid=%s)' % (iPid,));
1250 else:
1251 reporter.log('Failed to kill VBoxSVC (pid=%s)' % (iPid,));
1252 return fRc;
1253
1254 def _stopVBoxSVC(self):
1255 """
1256 Stops VBoxSVC. Try the polite way first.
1257 """
1258
1259 if self.oVBoxSvcProcess:
1260 self.removeTask(self.oVBoxSvcProcess);
1261 self.oVBoxSvcProcess.enableCrashReporting(None, None); # Disables it.
1262
1263 fRc = False;
1264 if self.oVBoxSvcProcess is not None \
1265 and not self.fVBoxSvcInDebugger:
1266 # by process object.
1267 if self.oVBoxSvcProcess.isRunning():
1268 reporter.log('Dropping VBoxSVC a SIGUSR1 hint...');
1269 if not self.oVBoxSvcProcess.sendUserSignal1() \
1270 or not self.oVBoxSvcProcess.wait(5000):
1271 reporter.log('Dropping VBoxSVC a SIGINT hint...');
1272 if not self.oVBoxSvcProcess.interrupt() \
1273 or not self.oVBoxSvcProcess.wait(5000):
1274 reporter.log('VBoxSVC is still around, killing it...');
1275 self.oVBoxSvcProcess.terminate();
1276 self.oVBoxSvcProcess.wait(7500);
1277 else:
1278 reporter.log('VBoxSVC is no longer running...');
1279 if not self.oVBoxSvcProcess.isRunning():
1280 self.oVBoxSvcProcess = None;
1281 else:
1282 # by pid file.
1283 self._killVBoxSVCByPidFile('%s/VBoxSVC.pid' % (self.sScratchPath,));
1284 return fRc;
1285
1286 def _setupVBoxApi(self):
1287 """
1288 Import and set up the vboxapi.
1289 The caller saves and restores sys.path.
1290 """
1291
1292 # Setup vbox logging for self (the test driver).
1293 self.sSelfLogFile = '%s/VBoxTestDriver.log' % (self.sScratchPath,);
1294 try: os.remove(self.sSelfLogFile);
1295 except: pass;
1296 os.environ['VBOX_LOG'] = self.sLogSelfGroups;
1297 os.environ['VBOX_LOG_FLAGS'] = '%s append' % (self.sLogSelfFlags, );
1298 if self.sLogSelfDest:
1299 os.environ['VBOX_LOG_DEST'] = 'nodeny ' + self.sLogSelfDest;
1300 else:
1301 os.environ['VBOX_LOG_DEST'] = 'nodeny file=%s' % (self.sSelfLogFile,);
1302 os.environ['VBOX_RELEASE_LOG_FLAGS'] = 'time append';
1303
1304 # Hack the sys.path + environment so the vboxapi can be found.
1305 sys.path.insert(0, self.oBuild.sInstallPath);
1306 if self.oBuild.sSdkPath is not None:
1307 sys.path.insert(0, os.path.join(self.oBuild.sSdkPath, 'installer'))
1308 sys.path.insert(1, os.path.join(self.oBuild.sSdkPath, 'install')); # stupid stupid windows installer!
1309 sys.path.insert(2, os.path.join(self.oBuild.sSdkPath, 'bindings', 'xpcom', 'python'))
1310 os.environ['VBOX_PROGRAM_PATH'] = self.oBuild.sInstallPath;
1311 reporter.log("sys.path: %s" % (sys.path));
1312
1313 try:
1314 from vboxapi import VirtualBoxManager; # pylint: disable=import-error
1315 except:
1316 reporter.logXcpt('Error importing vboxapi');
1317 return False;
1318
1319 # Exception and error hacks.
1320 try:
1321 # pylint: disable=import-error
1322 if self.sHost == 'win':
1323 from pythoncom import com_error as NativeComExceptionClass # pylint: disable=no-name-in-module
1324 import winerror as NativeComErrorClass
1325 else:
1326 from xpcom import Exception as NativeComExceptionClass
1327 from xpcom import nsError as NativeComErrorClass
1328 # pylint: enable=import-error
1329 except:
1330 reporter.logXcpt('Error importing (XP)COM related stuff for exception hacks and errors');
1331 return False;
1332 __deployExceptionHacks__(NativeComExceptionClass)
1333 ComError.copyErrors(NativeComErrorClass);
1334
1335 # Create the manager.
1336 try:
1337 self.oVBoxMgr = VirtualBoxManager(None, None)
1338 except:
1339 self.oVBoxMgr = None;
1340 reporter.logXcpt('VirtualBoxManager exception');
1341 return False;
1342
1343 # Figure the API version.
1344 try:
1345 oVBox = self.oVBoxMgr.getVirtualBox();
1346
1347 try:
1348 sVer = oVBox.version;
1349 except:
1350 reporter.logXcpt('Failed to get VirtualBox version, assuming 4.0.0');
1351 sVer = "4.0.0";
1352 reporter.log("IVirtualBox.version=%s" % (sVer,));
1353
1354 # Convert the string to three integer values and check ranges.
1355 asVerComponents = sVer.split('.');
1356 try:
1357 sLast = asVerComponents[2].split('_')[0].split('r')[0];
1358 aiVerComponents = (int(asVerComponents[0]), int(asVerComponents[1]), int(sLast));
1359 except:
1360 raise base.GenError('Malformed version "%s"' % (sVer,));
1361 if aiVerComponents[0] < 3 or aiVerComponents[0] > 19:
1362 raise base.GenError('Malformed version "%s" - 1st component is out of bounds 3..19: %u'
1363 % (sVer, aiVerComponents[0]));
1364 if aiVerComponents[1] < 0 or aiVerComponents[1] > 9:
1365 raise base.GenError('Malformed version "%s" - 2nd component is out of bounds 0..9: %u'
1366 % (sVer, aiVerComponents[1]));
1367 if aiVerComponents[2] < 0 or aiVerComponents[2] > 99:
1368 raise base.GenError('Malformed version "%s" - 3rd component is out of bounds 0..99: %u'
1369 % (sVer, aiVerComponents[2]));
1370
1371 # Convert the three integers into a floating point value. The API is table witin a
1372 # x.y release, so the third component only indicates whether it's a stable or
1373 # development build of the next release.
1374 self.fpApiVer = aiVerComponents[0] + 0.1 * aiVerComponents[1];
1375 if aiVerComponents[2] >= 51:
1376 if self.fpApiVer not in [4.3, 3.2,]:
1377 self.fpApiVer += 0.1;
1378 else:
1379 self.fpApiVer += 1.1;
1380
1381 try:
1382 self.uRevision = oVBox.revision;
1383 except:
1384 reporter.logXcpt('Failed to get VirtualBox revision, assuming 0');
1385 self.uRevision = 0;
1386 reporter.log("IVirtualBox.revision=%u" % (self.uRevision,));
1387
1388 try:
1389 self.uApiRevision = oVBox.APIRevision;
1390 except:
1391 reporter.logXcpt('Failed to get VirtualBox APIRevision, faking it.');
1392 self.uApiRevision = self.makeApiRevision(aiVerComponents[0], aiVerComponents[1], aiVerComponents[2], 0);
1393 reporter.log("IVirtualBox.APIRevision=%#x" % (self.uApiRevision,));
1394
1395 # Patch VBox manage to gloss over portability issues (error constants, etc).
1396 self._patchVBoxMgr();
1397
1398 # Wrap oVBox.
1399 from testdriver.vboxwrappers import VirtualBoxWrapper;
1400 self.oVBox = VirtualBoxWrapper(oVBox, self.oVBoxMgr, self.fpApiVer, self);
1401
1402 # Install the constant wrapping hack.
1403 vboxcon.goHackModuleClass.oVBoxMgr = self.oVBoxMgr; # VBoxConstantWrappingHack.
1404 vboxcon.fpApiVer = self.fpApiVer;
1405 reporter.setComXcptFormatter(formatComOrXpComException);
1406
1407 except:
1408 self.oVBoxMgr = None;
1409 self.oVBox = None;
1410 reporter.logXcpt("getVirtualBox / API version exception");
1411 return False;
1412
1413 # Done
1414 self.fImportedVBoxApi = True;
1415 reporter.log('Found version %s (%s)' % (self.fpApiVer, sVer));
1416 return True;
1417
1418 def _patchVBoxMgr(self):
1419 """
1420 Glosses over missing self.oVBoxMgr methods on older VBox versions.
1421 """
1422
1423 def _xcptGetResult(oSelf, oXcpt = None):
1424 """ See vboxapi. """
1425 _ = oSelf;
1426 if oXcpt is None: oXcpt = sys.exc_info()[1];
1427 if sys.platform == 'win32':
1428 import winerror; # pylint: disable=import-error
1429 hrXcpt = oXcpt.hresult;
1430 if hrXcpt == winerror.DISP_E_EXCEPTION:
1431 hrXcpt = oXcpt.excepinfo[5];
1432 else:
1433 hrXcpt = oXcpt.error;
1434 return hrXcpt;
1435
1436 def _xcptIsDeadInterface(oSelf, oXcpt = None):
1437 """ See vboxapi. """
1438 return oSelf.xcptGetStatus(oXcpt) in [
1439 0x80004004, -2147467260, # NS_ERROR_ABORT
1440 0x800706be, -2147023170, # NS_ERROR_CALL_FAILED (RPC_S_CALL_FAILED)
1441 0x800706ba, -2147023174, # RPC_S_SERVER_UNAVAILABLE.
1442 0x800706be, -2147023170, # RPC_S_CALL_FAILED.
1443 0x800706bf, -2147023169, # RPC_S_CALL_FAILED_DNE.
1444 0x80010108, -2147417848, # RPC_E_DISCONNECTED.
1445 0x800706b5, -2147023179, # RPC_S_UNKNOWN_IF
1446 ];
1447
1448 def _xcptIsOurXcptKind(oSelf, oXcpt = None):
1449 """ See vboxapi. """
1450 _ = oSelf;
1451 if oXcpt is None: oXcpt = sys.exc_info()[1];
1452 if sys.platform == 'win32':
1453 from pythoncom import com_error as NativeComExceptionClass # pylint: disable=import-error,no-name-in-module
1454 else:
1455 from xpcom import Exception as NativeComExceptionClass # pylint: disable=import-error
1456 return isinstance(oXcpt, NativeComExceptionClass);
1457
1458 def _xcptIsEqual(oSelf, oXcpt, hrStatus):
1459 """ See vboxapi. """
1460 hrXcpt = oSelf.xcptGetResult(oXcpt);
1461 return hrXcpt == hrStatus or hrXcpt == hrStatus - 0x100000000; # pylint: disable=consider-using-in
1462
1463 def _xcptToString(oSelf, oXcpt):
1464 """ See vboxapi. """
1465 _ = oSelf;
1466 if oXcpt is None: oXcpt = sys.exc_info()[1];
1467 return str(oXcpt);
1468
1469 def _getEnumValueName(oSelf, sEnumTypeNm, oEnumValue, fTypePrefix = False):
1470 """ See vboxapi. """
1471 _ = oSelf; _ = fTypePrefix;
1472 return '%s::%s' % (sEnumTypeNm, oEnumValue);
1473
1474 # Add utilities found in newer vboxapi revision.
1475 if not hasattr(self.oVBoxMgr, 'xcptIsDeadInterface'):
1476 import types;
1477 self.oVBoxMgr.xcptGetResult = types.MethodType(_xcptGetResult, self.oVBoxMgr);
1478 self.oVBoxMgr.xcptIsDeadInterface = types.MethodType(_xcptIsDeadInterface, self.oVBoxMgr);
1479 self.oVBoxMgr.xcptIsOurXcptKind = types.MethodType(_xcptIsOurXcptKind, self.oVBoxMgr);
1480 self.oVBoxMgr.xcptIsEqual = types.MethodType(_xcptIsEqual, self.oVBoxMgr);
1481 self.oVBoxMgr.xcptToString = types.MethodType(_xcptToString, self.oVBoxMgr);
1482 if not hasattr(self.oVBoxMgr, 'getEnumValueName'):
1483 import types;
1484 self.oVBoxMgr.getEnumValueName = types.MethodType(_getEnumValueName, self.oVBoxMgr);
1485
1486
1487 def _teardownVBoxApi(self): # pylint: disable=too-many-statements
1488 """
1489 Drop all VBox object references and shutdown com/xpcom.
1490 """
1491 if not self.fImportedVBoxApi:
1492 return True;
1493 import gc;
1494
1495 # Drop all references we've have to COM objects.
1496 self.aoRemoteSessions = [];
1497 self.aoVMs = [];
1498 self.oVBoxMgr = None;
1499 self.oVBox = None;
1500 vboxcon.goHackModuleClass.oVBoxMgr = None; # VBoxConstantWrappingHack.
1501 reporter.setComXcptFormatter(None);
1502
1503 # Do garbage collection to try get rid of those objects.
1504 try:
1505 gc.collect();
1506 except:
1507 reporter.logXcpt();
1508 self.fImportedVBoxApi = False;
1509
1510 # Check whether the python is still having any COM objects/interfaces around.
1511 cVBoxMgrs = 0;
1512 aoObjsLeftBehind = [];
1513 if self.sHost == 'win':
1514 import pythoncom; # pylint: disable=import-error
1515 try:
1516 cIfs = pythoncom._GetInterfaceCount(); # pylint: disable=no-member,protected-access
1517 cObjs = pythoncom._GetGatewayCount(); # pylint: disable=no-member,protected-access
1518 if cObjs == 0 and cIfs == 0:
1519 reporter.log('_teardownVBoxApi: no interfaces or objects left behind.');
1520 else:
1521 reporter.log('_teardownVBoxApi: Python COM still has %s objects and %s interfaces...' % ( cObjs, cIfs));
1522
1523 from win32com.client import DispatchBaseClass; # pylint: disable=import-error
1524 for oObj in gc.get_objects():
1525 if isinstance(oObj, DispatchBaseClass):
1526 reporter.log('_teardownVBoxApi: %s' % (oObj,));
1527 aoObjsLeftBehind.append(oObj);
1528 elif utils.getObjectTypeName(oObj) == 'VirtualBoxManager':
1529 reporter.log('_teardownVBoxApi: %s' % (oObj,));
1530 cVBoxMgrs += 1;
1531 aoObjsLeftBehind.append(oObj);
1532 oObj = None;
1533 except:
1534 reporter.logXcpt();
1535
1536 # If not being used, we can safely uninitialize COM.
1537 if cIfs == 0 and cObjs == 0 and cVBoxMgrs == 0 and not aoObjsLeftBehind:
1538 reporter.log('_teardownVBoxApi: Calling CoUninitialize...');
1539 try: pythoncom.CoUninitialize(); # pylint: disable=no-member
1540 except: reporter.logXcpt();
1541 else:
1542 reporter.log('_teardownVBoxApi: Returned from CoUninitialize.');
1543 else:
1544 try:
1545 # XPCOM doesn't crash and burn like COM if you shut it down with interfaces and objects around.
1546 # Also, it keeps a number of internal objects and interfaces around to do its job, so shutting
1547 # it down before we go looking for dangling interfaces is more or less required.
1548 from xpcom import _xpcom as _xpcom; # pylint: disable=import-error,useless-import-alias
1549 hrc = _xpcom.DeinitCOM();
1550 cIfs = _xpcom._GetInterfaceCount(); # pylint: disable=protected-access
1551 cObjs = _xpcom._GetGatewayCount(); # pylint: disable=protected-access
1552
1553 if cObjs == 0 and cIfs == 0:
1554 reporter.log('_teardownVBoxApi: No XPCOM interfaces or objects active. (hrc=%#x)' % (hrc,));
1555 else:
1556 reporter.log('_teardownVBoxApi: %s XPCOM objects and %s interfaces still around! (hrc=%#x)'
1557 % (cObjs, cIfs, hrc));
1558 if hasattr(_xpcom, '_DumpInterfaces'):
1559 try: _xpcom._DumpInterfaces(); # pylint: disable=protected-access
1560 except: reporter.logXcpt('_teardownVBoxApi: _DumpInterfaces failed');
1561
1562 from xpcom.client import Component; # pylint: disable=import-error
1563 for oObj in gc.get_objects():
1564 if isinstance(oObj, Component):
1565 reporter.log('_teardownVBoxApi: %s' % (oObj,));
1566 aoObjsLeftBehind.append(oObj);
1567 if utils.getObjectTypeName(oObj) == 'VirtualBoxManager':
1568 reporter.log('_teardownVBoxApi: %s' % (oObj,));
1569 cVBoxMgrs += 1;
1570 aoObjsLeftBehind.append(oObj);
1571 oObj = None;
1572 except:
1573 reporter.logXcpt();
1574
1575 # Try get the referrers to (XP)COM interfaces and objects that was left behind.
1576 for iObj in range(len(aoObjsLeftBehind)): # pylint: disable=consider-using-enumerate
1577 try:
1578 aoReferrers = gc.get_referrers(aoObjsLeftBehind[iObj]);
1579 reporter.log('_teardownVBoxApi: Found %u referrers to %s:' % (len(aoReferrers), aoObjsLeftBehind[iObj],));
1580 for oReferrer in aoReferrers:
1581 oMyFrame = sys._getframe(0); # pylint: disable=protected-access
1582 if oReferrer is oMyFrame:
1583 reporter.log('_teardownVBoxApi: - frame of this function');
1584 elif oReferrer is aoObjsLeftBehind:
1585 reporter.log('_teardownVBoxApi: - aoObjsLeftBehind');
1586 else:
1587 fPrinted = False;
1588 if isinstance(oReferrer, (dict, list, tuple)):
1589 try:
1590 aoSubReferreres = gc.get_referrers(oReferrer);
1591 for oSubRef in aoSubReferreres:
1592 if not isinstance(oSubRef, list) \
1593 and not isinstance(oSubRef, dict) \
1594 and oSubRef is not oMyFrame \
1595 and oSubRef is not aoSubReferreres:
1596 reporter.log('_teardownVBoxApi: - %s :: %s:'
1597 % (utils.getObjectTypeName(oSubRef), utils.getObjectTypeName(oReferrer)));
1598 fPrinted = True;
1599 break;
1600 del aoSubReferreres;
1601 except:
1602 reporter.logXcpt('subref');
1603 if not fPrinted:
1604 reporter.log('_teardownVBoxApi: - %s:' % (utils.getObjectTypeName(oReferrer),));
1605 try:
1606 import pprint;
1607 for sLine in pprint.pformat(oReferrer, width = 130).split('\n'):
1608 reporter.log('_teardownVBoxApi: %s' % (sLine,));
1609 except:
1610 reporter.log('_teardownVBoxApi: %s' % (oReferrer,));
1611 except:
1612 reporter.logXcpt();
1613 del aoObjsLeftBehind;
1614
1615 # Force garbage collection again, just for good measure.
1616 try:
1617 gc.collect();
1618 time.sleep(0.5); # fudge factor
1619 except:
1620 reporter.logXcpt();
1621 return True;
1622
1623 def _powerOffAllVms(self):
1624 """
1625 Tries to power off all running VMs.
1626 """
1627 for oSession in self.aoRemoteSessions:
1628 uPid = oSession.getPid();
1629 if uPid is not None:
1630 reporter.log('_powerOffAllVms: PID is %s for %s, trying to kill it.' % (uPid, oSession.sName,));
1631 base.processKill(uPid);
1632 else:
1633 reporter.log('_powerOffAllVms: No PID for %s' % (oSession.sName,));
1634 oSession.close();
1635 return None;
1636
1637
1638
1639 #
1640 # Build type, OS and arch getters.
1641 #
1642
1643 def getBuildType(self):
1644 """
1645 Get the build type.
1646 """
1647 if not self._detectBuild():
1648 return 'release';
1649 return self.oBuild.sType;
1650
1651 def getBuildOs(self):
1652 """
1653 Get the build OS.
1654 """
1655 if not self._detectBuild():
1656 return self.sHost;
1657 return self.oBuild.sOs;
1658
1659 def getBuildArch(self):
1660 """
1661 Get the build arch.
1662 """
1663 if not self._detectBuild():
1664 return self.sHostArch;
1665 return self.oBuild.sArch;
1666
1667 def getGuestAdditionsIso(self):
1668 """
1669 Get the path to the guest addition iso.
1670 """
1671 if not self._detectBuild():
1672 return None;
1673 return self.oBuild.sGuestAdditionsIso;
1674
1675 #
1676 # Override everything from the base class so the testdrivers don't have to
1677 # check whether we have overridden a method or not.
1678 #
1679
1680 def showUsage(self):
1681 rc = base.TestDriver.showUsage(self);
1682 reporter.log('');
1683 reporter.log('Generic VirtualBox Options:');
1684 reporter.log(' --vbox-session-type <type>');
1685 reporter.log(' Sets the session type. Typical values are: gui, headless, sdl');
1686 reporter.log(' Default: %s' % (self.sSessionTypeDef));
1687 reporter.log(' --vrdp, --no-vrdp');
1688 reporter.log(' Enables VRDP, ports starting at 6000');
1689 reporter.log(' Default: --vrdp');
1690 reporter.log(' --vrdp-base-port <port>');
1691 reporter.log(' Sets the base for VRDP port assignments.');
1692 reporter.log(' Default: %s' % (self.uVrdpBasePortDef));
1693 reporter.log(' --vbox-default-bridged-nic <interface>');
1694 reporter.log(' Sets the default interface for bridged networking.');
1695 reporter.log(' Default: autodetect');
1696 reporter.log(' --vbox-use-svc-defaults');
1697 reporter.log(' Use default locations and files for VBoxSVC. This is useful');
1698 reporter.log(' for automatically configuring the test VMs for debugging.');
1699 reporter.log(' --vbox-self-log');
1700 reporter.log(' The VBox logger group settings for the testdriver.');
1701 reporter.log(' --vbox-self-log-flags');
1702 reporter.log(' The VBox logger flags settings for the testdriver.');
1703 reporter.log(' --vbox-self-log-dest');
1704 reporter.log(' The VBox logger destination settings for the testdriver.');
1705 reporter.log(' --vbox-session-log');
1706 reporter.log(' The VM session logger group settings.');
1707 reporter.log(' --vbox-session-log-flags');
1708 reporter.log(' The VM session logger flags.');
1709 reporter.log(' --vbox-session-log-dest');
1710 reporter.log(' The VM session logger destination settings.');
1711 reporter.log(' --vbox-svc-log');
1712 reporter.log(' The VBoxSVC logger group settings.');
1713 reporter.log(' --vbox-svc-log-flags');
1714 reporter.log(' The VBoxSVC logger flag settings.');
1715 reporter.log(' --vbox-svc-log-dest');
1716 reporter.log(' The VBoxSVC logger destination settings.');
1717 reporter.log(' --vbox-log');
1718 reporter.log(' The VBox logger group settings for everyone.');
1719 reporter.log(' --vbox-log-flags');
1720 reporter.log(' The VBox logger flags settings for everyone.');
1721 reporter.log(' --vbox-log-dest');
1722 reporter.log(' The VBox logger destination settings for everyone.');
1723 reporter.log(' --vbox-svc-debug');
1724 reporter.log(' Start VBoxSVC in a debugger');
1725 reporter.log(' --vbox-always-upload-logs');
1726 reporter.log(' Whether to always upload log files, or only do so on failure.');
1727 reporter.log(' --vbox-always-upload-screenshots');
1728 reporter.log(' Whether to always upload final screen shots, or only do so on failure.');
1729 reporter.log(' --vbox-debugger, --no-vbox-debugger');
1730 reporter.log(' Enables the VBox debugger, port at 5000');
1731 reporter.log(' Default: --vbox-debugger');
1732 if self.oTestVmSet is not None:
1733 self.oTestVmSet.showUsage();
1734 return rc;
1735
1736 def parseOption(self, asArgs, iArg): # pylint: disable=too-many-statements
1737 if asArgs[iArg] == '--vbox-session-type':
1738 iArg += 1;
1739 if iArg >= len(asArgs):
1740 raise base.InvalidOption('The "--vbox-session-type" takes an argument');
1741 self.sSessionType = asArgs[iArg];
1742 elif asArgs[iArg] == '--vrdp':
1743 self.fEnableVrdp = True;
1744 elif asArgs[iArg] == '--no-vrdp':
1745 self.fEnableVrdp = False;
1746 elif asArgs[iArg] == '--vrdp-base-port':
1747 iArg += 1;
1748 if iArg >= len(asArgs):
1749 raise base.InvalidOption('The "--vrdp-base-port" takes an argument');
1750 try: self.uVrdpBasePort = int(asArgs[iArg]);
1751 except: raise base.InvalidOption('The "--vrdp-base-port" value "%s" is not a valid integer' % (asArgs[iArg],));
1752 if self.uVrdpBasePort <= 0 or self.uVrdpBasePort >= 65530:
1753 raise base.InvalidOption('The "--vrdp-base-port" value "%s" is not in the valid range (1..65530)'
1754 % (asArgs[iArg],));
1755 elif asArgs[iArg] == '--vbox-default-bridged-nic':
1756 iArg += 1;
1757 if iArg >= len(asArgs):
1758 raise base.InvalidOption('The "--vbox-default-bridged-nic" takes an argument');
1759 self.sDefBridgedNic = asArgs[iArg];
1760 elif asArgs[iArg] == '--vbox-use-svc-defaults':
1761 self.fUseDefaultSvc = True;
1762 elif asArgs[iArg] == '--vbox-self-log':
1763 iArg += 1;
1764 if iArg >= len(asArgs):
1765 raise base.InvalidOption('The "--vbox-self-log" takes an argument');
1766 self.sLogSelfGroups = asArgs[iArg];
1767 elif asArgs[iArg] == '--vbox-self-log-flags':
1768 iArg += 1;
1769 if iArg >= len(asArgs):
1770 raise base.InvalidOption('The "--vbox-self-log-flags" takes an argument');
1771 self.sLogSelfFlags = asArgs[iArg];
1772 elif asArgs[iArg] == '--vbox-self-log-dest':
1773 iArg += 1;
1774 if iArg >= len(asArgs):
1775 raise base.InvalidOption('The "--vbox-self-log-dest" takes an argument');
1776 self.sLogSelfDest = asArgs[iArg];
1777 elif asArgs[iArg] == '--vbox-session-log':
1778 iArg += 1;
1779 if iArg >= len(asArgs):
1780 raise base.InvalidOption('The "--vbox-session-log" takes an argument');
1781 self.sLogSessionGroups = asArgs[iArg];
1782 elif asArgs[iArg] == '--vbox-session-log-flags':
1783 iArg += 1;
1784 if iArg >= len(asArgs):
1785 raise base.InvalidOption('The "--vbox-session-log-flags" takes an argument');
1786 self.sLogSessionFlags = asArgs[iArg];
1787 elif asArgs[iArg] == '--vbox-session-log-dest':
1788 iArg += 1;
1789 if iArg >= len(asArgs):
1790 raise base.InvalidOption('The "--vbox-session-log-dest" takes an argument');
1791 self.sLogSessionDest = asArgs[iArg];
1792 elif asArgs[iArg] == '--vbox-svc-log':
1793 iArg += 1;
1794 if iArg >= len(asArgs):
1795 raise base.InvalidOption('The "--vbox-svc-log" takes an argument');
1796 self.sLogSvcGroups = asArgs[iArg];
1797 elif asArgs[iArg] == '--vbox-svc-log-flags':
1798 iArg += 1;
1799 if iArg >= len(asArgs):
1800 raise base.InvalidOption('The "--vbox-svc-log-flags" takes an argument');
1801 self.sLogSvcFlags = asArgs[iArg];
1802 elif asArgs[iArg] == '--vbox-svc-log-dest':
1803 iArg += 1;
1804 if iArg >= len(asArgs):
1805 raise base.InvalidOption('The "--vbox-svc-log-dest" takes an argument');
1806 self.sLogSvcDest = asArgs[iArg];
1807 elif asArgs[iArg] == '--vbox-log':
1808 iArg += 1;
1809 if iArg >= len(asArgs):
1810 raise base.InvalidOption('The "--vbox-log" takes an argument');
1811 self.sLogSelfGroups = asArgs[iArg];
1812 self.sLogSessionGroups = asArgs[iArg];
1813 self.sLogSvcGroups = asArgs[iArg];
1814 elif asArgs[iArg] == '--vbox-log-flags':
1815 iArg += 1;
1816 if iArg >= len(asArgs):
1817 raise base.InvalidOption('The "--vbox-svc-flags" takes an argument');
1818 self.sLogSelfFlags = asArgs[iArg];
1819 self.sLogSessionFlags = asArgs[iArg];
1820 self.sLogSvcFlags = asArgs[iArg];
1821 elif asArgs[iArg] == '--vbox-log-dest':
1822 iArg += 1;
1823 if iArg >= len(asArgs):
1824 raise base.InvalidOption('The "--vbox-log-dest" takes an argument');
1825 self.sLogSelfDest = asArgs[iArg];
1826 self.sLogSessionDest = asArgs[iArg];
1827 self.sLogSvcDest = asArgs[iArg];
1828 elif asArgs[iArg] == '--vbox-svc-debug':
1829 self.fVBoxSvcInDebugger = True;
1830 elif asArgs[iArg] == '--vbox-always-upload-logs':
1831 self.fAlwaysUploadLogs = True;
1832 elif asArgs[iArg] == '--vbox-always-upload-screenshots':
1833 self.fAlwaysUploadScreenshots = True;
1834 elif asArgs[iArg] == '--vbox-debugger':
1835 self.fEnableDebugger = True;
1836 elif asArgs[iArg] == '--no-vbox-debugger':
1837 self.fEnableDebugger = False;
1838 else:
1839 # Relevant for selecting VMs to test?
1840 if self.oTestVmSet is not None:
1841 iRc = self.oTestVmSet.parseOption(asArgs, iArg);
1842 if iRc != iArg:
1843 return iRc;
1844
1845 # Hand it to the base class.
1846 return base.TestDriver.parseOption(self, asArgs, iArg);
1847 return iArg + 1;
1848
1849 def completeOptions(self):
1850 return base.TestDriver.completeOptions(self);
1851
1852 def getResourceSet(self):
1853 asRsrcs = [];
1854 if self.oTestVmSet is not None:
1855 asRsrcs.extend(self.oTestVmSet.getResourceSet());
1856 asRsrcs.extend(base.TestDriver.getResourceSet(self));
1857 return asRsrcs;
1858
1859 def actionExtract(self):
1860 return base.TestDriver.actionExtract(self);
1861
1862 def actionVerify(self):
1863 return base.TestDriver.actionVerify(self);
1864
1865 def actionConfig(self):
1866 return base.TestDriver.actionConfig(self);
1867
1868 def actionExecute(self):
1869 return base.TestDriver.actionExecute(self);
1870
1871 def actionCleanupBefore(self):
1872 """
1873 Kill any VBoxSVC left behind by a previous test run.
1874 """
1875 self._killVBoxSVCByPidFile('%s/VBoxSVC.pid' % (self.sScratchPath,));
1876 return base.TestDriver.actionCleanupBefore(self);
1877
1878 def actionCleanupAfter(self):
1879 """
1880 Clean up the VBox bits and then call the base driver.
1881
1882 If your test driver overrides this, it should normally call us at the
1883 end of the job.
1884 """
1885
1886 # Kill any left over VM processes.
1887 self._powerOffAllVms();
1888
1889 # Drop all VBox object references and shutdown xpcom then
1890 # terminating VBoxSVC, with extreme prejudice if need be.
1891 self._teardownVBoxApi();
1892 self._stopVBoxSVC();
1893
1894 # Add the VBoxSVC and testdriver debug+release log files.
1895 if self.fAlwaysUploadLogs or reporter.getErrorCount() > 0:
1896 if self.sVBoxSvcLogFile is not None and os.path.isfile(self.sVBoxSvcLogFile):
1897 reporter.addLogFile(self.sVBoxSvcLogFile, 'log/debug/svc', 'Debug log file for VBoxSVC');
1898 self.sVBoxSvcLogFile = None;
1899
1900 if self.sSelfLogFile is not None and os.path.isfile(self.sSelfLogFile):
1901 reporter.addLogFile(self.sSelfLogFile, 'log/debug/client', 'Debug log file for the test driver');
1902 self.sSelfLogFile = None;
1903
1904 sVBoxSvcRelLog = os.path.join(self.sScratchPath, 'VBoxUserHome', 'VBoxSVC.log');
1905 if os.path.isfile(sVBoxSvcRelLog):
1906 reporter.addLogFile(sVBoxSvcRelLog, 'log/release/svc', 'Release log file for VBoxSVC');
1907 for sSuff in [ '.1', '.2', '.3', '.4', '.5', '.6', '.7', '.8' ]:
1908 if os.path.isfile(sVBoxSvcRelLog + sSuff):
1909 reporter.addLogFile(sVBoxSvcRelLog + sSuff, 'log/release/svc', 'Release log file for VBoxSVC');
1910 # Testbox debugging - START - TEMPORARY, REMOVE ASAP.
1911 if self.sHost in ('darwin', 'freebsd', 'linux', 'solaris', ):
1912 try:
1913 reporter.log('> ls -R -la %s' % (self.sScratchPath,));
1914 utils.processCall(['ls', '-R', '-la', self.sScratchPath]);
1915 except: pass;
1916 # Testbox debugging - END - TEMPORARY, REMOVE ASAP.
1917
1918 # Finally, call the base driver to wipe the scratch space.
1919 return base.TestDriver.actionCleanupAfter(self);
1920
1921 def actionAbort(self):
1922 """
1923 Terminate VBoxSVC if we've got a pid file.
1924 """
1925 #
1926 # Take default action first, then kill VBoxSVC. The other way around
1927 # is problematic since the testscript would continue running and possibly
1928 # trigger a new VBoxSVC to start.
1929 #
1930 fRc1 = base.TestDriver.actionAbort(self);
1931 fRc2 = self._killVBoxSVCByPidFile('%s/VBoxSVC.pid' % (self.sScratchPath,));
1932 return fRc1 is True and fRc2 is True;
1933
1934 def onExit(self, iRc):
1935 """
1936 Stop VBoxSVC if we've started it.
1937 """
1938 if self.oVBoxSvcProcess is not None:
1939 reporter.log('*** Shutting down the VBox API... (iRc=%s)' % (iRc,));
1940 self._powerOffAllVms();
1941 self._teardownVBoxApi();
1942 self._stopVBoxSVC();
1943 reporter.log('*** VBox API shutdown done.');
1944 return base.TestDriver.onExit(self, iRc);
1945
1946
1947 #
1948 # Task wait method override.
1949 #
1950
1951 def notifyAboutReadyTask(self, oTask):
1952 """
1953 Overriding base.TestDriver.notifyAboutReadyTask.
1954 """
1955 try:
1956 self.oVBoxMgr.interruptWaitEvents();
1957 reporter.log2('vbox.notifyAboutReadyTask: called interruptWaitEvents');
1958 except:
1959 reporter.logXcpt('vbox.notifyAboutReadyTask');
1960 return base.TestDriver.notifyAboutReadyTask(self, oTask);
1961
1962 def waitForTasksSleepWorker(self, cMsTimeout):
1963 """
1964 Overriding base.TestDriver.waitForTasksSleepWorker.
1965 """
1966 try:
1967 rc = self.oVBoxMgr.waitForEvents(int(cMsTimeout));
1968 _ = rc; #reporter.log2('vbox.waitForTasksSleepWorker(%u): true (waitForEvents -> %s)' % (cMsTimeout, rc));
1969 reporter.doPollWork('vbox.TestDriver.waitForTasksSleepWorker');
1970 return True;
1971 except KeyboardInterrupt:
1972 raise;
1973 except:
1974 reporter.logXcpt('vbox.waitForTasksSleepWorker');
1975 return False;
1976
1977 #
1978 # Utility methods.
1979 #
1980
1981 def processEvents(self, cMsTimeout = 0):
1982 """
1983 Processes events, returning after the first batch has been processed
1984 or the time limit has been reached.
1985
1986 Only Ctrl-C exception, no return.
1987 """
1988 try:
1989 self.oVBoxMgr.waitForEvents(cMsTimeout);
1990 except KeyboardInterrupt:
1991 raise;
1992 except:
1993 pass;
1994 return None;
1995
1996 def processPendingEvents(self):
1997 """ processEvents(0) - no waiting. """
1998 return self.processEvents(0);
1999
2000 def sleep(self, cSecs):
2001 """
2002 Sleep for a specified amount of time, processing XPCOM events all the while.
2003 """
2004 cMsTimeout = long(cSecs * 1000);
2005 msStart = base.timestampMilli();
2006 self.processEvents(0);
2007 while True:
2008 cMsElapsed = base.timestampMilli() - msStart;
2009 if cMsElapsed > cMsTimeout:
2010 break;
2011 #reporter.log2('cMsTimeout=%s - cMsElapsed=%d => %s' % (cMsTimeout, cMsElapsed, cMsTimeout - cMsElapsed));
2012 self.processEvents(cMsTimeout - cMsElapsed);
2013 return None;
2014
2015 def _logVmInfoUnsafe(self, oVM): # pylint: disable=too-many-statements,too-many-branches
2016 """
2017 Internal worker for logVmInfo that is wrapped in try/except.
2018 """
2019 reporter.log(" Name: %s" % (oVM.name,));
2020 reporter.log(" ID: %s" % (oVM.id,));
2021 oOsType = self.oVBox.getGuestOSType(oVM.OSTypeId);
2022 reporter.log(" OS Type: %s - %s" % (oVM.OSTypeId, oOsType.description,));
2023 reporter.log(" Machine state: %s" % (oVM.state,));
2024 reporter.log(" Session state: %s" % (oVM.sessionState,));
2025 if self.fpApiVer >= 4.2:
2026 reporter.log(" Session PID: %u (%#x)" % (oVM.sessionPID, oVM.sessionPID,));
2027 else:
2028 reporter.log(" Session PID: %u (%#x)" % (oVM.sessionPid, oVM.sessionPid,));
2029 if self.fpApiVer >= 5.0:
2030 reporter.log(" Session Name: %s" % (oVM.sessionName,));
2031 else:
2032 reporter.log(" Session Name: %s" % (oVM.sessionType,));
2033 reporter.log(" CPUs: %s" % (oVM.CPUCount,));
2034 reporter.log(" RAM: %sMB" % (oVM.memorySize,));
2035 if self.fpApiVer >= 6.1 and hasattr(oVM, 'graphicsAdapter'):
2036 reporter.log(" VRAM: %sMB" % (oVM.graphicsAdapter.VRAMSize,));
2037 reporter.log(" Monitors: %s" % (oVM.graphicsAdapter.monitorCount,));
2038 reporter.log(" GraphicsController: %s"
2039 % (self.oVBoxMgr.getEnumValueName('GraphicsControllerType', # pylint: disable=not-callable
2040 oVM.graphicsAdapter.graphicsControllerType),));
2041 else:
2042 reporter.log(" VRAM: %sMB" % (oVM.VRAMSize,));
2043 reporter.log(" Monitors: %s" % (oVM.monitorCount,));
2044 reporter.log(" GraphicsController: %s"
2045 % (self.oVBoxMgr.getEnumValueName('GraphicsControllerType', oVM.graphicsControllerType),)); # pylint: disable=not-callable
2046 reporter.log(" Chipset: %s" % (self.oVBoxMgr.getEnumValueName('ChipsetType', oVM.chipsetType),)); # pylint: disable=not-callable
2047 reporter.log(" Firmware: %s" % (self.oVBoxMgr.getEnumValueName('FirmwareType', oVM.firmwareType),)); # pylint: disable=not-callable
2048 reporter.log(" HwVirtEx: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_Enabled),));
2049 reporter.log(" VPID support: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_VPID),));
2050 reporter.log(" Nested paging: %s" % (oVM.getHWVirtExProperty(vboxcon.HWVirtExPropertyType_NestedPaging),));
2051 atTypes = [
2052 ( 'CPUPropertyType_PAE', 'PAE: '),
2053 ( 'CPUPropertyType_LongMode', 'Long-mode: '),
2054 ( 'CPUPropertyType_HWVirt', 'Nested VT-x/AMD-V: '),
2055 ( 'CPUPropertyType_APIC', 'APIC: '),
2056 ( 'CPUPropertyType_X2APIC', 'X2APIC: '),
2057 ( 'CPUPropertyType_TripleFaultReset', 'TripleFaultReset: '),
2058 ( 'CPUPropertyType_IBPBOnVMExit', 'IBPBOnVMExit: '),
2059 ( 'CPUPropertyType_SpecCtrl', 'SpecCtrl: '),
2060 ( 'CPUPropertyType_SpecCtrlByHost', 'SpecCtrlByHost: '),
2061 ];
2062 for sEnumValue, sDesc in atTypes:
2063 if hasattr(vboxcon, sEnumValue):
2064 reporter.log(" %s%s" % (sDesc, oVM.getCPUProperty(getattr(vboxcon, sEnumValue)),));
2065 reporter.log(" ACPI: %s" % (oVM.BIOSSettings.ACPIEnabled,));
2066 reporter.log(" IO-APIC: %s" % (oVM.BIOSSettings.IOAPICEnabled,));
2067 if self.fpApiVer >= 3.2:
2068 if self.fpApiVer >= 4.2:
2069 reporter.log(" HPET: %s" % (oVM.HPETEnabled,));
2070 else:
2071 reporter.log(" HPET: %s" % (oVM.hpetEnabled,));
2072 if self.fpApiVer >= 6.1 and hasattr(oVM, 'graphicsAdapter'):
2073 reporter.log(" 3D acceleration: %s" % (oVM.graphicsAdapter.accelerate3DEnabled,));
2074 reporter.log(" 2D acceleration: %s" % (oVM.graphicsAdapter.accelerate2DVideoEnabled,));
2075 else:
2076 reporter.log(" 3D acceleration: %s" % (oVM.accelerate3DEnabled,));
2077 reporter.log(" 2D acceleration: %s" % (oVM.accelerate2DVideoEnabled,));
2078 reporter.log(" TeleporterEnabled: %s" % (oVM.teleporterEnabled,));
2079 reporter.log(" TeleporterPort: %s" % (oVM.teleporterPort,));
2080 reporter.log(" TeleporterAddress: %s" % (oVM.teleporterAddress,));
2081 reporter.log(" TeleporterPassword: %s" % (oVM.teleporterPassword,));
2082 reporter.log(" Clipboard mode: %s" % (oVM.clipboardMode,));
2083 if self.fpApiVer >= 5.0:
2084 reporter.log(" Drag and drop mode: %s" % (oVM.dnDMode,));
2085 elif self.fpApiVer >= 4.3:
2086 reporter.log(" Drag and drop mode: %s" % (oVM.dragAndDropMode,));
2087 if self.fpApiVer >= 4.0:
2088 reporter.log(" VRDP server: %s" % (oVM.VRDEServer.enabled,));
2089 try: sPorts = oVM.VRDEServer.getVRDEProperty("TCP/Ports");
2090 except: sPorts = "";
2091 reporter.log(" VRDP server ports: %s" % (sPorts,));
2092 reporter.log(" VRDP auth: %s (%s)" % (oVM.VRDEServer.authType, oVM.VRDEServer.authLibrary,));
2093 else:
2094 reporter.log(" VRDP server: %s" % (oVM.VRDPServer.enabled,));
2095 reporter.log(" VRDP server ports: %s" % (oVM.VRDPServer.ports,));
2096 reporter.log(" Last changed: %s" % (oVM.lastStateChange,));
2097
2098 aoControllers = self.oVBoxMgr.getArray(oVM, 'storageControllers')
2099 if aoControllers:
2100 reporter.log(" Controllers:");
2101 for oCtrl in aoControllers:
2102 reporter.log(" %s %s bus: %s type: %s" % (oCtrl.name, oCtrl.controllerType, oCtrl.bus, oCtrl.controllerType,));
2103 reporter.log(" AudioController: %s"
2104 % (self.oVBoxMgr.getEnumValueName('AudioControllerType', oVM.audioAdapter.audioController),)); # pylint: disable=not-callable
2105 reporter.log(" AudioEnabled: %s" % (oVM.audioAdapter.enabled,));
2106 reporter.log(" Host AudioDriver: %s"
2107 % (self.oVBoxMgr.getEnumValueName('AudioDriverType', oVM.audioAdapter.audioDriver),)); # pylint: disable=not-callable
2108
2109 self.processPendingEvents();
2110 aoAttachments = self.oVBoxMgr.getArray(oVM, 'mediumAttachments')
2111 if aoAttachments:
2112 reporter.log(" Attachments:");
2113 for oAtt in aoAttachments:
2114 sCtrl = "Controller: %s port: %s device: %s type: %s" % (oAtt.controller, oAtt.port, oAtt.device, oAtt.type);
2115 oMedium = oAtt.medium
2116 if oAtt.type == vboxcon.DeviceType_HardDisk:
2117 reporter.log(" %s: HDD" % sCtrl);
2118 reporter.log(" Id: %s" % (oMedium.id,));
2119 reporter.log(" Name: %s" % (oMedium.name,));
2120 reporter.log(" Format: %s" % (oMedium.format,));
2121 reporter.log(" Location: %s" % (oMedium.location,));
2122
2123 if oAtt.type == vboxcon.DeviceType_DVD:
2124 reporter.log(" %s: DVD" % sCtrl);
2125 if oMedium:
2126 reporter.log(" Id: %s" % (oMedium.id,));
2127 reporter.log(" Name: %s" % (oMedium.name,));
2128 if oMedium.hostDrive:
2129 reporter.log(" Host DVD %s" % (oMedium.location,));
2130 if oAtt.passthrough:
2131 reporter.log(" [passthrough mode]");
2132 else:
2133 reporter.log(" Virtual image: %s" % (oMedium.location,));
2134 reporter.log(" Size: %s" % (oMedium.size,));
2135 else:
2136 reporter.log(" empty");
2137
2138 if oAtt.type == vboxcon.DeviceType_Floppy:
2139 reporter.log(" %s: Floppy" % sCtrl);
2140 if oMedium:
2141 reporter.log(" Id: %s" % (oMedium.id,));
2142 reporter.log(" Name: %s" % (oMedium.name,));
2143 if oMedium.hostDrive:
2144 reporter.log(" Host floppy: %s" % (oMedium.location,));
2145 else:
2146 reporter.log(" Virtual image: %s" % (oMedium.location,));
2147 reporter.log(" Size: %s" % (oMedium.size,));
2148 else:
2149 reporter.log(" empty");
2150 self.processPendingEvents();
2151
2152 reporter.log(" Network Adapter:");
2153 for iSlot in range(0, 32):
2154 try: oNic = oVM.getNetworkAdapter(iSlot)
2155 except: break;
2156 if not oNic.enabled:
2157 reporter.log2(" slot #%d found but not enabled, skipping" % (iSlot,));
2158 continue;
2159 reporter.log(" slot #%d: type: %s (%s) MAC Address: %s lineSpeed: %s"
2160 % (iSlot, self.oVBoxMgr.getEnumValueName('NetworkAdapterType', oNic.adapterType), # pylint: disable=not-callable
2161 oNic.adapterType, oNic.MACAddress, oNic.lineSpeed) );
2162
2163 if oNic.attachmentType == vboxcon.NetworkAttachmentType_NAT:
2164 reporter.log(" attachmentType: NAT (%s)" % (oNic.attachmentType,));
2165 if self.fpApiVer >= 4.1:
2166 reporter.log(" nat-network: %s" % (oNic.NATNetwork,));
2167 elif oNic.attachmentType == vboxcon.NetworkAttachmentType_Bridged:
2168 reporter.log(" attachmentType: Bridged (%s)" % (oNic.attachmentType,));
2169 if self.fpApiVer >= 4.1:
2170 reporter.log(" hostInterface: %s" % (oNic.bridgedInterface,));
2171 else:
2172 reporter.log(" hostInterface: %s" % (oNic.hostInterface,));
2173 elif oNic.attachmentType == vboxcon.NetworkAttachmentType_Internal:
2174 reporter.log(" attachmentType: Internal (%s)" % (oNic.attachmentType,));
2175 reporter.log(" intnet-name: %s" % (oNic.internalNetwork,));
2176 elif oNic.attachmentType == vboxcon.NetworkAttachmentType_HostOnly:
2177 reporter.log(" attachmentType: HostOnly (%s)" % (oNic.attachmentType,));
2178 if self.fpApiVer >= 4.1:
2179 reporter.log(" hostInterface: %s" % (oNic.hostOnlyInterface,));
2180 else:
2181 reporter.log(" hostInterface: %s" % (oNic.hostInterface,));
2182 else:
2183 if self.fpApiVer >= 4.1:
2184 if oNic.attachmentType == vboxcon.NetworkAttachmentType_Generic:
2185 reporter.log(" attachmentType: Generic (%s)" % (oNic.attachmentType,));
2186 reporter.log(" generic-driver: %s" % (oNic.GenericDriver,));
2187 else:
2188 reporter.log(" attachmentType: unknown-%s" % (oNic.attachmentType,));
2189 else:
2190 reporter.log(" attachmentType: unknown-%s" % (oNic.attachmentType,));
2191 if oNic.traceEnabled:
2192 reporter.log(" traceFile: %s" % (oNic.traceFile,));
2193 self.processPendingEvents();
2194
2195 reporter.log(" Serial ports:");
2196 for iSlot in range(0, 8):
2197 try: oPort = oVM.getSerialPort(iSlot)
2198 except: break;
2199 if oPort is not None and oPort.enabled:
2200 enmHostMode = oPort.hostMode;
2201 reporter.log(" slot #%d: hostMode: %s (%s) I/O port: %s IRQ: %s server: %s path: %s" %
2202 (iSlot, self.oVBoxMgr.getEnumValueName('PortMode', enmHostMode), # pylint: disable=not-callable
2203 enmHostMode, oPort.IOBase, oPort.IRQ, oPort.server, oPort.path,) );
2204 self.processPendingEvents();
2205
2206 return True;
2207
2208 def logVmInfo(self, oVM): # pylint: disable=too-many-statements,too-many-branches
2209 """
2210 Logs VM configuration details.
2211
2212 This is copy, past, search, replace and edit of infoCmd from vboxshell.py.
2213 """
2214 try:
2215 fRc = self._logVmInfoUnsafe(oVM);
2216 except:
2217 reporter.logXcpt();
2218 fRc = False;
2219 return fRc;
2220
2221 def logVmInfoByName(self, sName):
2222 """
2223 logVmInfo + getVmByName.
2224 """
2225 return self.logVmInfo(self.getVmByName(sName));
2226
2227 def tryFindGuestOsId(self, sIdOrDesc):
2228 """
2229 Takes a guest OS ID or Description and returns the ID.
2230 If nothing matching it is found, the input is returned unmodified.
2231 """
2232
2233 if self.fpApiVer >= 4.0:
2234 if sIdOrDesc == 'Solaris (64 bit)':
2235 sIdOrDesc = 'Oracle Solaris 10 5/09 and earlier (64 bit)';
2236
2237 try:
2238 aoGuestTypes = self.oVBoxMgr.getArray(self.oVBox, 'GuestOSTypes');
2239 except:
2240 reporter.logXcpt();
2241 else:
2242 for oGuestOS in aoGuestTypes:
2243 try:
2244 sId = oGuestOS.id;
2245 sDesc = oGuestOS.description;
2246 except:
2247 reporter.logXcpt();
2248 else:
2249 if sIdOrDesc in (sId, sDesc,):
2250 sIdOrDesc = sId;
2251 break;
2252 self.processPendingEvents();
2253 return sIdOrDesc
2254
2255 def resourceFindVmHd(self, sVmName, sFlavor):
2256 """
2257 Search the test resources for the most recent VM HD.
2258
2259 Returns path relative to the test resource root.
2260 """
2261 ## @todo implement a proper search algo here.
2262 return '4.2/' + sFlavor + '/' + sVmName + '/t-' + sVmName + '.vdi';
2263
2264
2265 #
2266 # VM Api wrappers that logs errors, hides exceptions and other details.
2267 #
2268
2269 def createTestVMOnly(self, sName, sKind):
2270 """
2271 Creates and register a test VM without doing any kind of configuration.
2272
2273 Returns VM object (IMachine) on success, None on failure.
2274 """
2275 if not self.importVBoxApi():
2276 return None;
2277
2278 # create + register the VM
2279 try:
2280 if self.fpApiVer >= 4.2: # Introduces grouping (third parameter, empty for now).
2281 oVM = self.oVBox.createMachine("", sName, [], self.tryFindGuestOsId(sKind), "");
2282 elif self.fpApiVer >= 4.0:
2283 oVM = self.oVBox.createMachine("", sName, self.tryFindGuestOsId(sKind), "", False);
2284 elif self.fpApiVer >= 3.2:
2285 oVM = self.oVBox.createMachine(sName, self.tryFindGuestOsId(sKind), "", "", False);
2286 else:
2287 oVM = self.oVBox.createMachine(sName, self.tryFindGuestOsId(sKind), "", "");
2288 try:
2289 oVM.saveSettings();
2290 try:
2291 self.oVBox.registerMachine(oVM);
2292 return oVM;
2293 except:
2294 reporter.logXcpt();
2295 raise;
2296 except:
2297 reporter.logXcpt();
2298 if self.fpApiVer >= 4.0:
2299 try:
2300 if self.fpApiVer >= 4.3:
2301 oProgress = oVM.deleteConfig([]);
2302 else:
2303 oProgress = oVM.delete(None);
2304 self.waitOnProgress(oProgress);
2305 except:
2306 reporter.logXcpt();
2307 else:
2308 try: oVM.deleteSettings();
2309 except: reporter.logXcpt();
2310 raise;
2311 except:
2312 reporter.errorXcpt('failed to create vm "%s"' % (sName));
2313 return None;
2314
2315 # pylint: disable=too-many-arguments,too-many-locals,too-many-statements
2316 def createTestVM(self,
2317 sName,
2318 iGroup,
2319 sHd = None,
2320 cMbRam = None,
2321 cCpus = 1,
2322 fVirtEx = None,
2323 fNestedPaging = None,
2324 sDvdImage = None,
2325 sKind = "Other",
2326 fIoApic = None,
2327 fNstHwVirt = None,
2328 fPae = None,
2329 fFastBootLogo = True,
2330 eNic0Type = None,
2331 eNic0AttachType = None,
2332 sNic0NetName = 'default',
2333 sNic0MacAddr = 'grouped',
2334 sFloppy = None,
2335 fNatForwardingForTxs = None,
2336 sHddControllerType = 'IDE Controller',
2337 fVmmDevTestingPart = None,
2338 fVmmDevTestingMmio = False,
2339 sFirmwareType = 'bios',
2340 sChipsetType = 'piix3',
2341 sDvdControllerType = 'IDE Controller',
2342 sCom1RawFile = None):
2343 """
2344 Creates a test VM with a immutable HD from the test resources.
2345 """
2346 # create + register the VM
2347 oVM = self.createTestVMOnly(sName, sKind);
2348 if not oVM:
2349 return None;
2350
2351 # Configure the VM.
2352 fRc = True;
2353 oSession = self.openSession(oVM);
2354 if oSession is not None:
2355 fRc = oSession.setupPreferredConfig();
2356
2357 if fRc and cMbRam is not None :
2358 fRc = oSession.setRamSize(cMbRam);
2359 if fRc and cCpus is not None:
2360 fRc = oSession.setCpuCount(cCpus);
2361 if fRc and fVirtEx is not None:
2362 fRc = oSession.enableVirtEx(fVirtEx);
2363 if fRc and fNestedPaging is not None:
2364 fRc = oSession.enableNestedPaging(fNestedPaging);
2365 if fRc and fIoApic is not None:
2366 fRc = oSession.enableIoApic(fIoApic);
2367 if fRc and fNstHwVirt is not None:
2368 fRc = oSession.enableNestedHwVirt(fNstHwVirt);
2369 if fRc and fPae is not None:
2370 fRc = oSession.enablePae(fPae);
2371 if fRc and sDvdImage is not None:
2372 fRc = oSession.attachDvd(sDvdImage, sDvdControllerType);
2373 if fRc and sHd is not None:
2374 fRc = oSession.attachHd(sHd, sHddControllerType);
2375 if fRc and sFloppy is not None:
2376 fRc = oSession.attachFloppy(sFloppy);
2377 if fRc and eNic0Type is not None:
2378 fRc = oSession.setNicType(eNic0Type, 0);
2379 if fRc and (eNic0AttachType is not None or (sNic0NetName is not None and sNic0NetName != 'default')):
2380 fRc = oSession.setNicAttachment(eNic0AttachType, sNic0NetName, 0);
2381 if fRc and sNic0MacAddr is not None:
2382 if sNic0MacAddr == 'grouped':
2383 sNic0MacAddr = '%02X' % (iGroup);
2384 fRc = oSession.setNicMacAddress(sNic0MacAddr, 0);
2385 if fRc and fNatForwardingForTxs is True:
2386 fRc = oSession.setupNatForwardingForTxs();
2387 if fRc and fFastBootLogo is not None:
2388 fRc = oSession.setupBootLogo(fFastBootLogo);
2389 if fRc and self.fEnableVrdp:
2390 fRc = oSession.setupVrdp(True, self.uVrdpBasePort + iGroup);
2391 if fRc and fVmmDevTestingPart is not None:
2392 fRc = oSession.enableVmmDevTestingPart(fVmmDevTestingPart, fVmmDevTestingMmio);
2393 if fRc and sFirmwareType == 'bios':
2394 fRc = oSession.setFirmwareType(vboxcon.FirmwareType_BIOS);
2395 elif sFirmwareType == 'efi':
2396 fRc = oSession.setFirmwareType(vboxcon.FirmwareType_EFI);
2397 if fRc and self.fEnableDebugger:
2398 fRc = oSession.setExtraData('VBoxInternal/DBGC/Enabled', '1');
2399 if fRc and sChipsetType == 'piix3':
2400 fRc = oSession.setChipsetType(vboxcon.ChipsetType_PIIX3);
2401 elif sChipsetType == 'ich9':
2402 fRc = oSession.setChipsetType(vboxcon.ChipsetType_ICH9);
2403 if fRc and sCom1RawFile:
2404 fRc = oSession.setupSerialToRawFile(0, sCom1RawFile);
2405
2406 if fRc: fRc = oSession.saveSettings();
2407 if not fRc: oSession.discardSettings(True);
2408 oSession.close();
2409 if not fRc:
2410 try: self.oVBox.unregisterMachine(oVM.id);
2411 except: pass;
2412 if self.fpApiVer >= 4.0:
2413 try:
2414 if self.fpApiVer >= 4.3:
2415 oProgress = oVM.deleteConfig([]);
2416 else:
2417 oProgress = oVM.delete(None);
2418 self.waitOnProgress(oProgress);
2419 except:
2420 reporter.logXcpt();
2421 else:
2422 try: oVM.deleteSettings();
2423 except: reporter.logXcpt();
2424 return None;
2425
2426 # success.
2427 reporter.log('created "%s" with name "%s"' % (oVM.id, sName));
2428 self.aoVMs.append(oVM);
2429 self.logVmInfo(oVM); # testing...
2430 return oVM;
2431 # pylint: enable=too-many-arguments,too-many-locals,too-many-statements
2432
2433 def createTestVmWithDefaults(self, # pylint: disable=too-many-arguments
2434 sName,
2435 iGroup,
2436 sKind,
2437 sDvdImage = None,
2438 fFastBootLogo = True,
2439 eNic0AttachType = None,
2440 sNic0NetName = 'default',
2441 sNic0MacAddr = 'grouped',
2442 fVmmDevTestingPart = None,
2443 fVmmDevTestingMmio = False,
2444 sCom1RawFile = None):
2445 """
2446 Creates a test VM with all defaults and no HDs.
2447 """
2448 # create + register the VM
2449 oVM = self.createTestVMOnly(sName, sKind);
2450 if oVM is not None:
2451 # Configure the VM with defaults according to sKind.
2452 fRc = True;
2453 oSession = self.openSession(oVM);
2454 if oSession is not None:
2455 if self.fpApiVer >= 6.0:
2456 try:
2457 oSession.o.machine.applyDefaults('');
2458 except:
2459 reporter.errorXcpt('failed to apply defaults to vm "%s"' % (sName,));
2460 fRc = False;
2461 else:
2462 reporter.error("Implement applyDefaults for vbox version %s" % (self.fpApiVer,));
2463 #fRc = oSession.setupPreferredConfig();
2464 fRc = False;
2465
2466 # Apply the specified configuration:
2467 if fRc and sDvdImage is not None:
2468 #fRc = oSession.insertDvd(sDvdImage); # attachDvd
2469 reporter.error('Implement: oSession.insertDvd(%s)' % (sDvdImage,));
2470 fRc = False;
2471
2472 if fRc and fFastBootLogo is not None:
2473 fRc = oSession.setupBootLogo(fFastBootLogo);
2474
2475 if fRc and (eNic0AttachType is not None or (sNic0NetName is not None and sNic0NetName != 'default')):
2476 fRc = oSession.setNicAttachment(eNic0AttachType, sNic0NetName, 0);
2477 if fRc and sNic0MacAddr is not None:
2478 if sNic0MacAddr == 'grouped':
2479 sNic0MacAddr = '%02X' % (iGroup,);
2480 fRc = oSession.setNicMacAddress(sNic0MacAddr, 0);
2481
2482 if fRc and fVmmDevTestingPart is not None:
2483 fRc = oSession.enableVmmDevTestingPart(fVmmDevTestingPart, fVmmDevTestingMmio);
2484
2485 if fRc and sCom1RawFile:
2486 fRc = oSession.setupSerialToRawFile(0, sCom1RawFile);
2487
2488 # Save the settings if we were successfull, otherwise discard them.
2489 if fRc:
2490 fRc = oSession.saveSettings();
2491 if not fRc:
2492 oSession.discardSettings(True);
2493 oSession.close();
2494
2495 if fRc is True:
2496 # If we've been successful, add the VM to the list and return it.
2497 # success.
2498 reporter.log('created "%s" with name "%s"' % (oVM.id, sName, ));
2499 self.aoVMs.append(oVM);
2500 self.logVmInfo(oVM); # testing...
2501 return oVM;
2502
2503 # Failed. Unregister the machine and delete it.
2504 try: self.oVBox.unregisterMachine(oVM.id);
2505 except: pass;
2506
2507 if self.fpApiVer >= 4.0:
2508 try:
2509 if self.fpApiVer >= 4.3:
2510 oProgress = oVM.deleteConfig([]);
2511 else:
2512 oProgress = oVM.delete(None);
2513 self.waitOnProgress(oProgress);
2514 except:
2515 reporter.logXcpt();
2516 else:
2517 try: oVM.deleteSettings();
2518 except: reporter.logXcpt();
2519 return None;
2520
2521 def addTestMachine(self, sNameOrId, fQuiet = False):
2522 """
2523 Adds an already existing (that is, configured) test VM to the
2524 test VM list.
2525 """
2526 # find + add the VM to the list.
2527 try:
2528 if self.fpApiVer >= 4.0:
2529 oVM = self.oVBox.findMachine(sNameOrId);
2530 else:
2531 reporter.error('fpApiVer=%s - did you remember to initialize the API' % (self.fpApiVer,));
2532 except:
2533 reporter.errorXcpt('could not find vm "%s"' % (sNameOrId,));
2534 return None;
2535
2536 self.aoVMs.append(oVM);
2537 if not fQuiet:
2538 reporter.log('Added "%s" with name "%s"' % (oVM.id, sNameOrId));
2539 self.logVmInfo(oVM);
2540 return oVM;
2541
2542 def openSession(self, oVM):
2543 """
2544 Opens a session for the VM. Returns the a Session wrapper object that
2545 will automatically close the session when the wrapper goes out of scope.
2546
2547 On failure None is returned and an error is logged.
2548 """
2549 try:
2550 sUuid = oVM.id;
2551 except:
2552 reporter.errorXcpt('failed to get the UUID for VM "%s"' % (oVM,));
2553 return None;
2554
2555 # This loop is a kludge to deal with us racing the closing of the
2556 # direct session of a previous VM run. See waitOnDirectSessionClose.
2557 for i in range(10):
2558 try:
2559 if self.fpApiVer <= 3.2:
2560 oSession = self.oVBoxMgr.openMachineSession(sUuid);
2561 else:
2562 oSession = self.oVBoxMgr.openMachineSession(oVM);
2563 break;
2564 except:
2565 if i == 9:
2566 reporter.errorXcpt('failed to open session for "%s" ("%s")' % (sUuid, oVM));
2567 return None;
2568 if i > 0:
2569 reporter.logXcpt('warning: failed to open session for "%s" ("%s") - retrying in %u secs' % (sUuid, oVM, i));
2570 self.waitOnDirectSessionClose(oVM, 5000 + i * 1000);
2571 from testdriver.vboxwrappers import SessionWrapper;
2572 return SessionWrapper(oSession, oVM, self.oVBox, self.oVBoxMgr, self, False);
2573
2574 #
2575 # Guest locations.
2576 #
2577
2578 @staticmethod
2579 def getGuestTempDir(oTestVm):
2580 """
2581 Helper for finding a temporary directory in the test VM.
2582
2583 Note! It may be necessary to create it!
2584 """
2585 if oTestVm.isWindows():
2586 return "C:\\Temp";
2587 if oTestVm.isOS2():
2588 return "C:\\Temp";
2589 return '/var/tmp';
2590
2591 @staticmethod
2592 def getGuestSystemDir(oTestVm, sPathPrefix = ''):
2593 """
2594 Helper for finding a system directory in the test VM that we can play around with.
2595 sPathPrefix can be used to specify other directories, such as /usr/local/bin/ or /usr/bin, for instance.
2596
2597 On Windows this is always the System32 directory, so this function can be used as
2598 basis for locating other files in or under that directory.
2599 """
2600 if oTestVm.isWindows():
2601 return oTestVm.pathJoin(TestDriver.getGuestWinDir(oTestVm), 'System32');
2602 if oTestVm.isOS2():
2603 return 'C:\\OS2\\DLL';
2604 return sPathPrefix + "/bin";
2605
2606 @staticmethod
2607 def getGuestSystemAdminDir(oTestVm, sPathPrefix = ''):
2608 """
2609 Helper for finding a system admin directory ("sbin") in the test VM that we can play around with.
2610 sPathPrefix can be used to specify other directories, such as /usr/local/sbin/ or /usr/sbin, for instance.
2611
2612 On Windows this is always the System32 directory, so this function can be used as
2613 basis for locating other files in or under that directory.
2614 On UNIX-y systems this always is the "sh" shell to guarantee a common shell syntax.
2615 """
2616 if oTestVm.isWindows():
2617 return oTestVm.pathJoin(TestDriver.getGuestWinDir(oTestVm), 'System32');
2618 if oTestVm.isOS2():
2619 return 'C:\\OS2\\DLL'; ## @todo r=andy Not sure here.
2620 return sPathPrefix + "/sbin";
2621
2622 @staticmethod
2623 def getGuestWinDir(oTestVm):
2624 """
2625 Helper for finding the Windows directory in the test VM that we can play around with.
2626 ASSUMES that we always install Windows on drive C.
2627
2628 Returns the Windows directory, or an empty string when executed on a non-Windows guest (asserts).
2629 """
2630 sWinDir = '';
2631 if oTestVm.isWindows():
2632 if oTestVm.sKind in ['WindowsNT4', 'WindowsNT3x',]:
2633 sWinDir = 'C:\\WinNT\\';
2634 else:
2635 sWinDir = 'C:\\Windows\\';
2636 assert sWinDir != '', 'Retrieving Windows directory for non-Windows OS';
2637 return sWinDir;
2638
2639 @staticmethod
2640 def getGuestSystemShell(oTestVm):
2641 """
2642 Helper for finding the default system shell in the test VM.
2643 """
2644 if oTestVm.isWindows():
2645 return TestDriver.getGuestSystemDir(oTestVm) + '\\cmd.exe';
2646 if oTestVm.isOS2():
2647 return TestDriver.getGuestSystemDir(oTestVm) + '\\..\\CMD.EXE';
2648 return "/bin/sh";
2649
2650 @staticmethod
2651 def getGuestSystemFileForReading(oTestVm):
2652 """
2653 Helper for finding a file in the test VM that we can read.
2654 """
2655 if oTestVm.isWindows():
2656 return TestDriver.getGuestSystemDir(oTestVm) + '\\ntdll.dll';
2657 if oTestVm.isOS2():
2658 return TestDriver.getGuestSystemDir(oTestVm) + '\\DOSCALL1.DLL';
2659 return "/bin/sh";
2660
2661 def getVmByName(self, sName):
2662 """
2663 Get a test VM by name. Returns None if not found, logged.
2664 """
2665 # Look it up in our 'cache'.
2666 for oVM in self.aoVMs:
2667 try:
2668 #reporter.log2('cur: %s / %s (oVM=%s)' % (oVM.name, oVM.id, oVM));
2669 if oVM.name == sName:
2670 return oVM;
2671 except:
2672 reporter.errorXcpt('failed to get the name from the VM "%s"' % (oVM));
2673
2674 # Look it up the standard way.
2675 return self.addTestMachine(sName, fQuiet = True);
2676
2677 def getVmByUuid(self, sUuid):
2678 """
2679 Get a test VM by uuid. Returns None if not found, logged.
2680 """
2681 # Look it up in our 'cache'.
2682 for oVM in self.aoVMs:
2683 try:
2684 if oVM.id == sUuid:
2685 return oVM;
2686 except:
2687 reporter.errorXcpt('failed to get the UUID from the VM "%s"' % (oVM));
2688
2689 # Look it up the standard way.
2690 return self.addTestMachine(sUuid, fQuiet = True);
2691
2692 def waitOnProgress(self, oProgress, cMsTimeout = 1000000, fErrorOnTimeout = True, cMsInterval = 1000):
2693 """
2694 Waits for a progress object to complete. Returns the status code.
2695 """
2696 # Wait for progress no longer than cMsTimeout time period.
2697 tsStart = datetime.datetime.now()
2698 while True:
2699 self.processPendingEvents();
2700 try:
2701 if oProgress.completed:
2702 break;
2703 except:
2704 return -1;
2705 self.processPendingEvents();
2706
2707 tsNow = datetime.datetime.now()
2708 tsDelta = tsNow - tsStart
2709 if ((tsDelta.microseconds + tsDelta.seconds * 1000000) // 1000) > cMsTimeout:
2710 if fErrorOnTimeout:
2711 reporter.errorTimeout('Timeout while waiting for progress.')
2712 return -1
2713
2714 reporter.doPollWork('vbox.TestDriver.waitOnProgress');
2715 try: oProgress.waitForCompletion(cMsInterval);
2716 except: return -2;
2717
2718 try: rc = oProgress.resultCode;
2719 except: rc = -2;
2720 self.processPendingEvents();
2721 return rc;
2722
2723 def waitOnDirectSessionClose(self, oVM, cMsTimeout):
2724 """
2725 Waits for the VM process to close it's current direct session.
2726
2727 Returns None.
2728 """
2729 # Get the original values so we're not subject to
2730 try:
2731 eCurState = oVM.sessionState;
2732 if self.fpApiVer >= 5.0:
2733 sCurName = sOrgName = oVM.sessionName;
2734 else:
2735 sCurName = sOrgName = oVM.sessionType;
2736 if self.fpApiVer >= 4.2:
2737 iCurPid = iOrgPid = oVM.sessionPID;
2738 else:
2739 iCurPid = iOrgPid = oVM.sessionPid;
2740 except Exception as oXcpt:
2741 if ComError.notEqual(oXcpt, ComError.E_ACCESSDENIED):
2742 reporter.logXcpt();
2743 self.processPendingEvents();
2744 return None;
2745 self.processPendingEvents();
2746
2747 msStart = base.timestampMilli();
2748 while iCurPid == iOrgPid \
2749 and sCurName == sOrgName \
2750 and sCurName != '' \
2751 and base.timestampMilli() - msStart < cMsTimeout \
2752 and eCurState in (vboxcon.SessionState_Unlocking, vboxcon.SessionState_Spawning, vboxcon.SessionState_Locked,):
2753 self.processEvents(1000);
2754 try:
2755 eCurState = oVM.sessionState;
2756 sCurName = oVM.sessionName if self.fpApiVer >= 5.0 else oVM.sessionType;
2757 iCurPid = oVM.sessionPID if self.fpApiVer >= 4.2 else oVM.sessionPid;
2758 except Exception as oXcpt:
2759 if ComError.notEqual(oXcpt, ComError.E_ACCESSDENIED):
2760 reporter.logXcpt();
2761 break;
2762 self.processPendingEvents();
2763 self.processPendingEvents();
2764 return None;
2765
2766 def uploadStartupLogFile(self, oVM, sVmName):
2767 """
2768 Uploads the VBoxStartup.log when present.
2769 """
2770 fRc = True;
2771 try:
2772 sLogFile = os.path.join(oVM.logFolder, 'VBoxHardening.log');
2773 except:
2774 reporter.logXcpt();
2775 fRc = False;
2776 else:
2777 if os.path.isfile(sLogFile):
2778 reporter.addLogFile(sLogFile, 'log/release/vm', '%s hardening log' % (sVmName, ),
2779 sAltName = '%s-%s' % (sVmName, os.path.basename(sLogFile),));
2780 return fRc;
2781
2782 def annotateAndUploadProcessReport(self, sProcessReport, sFilename, sKind, sDesc):
2783 """
2784 Annotates the given VM process report and uploads it if successfull.
2785 """
2786 fRc = False;
2787 if self.oBuild is not None and self.oBuild.sInstallPath is not None:
2788 oResolver = btresolver.BacktraceResolver(self.sScratchPath, self.oBuild.sInstallPath,
2789 self.getBuildOs(), self.getBuildArch(),
2790 fnLog = reporter.log);
2791 fRcTmp = oResolver.prepareEnv();
2792 if fRcTmp:
2793 reporter.log('Successfully prepared environment');
2794 sReportDbgSym = oResolver.annotateReport(sProcessReport);
2795 if sReportDbgSym is not None:
2796 reporter.addLogString(sReportDbgSym, sFilename, sKind, sDesc);
2797 fRc = True;
2798 else:
2799 reporter.log('Annotating report failed');
2800 oResolver.cleanupEnv();
2801 return fRc;
2802
2803 def startVmEx(self, oVM, fWait = True, sType = None, sName = None, asEnv = None): # pylint: disable=too-many-locals,too-many-statements
2804 """
2805 Start the VM, returning the VM session and progress object on success.
2806 The session is also added to the task list and to the aoRemoteSessions set.
2807
2808 asEnv is a list of string on the putenv() form.
2809
2810 On failure (None, None) is returned and an error is logged.
2811 """
2812 # Massage and check the input.
2813 if sType is None:
2814 sType = self.sSessionType;
2815 if sName is None:
2816 try: sName = oVM.name;
2817 except: sName = 'bad-vm-handle';
2818 reporter.log('startVmEx: sName=%s fWait=%s sType=%s' % (sName, fWait, sType));
2819 if oVM is None:
2820 return (None, None);
2821
2822 ## @todo Do this elsewhere.
2823 # Hack alert. Disables all annoying GUI popups.
2824 if sType == 'gui' and not self.aoRemoteSessions:
2825 try:
2826 self.oVBox.setExtraData('GUI/Input/AutoCapture', 'false');
2827 if self.fpApiVer >= 3.2:
2828 self.oVBox.setExtraData('GUI/LicenseAgreed', '8');
2829 else:
2830 self.oVBox.setExtraData('GUI/LicenseAgreed', '7');
2831 self.oVBox.setExtraData('GUI/RegistrationData', 'triesLeft=0');
2832 self.oVBox.setExtraData('GUI/SUNOnlineData', 'triesLeft=0');
2833 self.oVBox.setExtraData('GUI/SuppressMessages', 'confirmVMReset,remindAboutMouseIntegrationOn,'
2834 'remindAboutMouseIntegrationOff,remindAboutPausedVMInput,confirmInputCapture,'
2835 'confirmGoingFullscreen,remindAboutInaccessibleMedia,remindAboutWrongColorDepth,'
2836 'confirmRemoveMedium,allPopupPanes,allMessageBoxes,all');
2837 self.oVBox.setExtraData('GUI/UpdateDate', 'never');
2838 self.oVBox.setExtraData('GUI/PreventBetaWarning', self.oVBox.version);
2839 except:
2840 reporter.logXcpt();
2841
2842 # The UUID for the name.
2843 try:
2844 sUuid = oVM.id;
2845 except:
2846 reporter.errorXcpt('failed to get the UUID for VM "%s"' % (oVM));
2847 return (None, None);
2848 self.processPendingEvents();
2849
2850 # Construct the environment.
2851 sLogFile = '%s/VM-%s.log' % (self.sScratchPath, sUuid);
2852 try: os.remove(sLogFile);
2853 except: pass;
2854 if self.sLogSessionDest:
2855 sLogDest = self.sLogSessionDest;
2856 else:
2857 sLogDest = 'file=%s' % (sLogFile,);
2858 asEnvFinal = [
2859 'VBOX_LOG=%s' % (self.sLogSessionGroups,),
2860 'VBOX_LOG_FLAGS=%s' % (self.sLogSessionFlags,),
2861 'VBOX_LOG_DEST=nodeny %s' % (sLogDest,),
2862 'VBOX_RELEASE_LOG_FLAGS=append time',
2863 ];
2864 if sType == 'gui':
2865 asEnvFinal.append('VBOX_GUI_DBG_ENABLED=1');
2866 if asEnv is not None and asEnv:
2867 asEnvFinal += asEnv;
2868
2869 # Shortcuts for local testing.
2870 oProgress = oWrapped = None;
2871 oTestVM = self.oTestVmSet.findTestVmByName(sName) if self.oTestVmSet is not None else None;
2872 try:
2873 if oTestVM is not None \
2874 and oTestVM.fSnapshotRestoreCurrent is True:
2875 if oVM.state is vboxcon.MachineState_Running:
2876 reporter.log2('Machine "%s" already running.' % (sName,));
2877 oProgress = None;
2878 oWrapped = self.openSession(oVM);
2879 else:
2880 reporter.log2('Checking if snapshot for machine "%s" exists.' % (sName,));
2881 oSessionWrapperRestore = self.openSession(oVM);
2882 if oSessionWrapperRestore is not None:
2883 oSnapshotCur = oVM.currentSnapshot;
2884 if oSnapshotCur is not None:
2885 reporter.log2('Restoring snapshot for machine "%s".' % (sName,));
2886 oSessionWrapperRestore.restoreSnapshot(oSnapshotCur);
2887 reporter.log2('Current snapshot for machine "%s" restored.' % (sName,));
2888 else:
2889 reporter.log('warning: no current snapshot for machine "%s" found.' % (sName,));
2890 oSessionWrapperRestore.close();
2891 except:
2892 reporter.errorXcpt();
2893 return (None, None);
2894
2895 # Open a remote session, wait for this operation to complete.
2896 # (The loop is a kludge to deal with us racing the closing of the
2897 # direct session of a previous VM run. See waitOnDirectSessionClose.)
2898 if oWrapped is None:
2899 for i in range(10):
2900 try:
2901 if self.fpApiVer < 4.3 \
2902 or (self.fpApiVer == 4.3 and not hasattr(self.oVBoxMgr, 'getSessionObject')):
2903 oSession = self.oVBoxMgr.mgr.getSessionObject(self.oVBox); # pylint: disable=no-member
2904 elif self.fpApiVer < 5.2 \
2905 or (self.fpApiVer == 5.2 and hasattr(self.oVBoxMgr, 'vbox')):
2906 oSession = self.oVBoxMgr.getSessionObject(self.oVBox); # pylint: disable=no-member
2907 else:
2908 oSession = self.oVBoxMgr.getSessionObject(); # pylint: disable=no-member,no-value-for-parameter
2909 if self.fpApiVer < 3.3:
2910 oProgress = self.oVBox.openRemoteSession(oSession, sUuid, sType, '\n'.join(asEnvFinal));
2911 else:
2912 if self.uApiRevision >= self.makeApiRevision(6, 1, 0, 1):
2913 oProgress = oVM.launchVMProcess(oSession, sType, asEnvFinal);
2914 else:
2915 oProgress = oVM.launchVMProcess(oSession, sType, '\n'.join(asEnvFinal));
2916 break;
2917 except:
2918 if i == 9:
2919 reporter.errorXcpt('failed to start VM "%s" ("%s"), aborting.' % (sUuid, sName));
2920 return (None, None);
2921 oSession = None;
2922 if i >= 0:
2923 reporter.logXcpt('warning: failed to start VM "%s" ("%s") - retrying in %u secs.' % (sUuid, oVM, i)); # pylint: disable=line-too-long
2924 self.waitOnDirectSessionClose(oVM, 5000 + i * 1000);
2925 if fWait and oProgress is not None:
2926 rc = self.waitOnProgress(oProgress);
2927 if rc < 0:
2928 self.waitOnDirectSessionClose(oVM, 5000);
2929
2930 # VM failed to power up, still collect VBox.log, need to wrap the session object
2931 # in order to use the helper for adding the log files to the report.
2932 from testdriver.vboxwrappers import SessionWrapper;
2933 oTmp = SessionWrapper(oSession, oVM, self.oVBox, self.oVBoxMgr, self, True, sName, sLogFile);
2934 oTmp.addLogsToReport();
2935 try:
2936 if oSession is not None:
2937 oSession.close();
2938 except: pass;
2939 reportError(oProgress, 'failed to open session for "%s"' % (sName));
2940 self.uploadStartupLogFile(oVM, sName);
2941 return (None, None);
2942 reporter.log2('waitOnProgress -> %s' % (rc,));
2943
2944 # Wrap up the session object and push on to the list before returning it.
2945 if oWrapped is None:
2946 from testdriver.vboxwrappers import SessionWrapper;
2947 oWrapped = SessionWrapper(oSession, oVM, self.oVBox, self.oVBoxMgr, self, True, sName, sLogFile);
2948
2949 oWrapped.registerEventHandlerForTask();
2950 self.aoRemoteSessions.append(oWrapped);
2951 if oWrapped is not self.aoRemoteSessions[len(self.aoRemoteSessions) - 1]:
2952 reporter.error('not by reference: oWrapped=%s aoRemoteSessions[%s]=%s'
2953 % (oWrapped, len(self.aoRemoteSessions) - 1,
2954 self.aoRemoteSessions[len(self.aoRemoteSessions) - 1]));
2955 self.addTask(oWrapped);
2956
2957 reporter.log2('startVmEx: oSession=%s, oSessionWrapper=%s, oProgress=%s' % (oSession, oWrapped, oProgress));
2958
2959 from testdriver.vboxwrappers import ProgressWrapper;
2960 return (oWrapped, ProgressWrapper(oProgress, self.oVBoxMgr, self,
2961 'starting %s' % (sName,)) if oProgress else None);
2962
2963 def startVm(self, oVM, sType=None, sName = None, asEnv = None):
2964 """ Simplified version of startVmEx. """
2965 oSession, _ = self.startVmEx(oVM, True, sType, sName, asEnv = asEnv);
2966 return oSession;
2967
2968 def startVmByNameEx(self, sName, fWait=True, sType=None, asEnv = None):
2969 """
2970 Start the VM, returning the VM session and progress object on success.
2971 The session is also added to the task list and to the aoRemoteSessions set.
2972
2973 On failure (None, None) is returned and an error is logged.
2974 """
2975 oVM = self.getVmByName(sName);
2976 if oVM is None:
2977 return (None, None);
2978 return self.startVmEx(oVM, fWait, sType, sName, asEnv = asEnv);
2979
2980 def startVmByName(self, sName, sType=None, asEnv = None):
2981 """
2982 Start the VM, returning the VM session on success. The session is
2983 also added to the task list and to the aoRemoteSessions set.
2984
2985 On failure None is returned and an error is logged.
2986 """
2987 oSession, _ = self.startVmByNameEx(sName, True, sType, asEnv = asEnv);
2988 return oSession;
2989
2990 def terminateVmBySession(self, oSession, oProgress = None, fTakeScreenshot = None): # pylint: disable=too-many-statements
2991 """
2992 Terminates the VM specified by oSession and adds the release logs to
2993 the test report.
2994
2995 This will try achieve this by using powerOff, but will resort to
2996 tougher methods if that fails.
2997
2998 The session will always be removed from the task list.
2999 The session will be closed unless we fail to kill the process.
3000 The session will be removed from the remote session list if closed.
3001
3002 The progress object (a wrapper!) is for teleportation and similar VM
3003 operations, it will be attempted canceled before powering off the VM.
3004 Failures are logged but ignored.
3005 The progress object will always be removed from the task list.
3006
3007 Returns True if powerOff and session close both succeed.
3008 Returns False if on failure (logged), including when we successfully
3009 kill the VM process.
3010 """
3011 reporter.log2('terminateVmBySession: oSession=%s (pid=%s) oProgress=%s' % (oSession.sName, oSession.getPid(), oProgress));
3012
3013 # Call getPid first to make sure the PID is cached in the wrapper.
3014 oSession.getPid();
3015
3016 #
3017 # If the host is out of memory, just skip all the info collection as it
3018 # requires memory too and seems to wedge.
3019 #
3020 sHostProcessInfo = None;
3021 sHostProcessInfoHung = None;
3022 sLastScreenshotPath = None;
3023 sOsKernelLog = None;
3024 sVgaText = None;
3025 asMiscInfos = [];
3026
3027 if not oSession.fHostMemoryLow:
3028 # Try to fetch the VM process info before meddling with its state.
3029 if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0:
3030 sHostProcessInfo = utils.processGetInfo(oSession.getPid(), fSudo = True);
3031
3032 #
3033 # Pause the VM if we're going to take any screenshots or dig into the
3034 # guest. Failures are quitely ignored.
3035 #
3036 if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0:
3037 try:
3038 if oSession.oVM.state in [ vboxcon.MachineState_Running,
3039 vboxcon.MachineState_LiveSnapshotting,
3040 vboxcon.MachineState_Teleporting ]:
3041 oSession.o.console.pause();
3042 except:
3043 reporter.logXcpt();
3044
3045 #
3046 # Take Screenshot and upload it (see below) to Test Manager if appropriate/requested.
3047 #
3048 if fTakeScreenshot is True or self.fAlwaysUploadScreenshots or reporter.testErrorCount() > 0:
3049 sLastScreenshotPath = os.path.join(self.sScratchPath, "LastScreenshot-%s.png" % oSession.sName);
3050 fRc = oSession.takeScreenshot(sLastScreenshotPath);
3051 if fRc is not True:
3052 sLastScreenshotPath = None;
3053
3054 # Query the OS kernel log from the debugger if appropriate/requested.
3055 if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0:
3056 sOsKernelLog = oSession.queryOsKernelLog();
3057
3058 # Do "info vgatext all" separately.
3059 if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0:
3060 sVgaText = oSession.queryDbgInfoVgaText();
3061
3062 # Various infos (do after kernel because of symbols).
3063 if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0:
3064 # Dump the guest stack for all CPUs.
3065 cCpus = oSession.getCpuCount();
3066 if cCpus > 0:
3067 for iCpu in xrange(0, cCpus):
3068 sThis = oSession.queryDbgGuestStack(iCpu);
3069 if sThis:
3070 asMiscInfos += [
3071 '================ start guest stack VCPU %s ================\n' % (iCpu,),
3072 sThis,
3073 '================ end guest stack VCPU %s ==================\n' % (iCpu,),
3074 ];
3075
3076 for sInfo, sArg in [ ('mode', 'all'),
3077 ('fflags', ''),
3078 ('cpumguest', 'verbose all'),
3079 ('cpumguestinstr', 'symbol all'),
3080 ('exits', ''),
3081 ('pic', ''),
3082 ('apic', ''),
3083 ('apiclvt', ''),
3084 ('apictimer', ''),
3085 ('ioapic', ''),
3086 ('pit', ''),
3087 ('phys', ''),
3088 ('clocks', ''),
3089 ('timers', ''),
3090 ('gdtguest', ''),
3091 ('ldtguest', ''),
3092 ]:
3093 if sInfo in ['apic',] and self.fpApiVer < 5.1: # asserts and burns
3094 continue;
3095 sThis = oSession.queryDbgInfo(sInfo, sArg);
3096 if sThis:
3097 if sThis[-1] != '\n':
3098 sThis += '\n';
3099 asMiscInfos += [
3100 '================ start %s %s ================\n' % (sInfo, sArg),
3101 sThis,
3102 '================ end %s %s ==================\n' % (sInfo, sArg),
3103 ];
3104
3105 #
3106 # Terminate the VM
3107 #
3108
3109 # Cancel the progress object if specified.
3110 if oProgress is not None:
3111 if not oProgress.isCompleted() and oProgress.isCancelable():
3112 reporter.log2('terminateVmBySession: canceling "%s"...' % (oProgress.sName));
3113 try:
3114 oProgress.o.cancel();
3115 except:
3116 reporter.logXcpt();
3117 else:
3118 oProgress.wait();
3119 self.removeTask(oProgress);
3120
3121 # Check if the VM has terminated by itself before powering it off.
3122 fClose = True;
3123 fRc = True;
3124 if oSession.needsPoweringOff():
3125 reporter.log('terminateVmBySession: powering off "%s"...' % (oSession.sName,));
3126 fRc = oSession.powerOff(fFudgeOnFailure = False);
3127 if fRc is not True:
3128 # power off failed, try terminate it in a nice manner.
3129 fRc = False;
3130 uPid = oSession.getPid();
3131 if uPid is not None:
3132 #
3133 # Collect some information about the VM process first to have
3134 # some state information for further investigation why powering off failed.
3135 #
3136 sHostProcessInfoHung = utils.processGetInfo(uPid, fSudo = True);
3137
3138 # Exterminate...
3139 reporter.error('terminateVmBySession: Terminating PID %u (VM %s)' % (uPid, oSession.sName));
3140 fClose = base.processTerminate(uPid);
3141 if fClose is True:
3142 self.waitOnDirectSessionClose(oSession.oVM, 5000);
3143 fClose = oSession.waitForTask(1000);
3144
3145 if fClose is not True:
3146 # Being nice failed...
3147 reporter.error('terminateVmBySession: Termination failed, trying to kill PID %u (VM %s) instead' \
3148 % (uPid, oSession.sName));
3149 fClose = base.processKill(uPid);
3150 if fClose is True:
3151 self.waitOnDirectSessionClose(oSession.oVM, 5000);
3152 fClose = oSession.waitForTask(1000);
3153 if fClose is not True:
3154 reporter.error('terminateVmBySession: Failed to kill PID %u (VM %s)' % (uPid, oSession.sName));
3155
3156 # The final steps.
3157 if fClose is True:
3158 reporter.log('terminateVmBySession: closing session "%s"...' % (oSession.sName,));
3159 oSession.close();
3160 self.waitOnDirectSessionClose(oSession.oVM, 10000);
3161 try:
3162 eState = oSession.oVM.state;
3163 except:
3164 reporter.logXcpt();
3165 else:
3166 if eState == vboxcon.MachineState_Aborted:
3167 reporter.error('terminateVmBySession: The VM "%s" aborted!' % (oSession.sName,));
3168 self.removeTask(oSession);
3169
3170 #
3171 # Add the release log, debug log and a screenshot of the VM to the test report.
3172 #
3173 if self.fAlwaysUploadLogs or reporter.testErrorCount() > 0:
3174 oSession.addLogsToReport();
3175
3176 # Add a screenshot if it has been requested and taken successfully.
3177 if sLastScreenshotPath is not None:
3178 if reporter.testErrorCount() > 0:
3179 reporter.addLogFile(sLastScreenshotPath, 'screenshot/failure', 'Last VM screenshot');
3180 else:
3181 reporter.addLogFile(sLastScreenshotPath, 'screenshot/success', 'Last VM screenshot');
3182
3183 # Add the guest OS log if it has been requested and taken successfully.
3184 if sOsKernelLog is not None:
3185 reporter.addLogString(sOsKernelLog, 'kernel.log', 'log/guest/kernel', 'Guest OS kernel log');
3186
3187 # Add "info vgatext all" if we've got it.
3188 if sVgaText is not None:
3189 reporter.addLogString(sVgaText, 'vgatext.txt', 'info/vgatext', 'info vgatext all');
3190
3191 # Add the "info xxxx" items if we've got any.
3192 if asMiscInfos:
3193 reporter.addLogString(u''.join(asMiscInfos), 'info.txt', 'info/collection', 'A bunch of info items.');
3194
3195 # Add the host process info if we were able to retrieve it.
3196 if sHostProcessInfo is not None:
3197 reporter.log('Trying to annotate the VM process report, please stand by...');
3198 fRcTmp = self.annotateAndUploadProcessReport(sHostProcessInfo, 'vmprocess.log',
3199 'process/report/vm', 'Annotated VM process state');
3200 # Upload the raw log for manual annotation in case resolving failed.
3201 if not fRcTmp:
3202 reporter.log('Failed to annotate VM process report, uploading raw report');
3203 reporter.addLogString(sHostProcessInfo, 'vmprocess.log', 'process/report/vm', 'VM process state');
3204
3205 # Add the host process info for failed power off attempts if we were able to retrieve it.
3206 if sHostProcessInfoHung is not None:
3207 reporter.log('Trying to annotate the hung VM process report, please stand by...');
3208 fRcTmp = self.annotateAndUploadProcessReport(sHostProcessInfoHung, 'vmprocess-hung.log',
3209 'process/report/vm', 'Annotated hung VM process state');
3210 # Upload the raw log for manual annotation in case resolving failed.
3211 if not fRcTmp:
3212 reporter.log('Failed to annotate hung VM process report, uploading raw report');
3213 reporter.addLogString(sHostProcessInfoHung, 'vmprocess-hung.log', 'process/report/vm',
3214 'Hung VM process state');
3215
3216 return fRc;
3217
3218
3219 #
3220 # Some information query functions (mix).
3221 #
3222 # Methods require the VBox API. If the information is provided by both
3223 # the testboxscript as well as VBox API, we'll check if it matches.
3224 #
3225
3226 def _hasHostCpuFeature(self, sEnvVar, sEnum, fpApiMinVer, fQuiet):
3227 """
3228 Common Worker for hasHostNestedPaging() and hasHostHwVirt().
3229
3230 Returns True / False.
3231 Raises exception on environment / host mismatch.
3232 """
3233 fEnv = os.environ.get(sEnvVar, None);
3234 if fEnv is not None:
3235 fEnv = fEnv.lower() not in [ 'false', 'f', 'not', 'no', 'n', '0', ];
3236
3237 fVBox = None;
3238 self.importVBoxApi();
3239 if self.fpApiVer >= fpApiMinVer and hasattr(vboxcon, sEnum):
3240 try:
3241 fVBox = self.oVBox.host.getProcessorFeature(getattr(vboxcon, sEnum));
3242 except:
3243 if not fQuiet:
3244 reporter.logXcpt();
3245
3246 if fVBox is not None:
3247 if fEnv is not None:
3248 if fEnv != fVBox and not fQuiet:
3249 reporter.log('TestBox configuration overwritten: fVBox=%s (%s) vs. fEnv=%s (%s)'
3250 % (fVBox, sEnum, fEnv, sEnvVar));
3251 return fEnv;
3252 return fVBox;
3253 if fEnv is not None:
3254 return fEnv;
3255 return False;
3256
3257 def hasHostHwVirt(self, fQuiet = False):
3258 """
3259 Checks if hardware assisted virtualization is supported by the host.
3260
3261 Returns True / False.
3262 Raises exception on environment / host mismatch.
3263 """
3264 return self._hasHostCpuFeature('TESTBOX_HAS_HW_VIRT', 'ProcessorFeature_HWVirtEx', 3.1, fQuiet);
3265
3266 def hasHostNestedPaging(self, fQuiet = False):
3267 """
3268 Checks if nested paging is supported by the host.
3269
3270 Returns True / False.
3271 Raises exception on environment / host mismatch.
3272 """
3273 return self._hasHostCpuFeature('TESTBOX_HAS_NESTED_PAGING', 'ProcessorFeature_NestedPaging', 4.2, fQuiet) \
3274 and self.hasHostHwVirt(fQuiet);
3275
3276 def hasHostNestedHwVirt(self, fQuiet = False):
3277 """
3278 Checks if nested hardware-assisted virtualization is supported by the host.
3279
3280 Returns True / False.
3281 Raises exception on environment / host mismatch.
3282 """
3283 return self._hasHostCpuFeature('TESTBOX_HAS_NESTED_HWVIRT', 'ProcessorFeature_NestedHWVirt', 6.0, fQuiet) \
3284 and self.hasHostHwVirt(fQuiet);
3285
3286 def hasHostLongMode(self, fQuiet = False):
3287 """
3288 Checks if the host supports 64-bit guests.
3289
3290 Returns True / False.
3291 Raises exception on environment / host mismatch.
3292 """
3293 # Note that the testboxscript doesn't export this variable atm.
3294 return self._hasHostCpuFeature('TESTBOX_HAS_LONG_MODE', 'ProcessorFeature_LongMode', 3.1, fQuiet);
3295
3296 def getHostCpuCount(self, fQuiet = False):
3297 """
3298 Returns the number of CPUs on the host.
3299
3300 Returns True / False.
3301 Raises exception on environment / host mismatch.
3302 """
3303 cEnv = os.environ.get('TESTBOX_CPU_COUNT', None);
3304 if cEnv is not None:
3305 cEnv = int(cEnv);
3306
3307 try:
3308 cVBox = self.oVBox.host.processorOnlineCount;
3309 except:
3310 if not fQuiet:
3311 reporter.logXcpt();
3312 cVBox = None;
3313
3314 if cVBox is not None:
3315 if cEnv is not None:
3316 assert cVBox == cEnv, 'Misconfigured TestBox: VBox: %u CPUs, testboxscript: %u CPUs' % (cVBox, cEnv);
3317 return cVBox;
3318 if cEnv is not None:
3319 return cEnv;
3320 return 1;
3321
3322 def _getHostCpuDesc(self, fQuiet = False):
3323 """
3324 Internal method used for getting the host CPU description from VBoxSVC.
3325 Returns description string, on failure an empty string is returned.
3326 """
3327 try:
3328 return self.oVBox.host.getProcessorDescription(0);
3329 except:
3330 if not fQuiet:
3331 reporter.logXcpt();
3332 return '';
3333
3334 def isHostCpuAmd(self, fQuiet = False):
3335 """
3336 Checks if the host CPU vendor is AMD.
3337
3338 Returns True / False.
3339 """
3340 sCpuDesc = self._getHostCpuDesc(fQuiet);
3341 return 'AMD' in sCpuDesc or sCpuDesc == 'AuthenticAMD';
3342
3343 def isHostCpuIntel(self, fQuiet = False):
3344 """
3345 Checks if the host CPU vendor is Intel.
3346
3347 Returns True / False.
3348 """
3349 sCpuDesc = self._getHostCpuDesc(fQuiet);
3350 return sCpuDesc.startswith("Intel") or sCpuDesc == 'GenuineIntel';
3351
3352 def isHostCpuVia(self, fQuiet = False):
3353 """
3354 Checks if the host CPU vendor is VIA (or Centaur).
3355
3356 Returns True / False.
3357 """
3358 sCpuDesc = self._getHostCpuDesc(fQuiet);
3359 return sCpuDesc.startswith("VIA") or sCpuDesc == 'CentaurHauls';
3360
3361 def isHostCpuShanghai(self, fQuiet = False):
3362 """
3363 Checks if the host CPU vendor is Shanghai (or Zhaoxin).
3364
3365 Returns True / False.
3366 """
3367 sCpuDesc = self._getHostCpuDesc(fQuiet);
3368 return sCpuDesc.startswith("ZHAOXIN") or sCpuDesc.strip(' ') == 'Shanghai';
3369
3370 def isHostCpuP4(self, fQuiet = False):
3371 """
3372 Checks if the host CPU is a Pentium 4 / Pentium D.
3373
3374 Returns True / False.
3375 """
3376 if not self.isHostCpuIntel(fQuiet):
3377 return False;
3378
3379 (uFamilyModel, _, _, _) = self.oVBox.host.getProcessorCPUIDLeaf(0, 0x1, 0);
3380 return ((uFamilyModel >> 8) & 0xf) == 0xf;
3381
3382 def hasRawModeSupport(self, fQuiet = False):
3383 """
3384 Checks if raw-mode is supported by VirtualBox that the testbox is
3385 configured for it.
3386
3387 Returns True / False.
3388 Raises no exceptions.
3389
3390 Note! Differs from the rest in that we don't require the
3391 TESTBOX_WITH_RAW_MODE value to match the API. It is
3392 sometimes helpful to disable raw-mode on individual
3393 test boxes. (This probably goes for
3394 """
3395 # The environment variable can be used to disable raw-mode.
3396 fEnv = os.environ.get('TESTBOX_WITH_RAW_MODE', None);
3397 if fEnv is not None:
3398 fEnv = fEnv.lower() not in [ 'false', 'f', 'not', 'no', 'n', '0', ];
3399 if fEnv is False:
3400 return False;
3401
3402 # Starting with 5.0 GA / RC2 the API can tell us whether VBox was built
3403 # with raw-mode support or not.
3404 self.importVBoxApi();
3405 if self.fpApiVer >= 5.0:
3406 try:
3407 fVBox = self.oVBox.systemProperties.rawModeSupported;
3408 except:
3409 if not fQuiet:
3410 reporter.logXcpt();
3411 fVBox = True;
3412 if fVBox is False:
3413 return False;
3414
3415 return True;
3416
3417 #
3418 # Testdriver execution methods.
3419 #
3420
3421 def handleTask(self, oTask, sMethod):
3422 """
3423 Callback method for handling unknown tasks in the various run loops.
3424
3425 The testdriver should override this if it already tasks running when
3426 calling startVmAndConnectToTxsViaTcp, txsRunTest or similar methods.
3427 Call super to handle unknown tasks.
3428
3429 Returns True if handled, False if not.
3430 """
3431 reporter.error('%s: unknown task %s' % (sMethod, oTask));
3432 return False;
3433
3434 def txsDoTask(self, oSession, oTxsSession, fnAsync, aArgs):
3435 """
3436 Generic TXS task wrapper which waits both on the TXS and the session tasks.
3437
3438 Returns False on error, logged.
3439
3440 Returns task result on success.
3441 """
3442 # All async methods ends with the following two args.
3443 cMsTimeout = aArgs[-2];
3444 fIgnoreErrors = aArgs[-1];
3445
3446 fRemoveVm = self.addTask(oSession);
3447 fRemoveTxs = self.addTask(oTxsSession);
3448
3449 rc = fnAsync(*aArgs); # pylint: disable=star-args
3450 if rc is True:
3451 rc = False;
3452 oTask = self.waitForTasks(cMsTimeout + 1);
3453 if oTask is oTxsSession:
3454 if oTxsSession.isSuccess():
3455 rc = oTxsSession.getResult();
3456 elif fIgnoreErrors is True:
3457 reporter.log( 'txsDoTask: task failed (%s)' % (oTxsSession.getLastReply()[1],));
3458 else:
3459 reporter.error('txsDoTask: task failed (%s)' % (oTxsSession.getLastReply()[1],));
3460 else:
3461 oTxsSession.cancelTask();
3462 if oTask is None:
3463 if fIgnoreErrors is True:
3464 reporter.log( 'txsDoTask: The task timed out.');
3465 else:
3466 reporter.errorTimeout('txsDoTask: The task timed out.');
3467 elif oTask is oSession:
3468 reporter.error('txsDoTask: The VM terminated unexpectedly');
3469 else:
3470 if fIgnoreErrors is True:
3471 reporter.log( 'txsDoTask: An unknown task %s was returned' % (oTask,));
3472 else:
3473 reporter.error('txsDoTask: An unknown task %s was returned' % (oTask,));
3474 else:
3475 reporter.error('txsDoTask: fnAsync returned %s' % (rc,));
3476
3477 if fRemoveTxs:
3478 self.removeTask(oTxsSession);
3479 if fRemoveVm:
3480 self.removeTask(oSession);
3481 return rc;
3482
3483 # pylint: disable=missing-docstring
3484
3485 def txsDisconnect(self, oSession, oTxsSession, cMsTimeout = 30000, fIgnoreErrors = False):
3486 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncDisconnect,
3487 (self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3488
3489 def txsUuid(self, oSession, oTxsSession, cMsTimeout = 30000, fIgnoreErrors = False):
3490 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUuid,
3491 (self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3492
3493 def txsMkDir(self, oSession, oTxsSession, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False):
3494 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncMkDir,
3495 (sRemoteDir, fMode, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3496
3497 def txsMkDirPath(self, oSession, oTxsSession, sRemoteDir, fMode = 0o700, cMsTimeout = 30000, fIgnoreErrors = False):
3498 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncMkDirPath,
3499 (sRemoteDir, fMode, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3500
3501 def txsMkSymlink(self, oSession, oTxsSession, sLinkTarget, sLink, cMsTimeout = 30000, fIgnoreErrors = False):
3502 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncMkSymlink,
3503 (sLinkTarget, sLink, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3504
3505 def txsRmDir(self, oSession, oTxsSession, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False):
3506 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmDir,
3507 (sRemoteDir, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3508
3509 def txsRmFile(self, oSession, oTxsSession, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
3510 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmFile,
3511 (sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3512
3513 def txsRmSymlink(self, oSession, oTxsSession, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False):
3514 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmSymlink,
3515 (sRemoteSymlink, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3516
3517 def txsRmTree(self, oSession, oTxsSession, sRemoteTree, cMsTimeout = 30000, fIgnoreErrors = False):
3518 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncRmTree,
3519 (sRemoteTree, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3520
3521 def txsIsDir(self, oSession, oTxsSession, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False):
3522 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncIsDir,
3523 (sRemoteDir, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3524
3525 def txsIsFile(self, oSession, oTxsSession, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
3526 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncIsFile,
3527 (sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3528
3529 def txsIsSymlink(self, oSession, oTxsSession, sRemoteSymlink, cMsTimeout = 30000, fIgnoreErrors = False):
3530 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncIsSymlink,
3531 (sRemoteSymlink, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3532
3533 def txsUploadFile(self, oSession, oTxsSession, sLocalFile, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
3534 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUploadFile, \
3535 (sLocalFile, sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3536
3537 def txsUploadString(self, oSession, oTxsSession, sContent, sRemoteFile, cMsTimeout = 30000, fIgnoreErrors = False):
3538 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUploadString, \
3539 (sContent, sRemoteFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3540
3541 def txsDownloadFile(self, oSession, oTxsSession, sRemoteFile, sLocalFile, cMsTimeout = 30000, fIgnoreErrors = False):
3542 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncDownloadFile, \
3543 (sRemoteFile, sLocalFile, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3544
3545 def txsDownloadFiles(self, oSession, oTxsSession, asFiles, fIgnoreErrors = False):
3546 """
3547 Convenience function to get files from the guest and stores it
3548 into the scratch directory for later (manual) review.
3549
3550 Returns True on success.
3551
3552 Returns False on failure, logged.
3553 """
3554 fRc = True;
3555 for sGstFile in asFiles:
3556 sTmpFile = os.path.join(self.sScratchPath, 'tmp-' + os.path.basename(sGstFile));
3557 reporter.log2('Downloading file "%s" to "%s" ...' % (sGstFile, sTmpFile));
3558 # First try to remove (unlink) an existing temporary file, as we don't truncate the file.
3559 try: os.unlink(sTmpFile);
3560 except: pass;
3561 ## @todo Check for already existing files on the host and create a new
3562 # name for the current file to download.
3563 fRc = self.txsDownloadFile(oSession, oTxsSession, sGstFile, sTmpFile, 30 * 1000, fIgnoreErrors);
3564 if fRc:
3565 reporter.addLogFile(sTmpFile, 'misc/other', 'guest - ' + sGstFile);
3566 else:
3567 if fIgnoreErrors is not True:
3568 reporter.error('error downloading file "%s" to "%s"' % (sGstFile, sTmpFile));
3569 return fRc;
3570 reporter.log('warning: file "%s" was not downloaded, ignoring.' % (sGstFile,));
3571 return True;
3572
3573 def txsDownloadString(self, oSession, oTxsSession, sRemoteFile, sEncoding = 'utf-8', fIgnoreEncodingErrors = True,
3574 cMsTimeout = 30000, fIgnoreErrors = False):
3575 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncDownloadString,
3576 (sRemoteFile, sEncoding, fIgnoreEncodingErrors, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3577
3578 def txsUnpackFile(self, oSession, oTxsSession, sRemoteFile, sRemoteDir, cMsTimeout = 30000, fIgnoreErrors = False):
3579 return self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUnpackFile, \
3580 (sRemoteFile, sRemoteDir, self.adjustTimeoutMs(cMsTimeout), fIgnoreErrors));
3581
3582 # pylint: enable=missing-docstring
3583
3584 def txsCdWait(self,
3585 oSession, # type: vboxwrappers.SessionWrapper
3586 oTxsSession, # type: txsclient.Session
3587 cMsTimeout = 30000, # type: int
3588 sFile = None # type: String
3589 ): # -> bool
3590 """
3591 Mostly an internal helper for txsRebootAndReconnectViaTcp and
3592 startVmAndConnectToTxsViaTcp that waits for the CDROM drive to become
3593 ready. It does this by polling for a file it knows to exist on the CD.
3594
3595 Returns True on success.
3596
3597 Returns False on failure, logged.
3598 """
3599
3600 if sFile is None:
3601 sFile = 'valkit.txt';
3602
3603 reporter.log('txsCdWait: Waiting for file "%s" to become available ...' % (sFile,));
3604
3605 fRemoveVm = self.addTask(oSession);
3606 fRemoveTxs = self.addTask(oTxsSession);
3607 cMsTimeout = self.adjustTimeoutMs(cMsTimeout);
3608 msStart = base.timestampMilli();
3609 cMsTimeout2 = cMsTimeout;
3610 fRc = oTxsSession.asyncIsFile('${CDROM}/%s' % (sFile,), cMsTimeout2);
3611 if fRc is True:
3612 while True:
3613 # wait for it to complete.
3614 oTask = self.waitForTasks(cMsTimeout2 + 1);
3615 if oTask is not oTxsSession:
3616 oTxsSession.cancelTask();
3617 if oTask is None:
3618 reporter.errorTimeout('txsCdWait: The task timed out (after %s ms).'
3619 % (base.timestampMilli() - msStart,));
3620 elif oTask is oSession:
3621 reporter.error('txsCdWait: The VM terminated unexpectedly');
3622 else:
3623 reporter.error('txsCdWait: An unknown task %s was returned' % (oTask,));
3624 fRc = False;
3625 break;
3626 if oTxsSession.isSuccess():
3627 break;
3628
3629 # Check for timeout.
3630 cMsElapsed = base.timestampMilli() - msStart;
3631 if cMsElapsed >= cMsTimeout:
3632 reporter.error('txsCdWait: timed out');
3633 fRc = False;
3634 break;
3635 # delay.
3636 self.sleep(1);
3637
3638 # resubmit the task.
3639 cMsTimeout2 = msStart + cMsTimeout - base.timestampMilli();
3640 if cMsTimeout2 < 500:
3641 cMsTimeout2 = 500;
3642 fRc = oTxsSession.asyncIsFile('${CDROM}/%s' % (sFile,), cMsTimeout2);
3643 if fRc is not True:
3644 reporter.error('txsCdWait: asyncIsFile failed');
3645 break;
3646 else:
3647 reporter.error('txsCdWait: asyncIsFile failed');
3648
3649 if not fRc:
3650 # Do some diagnosis to find out why this failed.
3651 ## @todo Identify guest OS type and only run one of the following commands.
3652 fIsNotWindows = True;
3653 reporter.log('txsCdWait: Listing root contents of ${CDROM}:');
3654 if fIsNotWindows:
3655 reporter.log('txsCdWait: Tiggering udevadm ...');
3656 oTxsSession.syncExec("/sbin/udevadm", ("/sbin/udevadm", "trigger", "--verbose"), fIgnoreErrors = True);
3657 time.sleep(15);
3658 oTxsSession.syncExec("/bin/ls", ("/bin/ls", "-al", "${CDROM}"), fIgnoreErrors = True);
3659 reporter.log('txsCdWait: Listing media directory:');
3660 oTxsSession.syncExec('/bin/ls', ('/bin/ls', '-l', '-a', '-R', '/media'), fIgnoreErrors = True);
3661 reporter.log('txsCdWait: Listing mount points / drives:');
3662 oTxsSession.syncExec('/bin/mount', ('/bin/mount',), fIgnoreErrors = True);
3663 oTxsSession.syncExec('/bin/cat', ('/bin/cat', '/etc/fstab'), fIgnoreErrors = True);
3664 oTxsSession.syncExec('/bin/dmesg', ('/bin/dmesg',), fIgnoreErrors = True);
3665 oTxsSession.syncExec('/usr/bin/lshw', ('/usr/bin/lshw', '-c', 'disk'), fIgnoreErrors = True);
3666 oTxsSession.syncExec('/bin/journalctl',
3667 ('/bin/journalctl', '-x', '-b'), fIgnoreErrors = True);
3668 oTxsSession.syncExec('/bin/journalctl',
3669 ('/bin/journalctl', '-x', '-b', '/usr/lib/udisks2/udisksd'), fIgnoreErrors = True);
3670 oTxsSession.syncExec('/usr/bin/udisksctl',
3671 ('/usr/bin/udisksctl', 'info', '-b', '/dev/sr0'), fIgnoreErrors = True);
3672 oTxsSession.syncExec('/bin/systemctl',
3673 ('/bin/systemctl', 'status', 'udisks2'), fIgnoreErrors = True);
3674 oTxsSession.syncExec('/bin/ps',
3675 ('/bin/ps', '-a', '-u', '-x'), fIgnoreErrors = True);
3676 reporter.log('txsCdWait: Mounting manually ...');
3677 for _ in range(3):
3678 oTxsSession.syncExec('/bin/mount', ('/bin/mount', '/dev/sr0', '${CDROM}'), fIgnoreErrors = True);
3679 time.sleep(5);
3680 reporter.log('txsCdWait: Re-Listing media directory:');
3681 oTxsSession.syncExec('/bin/ls', ('/bin/ls', '-l', '-a', '-R', '/media'), fIgnoreErrors = True);
3682 else:
3683 # ASSUMES that we always install Windows on drive C right now.
3684 sWinDir = "C:\\Windows\\System32\\";
3685 # Should work since WinXP Pro.
3686 oTxsSession.syncExec(sWinDir + "wbem\\WMIC.exe",
3687 ("WMIC.exe", "logicaldisk", "get",
3688 "deviceid, volumename, description"),
3689 fIgnoreErrors = True);
3690 oTxsSession.syncExec(sWinDir + " cmd.exe",
3691 ('cmd.exe', '/C', 'dir', '${CDROM}'),
3692 fIgnoreErrors = True);
3693
3694 if fRemoveTxs:
3695 self.removeTask(oTxsSession);
3696 if fRemoveVm:
3697 self.removeTask(oSession);
3698 return fRc;
3699
3700 def txsDoConnectViaTcp(self, oSession, cMsTimeout, fNatForwardingForTxs = False):
3701 """
3702 Mostly an internal worker for connecting to TXS via TCP used by the
3703 *ViaTcp methods.
3704
3705 Returns a tuplet with True/False and TxsSession/None depending on the
3706 result. Errors are logged.
3707 """
3708
3709 reporter.log2('txsDoConnectViaTcp: oSession=%s, cMsTimeout=%s, fNatForwardingForTxs=%s'
3710 % (oSession, cMsTimeout, fNatForwardingForTxs));
3711
3712 cMsTimeout = self.adjustTimeoutMs(cMsTimeout);
3713 oTxsConnect = oSession.txsConnectViaTcp(cMsTimeout, fNatForwardingForTxs = fNatForwardingForTxs);
3714 if oTxsConnect is not None:
3715 self.addTask(oTxsConnect);
3716 fRemoveVm = self.addTask(oSession);
3717 oTask = self.waitForTasks(cMsTimeout + 1);
3718 reporter.log2('txsDoConnectViaTcp: waitForTasks returned %s' % (oTask,));
3719 self.removeTask(oTxsConnect);
3720 if oTask is oTxsConnect:
3721 oTxsSession = oTxsConnect.getResult();
3722 if oTxsSession is not None:
3723 reporter.log('txsDoConnectViaTcp: Connected to TXS on %s.' % (oTxsSession.oTransport.sHostname,));
3724 return (True, oTxsSession);
3725
3726 reporter.error('txsDoConnectViaTcp: failed to connect to TXS.');
3727 else:
3728 oTxsConnect.cancelTask();
3729 if oTask is None:
3730 reporter.errorTimeout('txsDoConnectViaTcp: connect stage 1 timed out');
3731 elif oTask is oSession:
3732 oSession.reportPrematureTermination('txsDoConnectViaTcp: ');
3733 else:
3734 reporter.error('txsDoConnectViaTcp: unknown/wrong task %s' % (oTask,));
3735 if fRemoveVm:
3736 self.removeTask(oSession);
3737 else:
3738 reporter.error('txsDoConnectViaTcp: txsConnectViaTcp failed');
3739 return (False, None);
3740
3741 def startVmAndConnectToTxsViaTcp(self, sVmName, fCdWait = False, cMsTimeout = 15*60000, \
3742 cMsCdWait = 30000, sFileCdWait = None, \
3743 fNatForwardingForTxs = False):
3744 """
3745 Starts the specified VM and tries to connect to its TXS via TCP.
3746 The VM will be powered off if TXS doesn't respond before the specified
3747 time has elapsed.
3748
3749 Returns a the VM and TXS sessions (a two tuple) on success. The VM
3750 session is in the task list, the TXS session is not.
3751 Returns (None, None) on failure, fully logged.
3752 """
3753
3754 # Zap the guest IP to make sure we're not getting a stale entry
3755 # (unless we're restoring the VM of course).
3756 oTestVM = self.oTestVmSet.findTestVmByName(sVmName) if self.oTestVmSet is not None else None;
3757 if oTestVM is None \
3758 or oTestVM.fSnapshotRestoreCurrent is False:
3759 try:
3760 oSession1 = self.openSession(self.getVmByName(sVmName));
3761 oSession1.delGuestPropertyValue('/VirtualBox/GuestInfo/Net/0/V4/IP');
3762 oSession1.saveSettings(True);
3763 del oSession1;
3764 except:
3765 reporter.logXcpt();
3766
3767 # Start the VM.
3768 reporter.log('startVmAndConnectToTxsViaTcp: Starting(/preparing) "%s" (timeout %s s)...' % (sVmName, cMsTimeout / 1000));
3769 reporter.flushall();
3770 oSession = self.startVmByName(sVmName);
3771 if oSession is not None:
3772 # Connect to TXS.
3773 reporter.log2('startVmAndConnectToTxsViaTcp: Started(/prepared) "%s", connecting to TXS ...' % (sVmName,));
3774 (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, cMsTimeout, fNatForwardingForTxs);
3775 if fRc is True:
3776 if fCdWait:
3777 # Wait for CD?
3778 reporter.log2('startVmAndConnectToTxsViaTcp: Waiting for file "%s" to become available ...' % (sFileCdWait,));
3779 fRc = self.txsCdWait(oSession, oTxsSession, cMsCdWait, sFileCdWait);
3780 if fRc is not True:
3781 reporter.error('startVmAndConnectToTxsViaTcp: txsCdWait failed');
3782 if fRc is True:
3783 # Success!
3784 return (oSession, oTxsSession);
3785 else:
3786 reporter.error('startVmAndConnectToTxsViaTcp: txsDoConnectViaTcp failed');
3787 # If something went wrong while waiting for TXS to be started - take VM screenshot before terminate it
3788 self.terminateVmBySession(oSession);
3789 return (None, None);
3790
3791 def txsRebootAndReconnectViaTcp(self, oSession, oTxsSession, fCdWait = False, cMsTimeout = 15*60000, \
3792 cMsCdWait = 30000, sFileCdWait = None, fNatForwardingForTxs = False):
3793 """
3794 Executes the TXS reboot command
3795
3796 Returns A tuple of True and the new TXS session on success.
3797
3798 Returns A tuple of False and either the old TXS session or None on failure.
3799 """
3800 reporter.log2('txsRebootAndReconnect: cMsTimeout=%u' % (cMsTimeout,));
3801
3802 #
3803 # This stuff is a bit complicated because of rebooting being kind of
3804 # disruptive to the TXS and such... The protocol is that TXS will:
3805 # - ACK the reboot command.
3806 # - Shutdown the transport layer, implicitly disconnecting us.
3807 # - Execute the reboot operation.
3808 # - On failure, it will be re-init the transport layer and be
3809 # available pretty much immediately. UUID unchanged.
3810 # - On success, it will be respawed after the reboot (hopefully),
3811 # with a different UUID.
3812 #
3813 fRc = False;
3814 iStart = base.timestampMilli();
3815
3816 # Get UUID.
3817 cMsTimeout2 = min(60000, cMsTimeout);
3818 sUuidBefore = self.txsUuid(oSession, oTxsSession, self.adjustTimeoutMs(cMsTimeout2, 60000));
3819 if sUuidBefore is not False:
3820 # Reboot.
3821 cMsElapsed = base.timestampMilli() - iStart;
3822 cMsTimeout2 = cMsTimeout - cMsElapsed;
3823 fRc = self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncReboot,
3824 (self.adjustTimeoutMs(cMsTimeout2, 60000), False));
3825 if fRc is True:
3826 # Reconnect.
3827 if fNatForwardingForTxs is True:
3828 self.sleep(22); # NAT fudge - Two fixes are wanted: 1. TXS connect retries. 2. Main API reboot/reset hint.
3829 cMsElapsed = base.timestampMilli() - iStart;
3830 (fRc, oTxsSession) = self.txsDoConnectViaTcp(oSession, cMsTimeout - cMsElapsed, fNatForwardingForTxs);
3831 if fRc is True:
3832 # Check the UUID.
3833 cMsElapsed = base.timestampMilli() - iStart;
3834 cMsTimeout2 = min(60000, cMsTimeout - cMsElapsed);
3835 sUuidAfter = self.txsDoTask(oSession, oTxsSession, oTxsSession.asyncUuid,
3836 (self.adjustTimeoutMs(cMsTimeout2, 60000), False));
3837 if sUuidBefore is not False:
3838 if sUuidAfter != sUuidBefore:
3839 reporter.log('The guest rebooted (UUID %s -> %s)' % (sUuidBefore, sUuidAfter))
3840
3841 # Do CD wait if specified.
3842 if fCdWait:
3843 fRc = self.txsCdWait(oSession, oTxsSession, cMsCdWait, sFileCdWait);
3844 if fRc is not True:
3845 reporter.error('txsRebootAndReconnectViaTcp: txsCdWait failed');
3846 else:
3847 reporter.error('txsRebootAndReconnectViaTcp: failed to get UUID (after)');
3848 else:
3849 reporter.error('txsRebootAndReconnectViaTcp: did not reboot (UUID %s)' % (sUuidBefore,));
3850 else:
3851 reporter.error('txsRebootAndReconnectViaTcp: txsDoConnectViaTcp failed');
3852 else:
3853 reporter.error('txsRebootAndReconnectViaTcp: reboot failed');
3854 else:
3855 reporter.error('txsRebootAndReconnectViaTcp: failed to get UUID (before)');
3856 return (fRc, oTxsSession);
3857
3858 # pylint: disable=too-many-locals,too-many-arguments
3859
3860 def txsRunTest(self, oTxsSession, sTestName, cMsTimeout, sExecName, asArgs = (), asAddEnv = (), sAsUser = "",
3861 fCheckSessionStatus = False):
3862 """
3863 Executes the specified test task, waiting till it completes or times out.
3864
3865 The VM session (if any) must be in the task list.
3866
3867 Returns True if we executed the task and nothing abnormal happend.
3868 Query the process status from the TXS session.
3869
3870 Returns False if some unexpected task was signalled or we failed to
3871 submit the job.
3872
3873 If fCheckSessionStatus is set to True, the overall session status will be
3874 taken into account and logged as an error on failure.
3875 """
3876 reporter.testStart(sTestName);
3877 reporter.log2('txsRunTest: cMsTimeout=%u sExecName=%s asArgs=%s' % (cMsTimeout, sExecName, asArgs));
3878
3879 # Submit the job.
3880 fRc = False;
3881 if oTxsSession.asyncExec(sExecName, asArgs, asAddEnv, sAsUser, cMsTimeout = self.adjustTimeoutMs(cMsTimeout)):
3882 self.addTask(oTxsSession);
3883
3884 # Wait for the job to complete.
3885 while True:
3886 oTask = self.waitForTasks(cMsTimeout + 1);
3887 if oTask is None:
3888 if fCheckSessionStatus:
3889 reporter.error('txsRunTest: waitForTasks for test "%s" timed out' % (sTestName,));
3890 else:
3891 reporter.log('txsRunTest: waitForTasks for test "%s" timed out' % (sTestName,));
3892 break;
3893 if oTask is oTxsSession:
3894 if fCheckSessionStatus \
3895 and not oTxsSession.isSuccess():
3896 reporter.error('txsRunTest: Test "%s" failed' % (sTestName,));
3897 else:
3898 fRc = True;
3899 reporter.log('txsRunTest: isSuccess=%s getResult=%s' \
3900 % (oTxsSession.isSuccess(), oTxsSession.getResult()));
3901 break;
3902 if not self.handleTask(oTask, 'txsRunTest'):
3903 break;
3904
3905 self.removeTask(oTxsSession);
3906 if not oTxsSession.pollTask():
3907 oTxsSession.cancelTask();
3908 else:
3909 reporter.error('txsRunTest: asyncExec failed');
3910
3911 reporter.testDone();
3912 return fRc;
3913
3914 def txsRunTestRedirectStd(self, oTxsSession, sTestName, cMsTimeout, sExecName, asArgs = (), asAddEnv = (), sAsUser = "",
3915 oStdIn = '/dev/null', oStdOut = '/dev/null', oStdErr = '/dev/null', oTestPipe = '/dev/null'):
3916 """
3917 Executes the specified test task, waiting till it completes or times out,
3918 redirecting stdin, stdout and stderr to the given objects.
3919
3920 The VM session (if any) must be in the task list.
3921
3922 Returns True if we executed the task and nothing abnormal happend.
3923 Query the process status from the TXS session.
3924
3925 Returns False if some unexpected task was signalled or we failed to
3926 submit the job.
3927 """
3928 reporter.testStart(sTestName);
3929 reporter.log2('txsRunTestRedirectStd: cMsTimeout=%u sExecName=%s asArgs=%s' % (cMsTimeout, sExecName, asArgs));
3930
3931 # Submit the job.
3932 fRc = False;
3933 if oTxsSession.asyncExecEx(sExecName, asArgs, asAddEnv, oStdIn, oStdOut, oStdErr,
3934 oTestPipe, sAsUser, cMsTimeout = self.adjustTimeoutMs(cMsTimeout)):
3935 self.addTask(oTxsSession);
3936
3937 # Wait for the job to complete.
3938 while True:
3939 oTask = self.waitForTasks(cMsTimeout + 1);
3940 if oTask is None:
3941 reporter.log('txsRunTestRedirectStd: waitForTasks timed out');
3942 break;
3943 if oTask is oTxsSession:
3944 fRc = True;
3945 reporter.log('txsRunTestRedirectStd: isSuccess=%s getResult=%s'
3946 % (oTxsSession.isSuccess(), oTxsSession.getResult()));
3947 break;
3948 if not self.handleTask(oTask, 'txsRunTestRedirectStd'):
3949 break;
3950
3951 self.removeTask(oTxsSession);
3952 if not oTxsSession.pollTask():
3953 oTxsSession.cancelTask();
3954 else:
3955 reporter.error('txsRunTestRedirectStd: asyncExec failed');
3956
3957 reporter.testDone();
3958 return fRc;
3959
3960 def txsRunTest2(self, oTxsSession1, oTxsSession2, sTestName, cMsTimeout,
3961 sExecName1, asArgs1,
3962 sExecName2, asArgs2,
3963 asAddEnv1 = (), sAsUser1 = '', fWithTestPipe1 = True,
3964 asAddEnv2 = (), sAsUser2 = '', fWithTestPipe2 = True):
3965 """
3966 Executes the specified test tasks, waiting till they complete or
3967 times out. The 1st task is started after the 2nd one.
3968
3969 The VM session (if any) must be in the task list.
3970
3971 Returns True if we executed the task and nothing abnormal happend.
3972 Query the process status from the TXS sessions.
3973
3974 Returns False if some unexpected task was signalled or we failed to
3975 submit the job.
3976 """
3977 reporter.testStart(sTestName);
3978
3979 # Submit the jobs.
3980 fRc = False;
3981 if oTxsSession1.asyncExec(sExecName1, asArgs1, asAddEnv1, sAsUser1, fWithTestPipe1, '1-',
3982 self.adjustTimeoutMs(cMsTimeout)):
3983 self.addTask(oTxsSession1);
3984
3985 self.sleep(2); # fudge! grr
3986
3987 if oTxsSession2.asyncExec(sExecName2, asArgs2, asAddEnv2, sAsUser2, fWithTestPipe2, '2-',
3988 self.adjustTimeoutMs(cMsTimeout)):
3989 self.addTask(oTxsSession2);
3990
3991 # Wait for the jobs to complete.
3992 cPendingJobs = 2;
3993 while True:
3994 oTask = self.waitForTasks(cMsTimeout + 1);
3995 if oTask is None:
3996 reporter.log('txsRunTest2: waitForTasks timed out');
3997 break;
3998
3999 if oTask is oTxsSession1 or oTask is oTxsSession2:
4000 if oTask is oTxsSession1: iTask = 1;
4001 else: iTask = 2;
4002 reporter.log('txsRunTest2: #%u - isSuccess=%s getResult=%s' \
4003 % (iTask, oTask.isSuccess(), oTask.getResult()));
4004 self.removeTask(oTask);
4005 cPendingJobs -= 1;
4006 if cPendingJobs <= 0:
4007 fRc = True;
4008 break;
4009
4010 elif not self.handleTask(oTask, 'txsRunTest'):
4011 break;
4012
4013 self.removeTask(oTxsSession2);
4014 if not oTxsSession2.pollTask():
4015 oTxsSession2.cancelTask();
4016 else:
4017 reporter.error('txsRunTest2: asyncExec #2 failed');
4018
4019 self.removeTask(oTxsSession1);
4020 if not oTxsSession1.pollTask():
4021 oTxsSession1.cancelTask();
4022 else:
4023 reporter.error('txsRunTest2: asyncExec #1 failed');
4024
4025 reporter.testDone();
4026 return fRc;
4027
4028 # pylint: enable=too-many-locals,too-many-arguments
4029
4030
4031 #
4032 # Working with test results via serial port.
4033 #
4034
4035 class TxsMonitorComFile(base.TdTaskBase):
4036 """
4037 Class that monitors a COM output file.
4038 """
4039
4040 def __init__(self, sComRawFile, asStopWords = None):
4041 base.TdTaskBase.__init__(self, utils.getCallerName());
4042 self.sComRawFile = sComRawFile;
4043 self.oStopRegExp = re.compile('\\b(' + '|'.join(asStopWords if asStopWords else ('PASSED', 'FAILED',)) + ')\\b');
4044 self.sResult = None; ##< The result.
4045 self.cchDisplayed = 0; ##< Offset into the file string of what we've already fed to the logger.
4046
4047 def toString(self):
4048 return '<%s sComRawFile=%s oStopRegExp=%s sResult=%s cchDisplayed=%s>' \
4049 % (base.TdTaskBase.toString(self), self.sComRawFile, self.oStopRegExp, self.sResult, self.cchDisplayed,);
4050
4051 def pollTask(self, fLocked = False):
4052 """
4053 Overrides TdTaskBase.pollTask() for the purpose of polling the file.
4054 """
4055 if not fLocked:
4056 self.lockTask();
4057
4058 sFile = utils.noxcptReadFile(self.sComRawFile, '', 'rU');
4059 if len(sFile) > self.cchDisplayed:
4060 sNew = sFile[self.cchDisplayed:];
4061 oMatch = self.oStopRegExp.search(sNew);
4062 if oMatch:
4063 # Done! Get result, flush all the output and signal the task.
4064 self.sResult = oMatch.group(1);
4065 for sLine in sNew.split('\n'):
4066 reporter.log('COM OUTPUT: %s' % (sLine,));
4067 self.cchDisplayed = len(sFile);
4068 self.signalTaskLocked();
4069 else:
4070 # Output whole lines only.
4071 offNewline = sFile.find('\n', self.cchDisplayed);
4072 while offNewline >= 0:
4073 reporter.log('COM OUTPUT: %s' % (sFile[self.cchDisplayed:offNewline]))
4074 self.cchDisplayed = offNewline + 1;
4075 offNewline = sFile.find('\n', self.cchDisplayed);
4076
4077 fRet = self.fSignalled;
4078 if not fLocked:
4079 self.unlockTask();
4080 return fRet;
4081
4082 # Our stuff.
4083 def getResult(self):
4084 """
4085 Returns the connected TXS session object on success.
4086 Returns None on failure or if the task has not yet completed.
4087 """
4088 self.oCv.acquire();
4089 sResult = self.sResult;
4090 self.oCv.release();
4091 return sResult;
4092
4093 def cancelTask(self):
4094 """ Cancels the task. """
4095 self.signalTask();
4096 return True;
4097
4098
4099 def monitorComRawFile(self, oSession, sComRawFile, cMsTimeout = 15*60000, asStopWords = None):
4100 """
4101 Monitors the COM output file for stop words (PASSED and FAILED by default).
4102
4103 Returns the stop word.
4104 Returns None on VM error and timeout.
4105 """
4106
4107 reporter.log2('monitorComRawFile: oSession=%s, cMsTimeout=%s, sComRawFile=%s' % (oSession, cMsTimeout, sComRawFile));
4108
4109 oMonitorTask = self.TxsMonitorComFile(sComRawFile, asStopWords);
4110 self.addTask(oMonitorTask);
4111
4112 cMsTimeout = self.adjustTimeoutMs(cMsTimeout);
4113 oTask = self.waitForTasks(cMsTimeout + 1);
4114 reporter.log2('monitorComRawFile: waitForTasks returned %s' % (oTask,));
4115
4116 if oTask is not oMonitorTask:
4117 oMonitorTask.cancelTask();
4118 self.removeTask(oMonitorTask);
4119
4120 oMonitorTask.pollTask();
4121 return oMonitorTask.getResult();
4122
4123
4124 def runVmAndMonitorComRawFile(self, sVmName, sComRawFile, cMsTimeout = 15*60000, asStopWords = None):
4125 """
4126 Runs the specified VM and monitors the given COM output file for stop
4127 words (PASSED and FAILED by default).
4128
4129 The caller is assumed to have configured the VM to use the given
4130 file. The method will take no action to verify this.
4131
4132 Returns the stop word.
4133 Returns None on VM error and timeout.
4134 """
4135
4136 # Start the VM.
4137 reporter.log('runVmAndMonitorComRawFile: Starting(/preparing) "%s" (timeout %s s)...' % (sVmName, cMsTimeout / 1000));
4138 reporter.flushall();
4139 oSession = self.startVmByName(sVmName);
4140 if oSession is not None:
4141 # Let it run and then terminate it.
4142 sRet = self.monitorComRawFile(oSession, sComRawFile, cMsTimeout, asStopWords);
4143 self.terminateVmBySession(oSession);
4144 else:
4145 sRet = None;
4146 return sRet;
4147
4148 #
4149 # Other stuff
4150 #
4151
4152 def waitForGAs(self,
4153 oSession, # type: vboxwrappers.SessionWrapper
4154 cMsTimeout = 120000, aenmWaitForRunLevels = None, aenmWaitForActive = None, aenmWaitForInactive = None):
4155 """
4156 Waits for the guest additions to enter a certain state.
4157
4158 aenmWaitForRunLevels - List of run level values to wait for (success if one matches).
4159 aenmWaitForActive - List facilities (type values) that must be active.
4160 aenmWaitForInactive - List facilities (type values) that must be inactive.
4161
4162 Defaults to wait for AdditionsRunLevelType_Userland if nothing else is given.
4163
4164 Returns True on success, False w/ error logging on timeout or failure.
4165 """
4166 reporter.log2('waitForGAs: oSession=%s, cMsTimeout=%s' % (oSession, cMsTimeout,));
4167
4168 #
4169 # Get IGuest:
4170 #
4171 try:
4172 oIGuest = oSession.o.console.guest;
4173 except:
4174 return reporter.errorXcpt();
4175
4176 #
4177 # Create a wait task:
4178 #
4179 from testdriver.vboxwrappers import AdditionsStatusTask;
4180 try:
4181 oGaStatusTask = AdditionsStatusTask(oSession = oSession,
4182 oIGuest = oIGuest,
4183 cMsTimeout = cMsTimeout,
4184 aenmWaitForRunLevels = aenmWaitForRunLevels,
4185 aenmWaitForActive = aenmWaitForActive,
4186 aenmWaitForInactive = aenmWaitForInactive);
4187 except:
4188 return reporter.errorXcpt();
4189
4190 #
4191 # Add the task and make sure the VM session is also present.
4192 #
4193 self.addTask(oGaStatusTask);
4194 fRemoveSession = self.addTask(oSession);
4195 oTask = self.waitForTasks(cMsTimeout + 1);
4196 reporter.log2('waitForGAs: returned %s (oGaStatusTask=%s, oSession=%s)' % (oTask, oGaStatusTask, oSession,));
4197 self.removeTask(oGaStatusTask);
4198 if fRemoveSession:
4199 self.removeTask(oSession);
4200
4201 #
4202 # Digest the result.
4203 #
4204 if oTask is oGaStatusTask:
4205 fSucceeded = oGaStatusTask.getResult();
4206 if fSucceeded is True:
4207 reporter.log('waitForGAs: Succeeded.');
4208 else:
4209 reporter.error('waitForGAs: Failed.');
4210 else:
4211 oGaStatusTask.cancelTask();
4212 if oTask is None:
4213 reporter.error('waitForGAs: Timed out.');
4214 elif oTask is oSession:
4215 oSession.reportPrematureTermination('waitForGAs: ');
4216 else:
4217 reporter.error('waitForGAs: unknown/wrong task %s' % (oTask,));
4218 fSucceeded = False;
4219 return fSucceeded;
4220
4221 @staticmethod
4222 def controllerTypeToName(eControllerType):
4223 """
4224 Translate a controller type to a standard controller name.
4225 """
4226 if eControllerType in (vboxcon.StorageControllerType_PIIX3, vboxcon.StorageControllerType_PIIX4,):
4227 sName = "IDE Controller";
4228 elif eControllerType == vboxcon.StorageControllerType_IntelAhci:
4229 sName = "SATA Controller";
4230 elif eControllerType == vboxcon.StorageControllerType_LsiLogicSas:
4231 sName = "SAS Controller";
4232 elif eControllerType in (vboxcon.StorageControllerType_LsiLogic, vboxcon.StorageControllerType_BusLogic,):
4233 sName = "SCSI Controller";
4234 elif eControllerType == vboxcon.StorageControllerType_NVMe:
4235 sName = "NVMe Controller";
4236 elif eControllerType == vboxcon.StorageControllerType_VirtioSCSI:
4237 sName = "VirtIO SCSI Controller";
4238 else:
4239 sName = "Storage Controller";
4240 return sName;
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette