VirtualBox

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

Last change on this file since 69589 was 69589, checked in by vboxsync, 7 years ago

scm fix

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

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