VirtualBox

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

Last change on this file since 94126 was 94126, checked in by vboxsync, 3 years ago

ValKit/testdriver: pylint 2.9.6 adjustments (mostly about using 'with' statements).

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 47.2 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5VirtualBox Installer Wrapper Driver.
6
7This installs VirtualBox, starts a sub driver which does the real testing,
8and then uninstall VirtualBox afterwards. This reduces the complexity of the
9other VBox test drivers.
10"""
11
12__copyright__ = \
13"""
14Copyright (C) 2010-2022 Oracle Corporation
15
16This file is part of VirtualBox Open Source Edition (OSE), as
17available from http://www.virtualbox.org. This file is free software;
18you can redistribute it and/or modify it under the terms of the GNU
19General Public License (GPL) as published by the Free Software
20Foundation, in version 2 as it comes in the "COPYING" file of the
21VirtualBox OSE distribution. VirtualBox OSE is distributed in the
22hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
23
24The contents of this file may alternatively be used under the terms
25of the Common Development and Distribution License Version 1.0
26(CDDL) only, as it comes in the "COPYING.CDDL" file of the
27VirtualBox OSE distribution, in which case the provisions of the
28CDDL are applicable instead of those of the GPL.
29
30You may elect to license modified versions of this file under the
31terms and conditions of either the GPL or the CDDL or both.
32"""
33__version__ = "$Revision: 94126 $"
34
35
36# Standard Python imports.
37import os
38import sys
39import re
40import socket
41import tempfile
42import time
43
44# Only the main script needs to modify the path.
45try: __file__
46except: __file__ = sys.argv[0];
47g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)));
48sys.path.append(g_ksValidationKitDir);
49
50# Validation Kit imports.
51from common import utils, webutils;
52from common.constants import rtexitcode;
53from testdriver import reporter;
54from testdriver.base import TestDriverBase;
55
56
57
58class VBoxInstallerTestDriver(TestDriverBase):
59 """
60 Implementation of a top level test driver.
61 """
62
63
64 ## State file indicating that we've skipped installation.
65 ksVar_Skipped = 'vboxinstaller-skipped';
66
67
68 def __init__(self):
69 TestDriverBase.__init__(self);
70 self._asSubDriver = []; # The sub driver and it's arguments.
71 self._asBuildUrls = []; # The URLs passed us on the command line.
72 self._asBuildFiles = []; # The downloaded file names.
73 self._fUnpackedBuildFiles = False;
74 self._fAutoInstallPuelExtPack = True;
75 self._fKernelDrivers = True;
76
77 #
78 # Base method we override
79 #
80
81 def showUsage(self):
82 rc = TestDriverBase.showUsage(self);
83 # 0 1 2 3 4 5 6 7 8
84 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890
85 reporter.log('');
86 reporter.log('vboxinstaller Options:');
87 reporter.log(' --vbox-build <url[,url2[,...]]>');
88 reporter.log(' Comma separated list of URL to file to download and install or/and');
89 reporter.log(' unpack. URLs without a schema are assumed to be files on the');
90 reporter.log(' build share and will be copied off it.');
91 reporter.log(' --no-puel-extpack');
92 reporter.log(' Indicates that the PUEL extension pack should not be installed if found.');
93 reporter.log(' The default is to install it if found in the vbox-build.');
94 reporter.log(' --no-kernel-drivers');
95 reporter.log(' Indicates that the kernel drivers should not be installed on platforms where this is supported.');
96 reporter.log(' The default is to install them.');
97 reporter.log(' --');
98 reporter.log(' Indicates the end of our parameters and the start of the sub');
99 reporter.log(' testdriver and its arguments.');
100 return rc;
101
102 def parseOption(self, asArgs, iArg):
103 """
104 Parse our arguments.
105 """
106 if asArgs[iArg] == '--':
107 # End of our parameters and start of the sub driver invocation.
108 iArg = self.requireMoreArgs(1, asArgs, iArg);
109 assert not self._asSubDriver;
110 self._asSubDriver = asArgs[iArg:];
111 self._asSubDriver[0] = self._asSubDriver[0].replace('/', os.path.sep);
112 iArg = len(asArgs) - 1;
113 elif asArgs[iArg] == '--vbox-build':
114 # List of files to copy/download and install.
115 iArg = self.requireMoreArgs(1, asArgs, iArg);
116 self._asBuildUrls = asArgs[iArg].split(',');
117 elif asArgs[iArg] == '--no-puel-extpack':
118 self._fAutoInstallPuelExtPack = False;
119 elif asArgs[iArg] == '--puel-extpack':
120 self._fAutoInstallPuelExtPack = True;
121 elif asArgs[iArg] == '--no-kernel-drivers':
122 self._fKernelDrivers = False;
123 elif asArgs[iArg] == '--kernel-drivers':
124 self._fKernelDrivers = True;
125 else:
126 return TestDriverBase.parseOption(self, asArgs, iArg);
127 return iArg + 1;
128
129 def completeOptions(self):
130 #
131 # Check that we've got what we need.
132 #
133 if not self._asBuildUrls:
134 reporter.error('No build files specified ("--vbox-build file1[,file2[...]]")');
135 return False;
136 if not self._asSubDriver:
137 reporter.error('No sub testdriver specified. (" -- test/stuff/tdStuff1.py args")');
138 return False;
139
140 #
141 # Construct _asBuildFiles as an array parallel to _asBuildUrls.
142 #
143 for sUrl in self._asBuildUrls:
144 sDstFile = os.path.join(self.sScratchPath, webutils.getFilename(sUrl));
145 self._asBuildFiles.append(sDstFile);
146
147 return TestDriverBase.completeOptions(self);
148
149 def actionExtract(self):
150 reporter.error('vboxinstall does not support extracting resources, you have to do that using the sub testdriver.');
151 return False;
152
153 def actionCleanupBefore(self):
154 """
155 Kills all VBox process we see.
156
157 This is only supposed to execute on a testbox so we don't need to go
158 all complicated wrt other users.
159 """
160 return self._killAllVBoxProcesses();
161
162 def actionConfig(self):
163 """
164 Install VBox and pass on the configure request to the sub testdriver.
165 """
166 fRc = self._installVBox();
167 if fRc is None:
168 self._persistentVarSet(self.ksVar_Skipped, 'true');
169 self.fBadTestbox = True;
170 else:
171 self._persistentVarUnset(self.ksVar_Skipped);
172
173 ## @todo vbox.py still has bugs preventing us from invoking it seperately with each action.
174 if fRc is True and 'execute' not in self.asActions and 'all' not in self.asActions:
175 fRc = self._executeSubDriver([ 'verify', ]);
176 if fRc is True and 'execute' not in self.asActions and 'all' not in self.asActions:
177 fRc = self._executeSubDriver([ 'config', ], fPreloadASan = True);
178 return fRc;
179
180 def actionExecute(self):
181 """
182 Execute the sub testdriver.
183 """
184 return self._executeSubDriver(self.asActions, fPreloadASan = True);
185
186 def actionCleanupAfter(self):
187 """
188 Forward this to the sub testdriver, then uninstall VBox.
189 """
190 fRc = True;
191 if 'execute' not in self.asActions and 'all' not in self.asActions:
192 fRc = self._executeSubDriver([ 'cleanup-after', ], fMaySkip = False);
193
194 if not self._killAllVBoxProcesses():
195 fRc = False;
196
197 if not self._uninstallVBox(self._persistentVarExists(self.ksVar_Skipped)):
198 fRc = False;
199
200 if utils.getHostOs() == 'darwin':
201 self._darwinUnmountDmg(fIgnoreError = True); # paranoia
202
203 if not TestDriverBase.actionCleanupAfter(self):
204 fRc = False;
205
206 return fRc;
207
208
209 def actionAbort(self):
210 """
211 Forward this to the sub testdriver first, then wipe all VBox like
212 processes, and finally do the pid file processing (again).
213 """
214 fRc1 = self._executeSubDriver([ 'abort', ], fMaySkip = False, fPreloadASan = True);
215 fRc2 = self._killAllVBoxProcesses();
216 fRc3 = TestDriverBase.actionAbort(self);
217 return fRc1 and fRc2 and fRc3;
218
219
220 #
221 # Persistent variables.
222 #
223 ## @todo integrate into the base driver. Persistent accross scratch wipes?
224
225 def __persistentVarCalcName(self, sVar):
226 """Returns the (full) filename for the given persistent variable."""
227 assert re.match(r'^[a-zA-Z0-9_-]*$', sVar) is not None;
228 return os.path.join(self.sScratchPath, 'persistent-%s.var' % (sVar,));
229
230 def _persistentVarSet(self, sVar, sValue = ''):
231 """
232 Sets a persistent variable.
233
234 Returns True on success, False + reporter.error on failure.
235
236 May raise exception if the variable name is invalid or something
237 unexpected happens.
238 """
239 sFull = self.__persistentVarCalcName(sVar);
240 try:
241 with open(sFull, 'w') as oFile:
242 if sValue:
243 oFile.write(sValue.encode('utf-8'));
244 except:
245 reporter.errorXcpt('Error creating "%s"' % (sFull,));
246 return False;
247 return True;
248
249 def _persistentVarUnset(self, sVar):
250 """
251 Unsets a persistent variable.
252
253 Returns True on success, False + reporter.error on failure.
254
255 May raise exception if the variable name is invalid or something
256 unexpected happens.
257 """
258 sFull = self.__persistentVarCalcName(sVar);
259 if os.path.exists(sFull):
260 try:
261 os.unlink(sFull);
262 except:
263 reporter.errorXcpt('Error unlinking "%s"' % (sFull,));
264 return False;
265 return True;
266
267 def _persistentVarExists(self, sVar):
268 """
269 Checks if a persistent variable exists.
270
271 Returns true/false.
272
273 May raise exception if the variable name is invalid or something
274 unexpected happens.
275 """
276 return os.path.exists(self.__persistentVarCalcName(sVar));
277
278 def _persistentVarGet(self, sVar):
279 """
280 Gets the value of a persistent variable.
281
282 Returns variable value on success.
283 Returns None if the variable doesn't exist or if an
284 error (reported) occured.
285
286 May raise exception if the variable name is invalid or something
287 unexpected happens.
288 """
289 sFull = self.__persistentVarCalcName(sVar);
290 if not os.path.exists(sFull):
291 return None;
292 try:
293 with open(sFull, 'r') as oFile:
294 sValue = oFile.read().decode('utf-8');
295 except:
296 reporter.errorXcpt('Error creating "%s"' % (sFull,));
297 return None;
298 return sValue;
299
300
301 #
302 # Helpers.
303 #
304
305 def _killAllVBoxProcesses(self):
306 """
307 Kills all virtual box related processes we find in the system.
308 """
309 sHostOs = utils.getHostOs();
310 asDebuggers = [ 'cdb', 'windbg', ] if sHostOs == 'windows' else [ 'gdb', 'gdb-i386-apple-darwin', 'lldb' ];
311
312 for iIteration in range(22):
313 # Gather processes to kill.
314 aoTodo = [];
315 aoDebuggers = [];
316 for oProcess in utils.processListAll():
317 sBase = oProcess.getBaseImageNameNoExeSuff();
318 if sBase is None:
319 continue;
320 sBase = sBase.lower();
321 if sBase in [ 'vboxsvc', 'vboxsds', 'virtualbox', 'virtualboxvm', 'vboxheadless', 'vboxmanage', 'vboxsdl',
322 'vboxwebsrv', 'vboxautostart', 'vboxballoonctrl', 'vboxbfe', 'vboxextpackhelperapp', 'vboxnetdhcp',
323 'vboxnetnat', 'vboxnetadpctl', 'vboxtestogl', 'vboxtunctl', 'vboxvmmpreload', 'vboxxpcomipcd', ]:
324 aoTodo.append(oProcess);
325 if sBase.startswith('virtualbox-') and sBase.endswith('-multiarch.exe'):
326 aoTodo.append(oProcess);
327 if sBase in asDebuggers:
328 aoDebuggers.append(oProcess);
329 if iIteration in [0, 21]:
330 reporter.log('Warning: debugger running: %s (%s %s)' % (oProcess.iPid, sBase, oProcess.asArgs));
331 if not aoTodo:
332 return True;
333
334 # Are any of the debugger processes hooked up to a VBox process?
335 if sHostOs == 'windows':
336 # On demand debugging windows: windbg -p <decimal-pid> -e <decimal-event> -g
337 for oDebugger in aoDebuggers:
338 for oProcess in aoTodo:
339 # The whole command line is asArgs[0] here. Fix if that changes.
340 if oDebugger.asArgs and oDebugger.asArgs[0].find('-p %s ' % (oProcess.iPid,)) >= 0:
341 aoTodo.append(oDebugger);
342 break;
343 else:
344 for oDebugger in aoDebuggers:
345 for oProcess in aoTodo:
346 # Simplistic approach: Just check for argument equaling our pid.
347 if oDebugger.asArgs and ('%s' % oProcess.iPid) in oDebugger.asArgs:
348 aoTodo.append(oDebugger);
349 break;
350
351 # Kill.
352 for oProcess in aoTodo:
353 reporter.log('Loop #%d - Killing %s (%s, uid=%s)'
354 % ( iIteration, oProcess.iPid, oProcess.sImage if oProcess.sName is None else oProcess.sName,
355 oProcess.iUid, ));
356 if not utils.processKill(oProcess.iPid) \
357 and sHostOs != 'windows' \
358 and utils.processExists(oProcess.iPid):
359 # Many of the vbox processes are initially set-uid-to-root and associated debuggers are running
360 # via sudo, so we might not be able to kill them unless we sudo and use /bin/kill.
361 try: utils.sudoProcessCall(['/bin/kill', '-9', '%s' % (oProcess.iPid,)]);
362 except: reporter.logXcpt();
363
364 # Check if they're all dead like they should be.
365 time.sleep(0.1);
366 for oProcess in aoTodo:
367 if utils.processExists(oProcess.iPid):
368 time.sleep(2);
369 break;
370
371 return False;
372
373 def _executeSync(self, asArgs, fMaySkip = False):
374 """
375 Executes a child process synchronously.
376
377 Returns True if the process executed successfully and returned 0.
378 Returns None if fMaySkip is true and the child exits with RTEXITCODE_SKIPPED.
379 Returns False for all other cases.
380 """
381 reporter.log('Executing: %s' % (asArgs, ));
382 reporter.flushall();
383 try:
384 iRc = utils.processCall(asArgs, shell = False, close_fds = False);
385 except:
386 reporter.errorXcpt();
387 return False;
388 reporter.log('Exit code: %s (%s)' % (iRc, asArgs));
389 if fMaySkip and iRc == rtexitcode.RTEXITCODE_SKIPPED:
390 return None;
391 return iRc == 0;
392
393 def _sudoExecuteSync(self, asArgs):
394 """
395 Executes a sudo child process synchronously.
396 Returns a tuple [True, 0] if the process executed successfully
397 and returned 0, otherwise [False, rc] is returned.
398 """
399 reporter.log('Executing [sudo]: %s' % (asArgs, ));
400 reporter.flushall();
401 iRc = 0;
402 try:
403 iRc = utils.sudoProcessCall(asArgs, shell = False, close_fds = False);
404 except:
405 reporter.errorXcpt();
406 return (False, 0);
407 reporter.log('Exit code [sudo]: %s (%s)' % (iRc, asArgs));
408 return (iRc == 0, iRc);
409
410 def _findASanLibsForASanBuild(self):
411 """
412 Returns a list of (address) santizier related libraries to preload
413 when launching the sub driver.
414 Returns empty list for non-asan builds or on platforms where this isn't needed.
415 """
416 # Note! We include libasan.so.X in the VBoxAll tarball for asan builds, so we
417 # can use its presence both to detect an 'asan' build and to return it.
418 # Only the libasan.so.X library needs preloading at present.
419 if self.sHost in ('linux',):
420 sLibASan = self._findFile(r'libasan\.so\..*');
421 if sLibASan:
422 return [sLibASan,];
423 return [];
424
425 def _executeSubDriver(self, asActions, fMaySkip = True, fPreloadASan = True):
426 """
427 Execute the sub testdriver with the specified action.
428 """
429 asArgs = list(self._asSubDriver)
430 asArgs.append('--no-wipe-clean');
431 asArgs.extend(asActions);
432
433 asASanLibs = [];
434 if fPreloadASan:
435 asASanLibs = self._findASanLibsForASanBuild();
436 if asASanLibs:
437 os.environ['LD_PRELOAD'] = ':'.join(asASanLibs);
438 os.environ['LSAN_OPTIONS'] = 'detect_leaks=0'; # We don't want python leaks. vbox.py disables this.
439
440 # Because of https://github.com/google/sanitizers/issues/856 we must try use setarch to disable
441 # address space randomization.
442
443 reporter.log('LD_PRELOAD...')
444 if utils.getHostArch() == 'amd64':
445 sSetArch = utils.whichProgram('setarch');
446 reporter.log('sSetArch=%s' % (sSetArch,));
447 if sSetArch:
448 asArgs = [ sSetArch, 'x86_64', '-R', sys.executable ] + asArgs;
449 reporter.log('asArgs=%s' % (asArgs,));
450
451 rc = self._executeSync(asArgs, fMaySkip = fMaySkip);
452
453 del os.environ['LSAN_OPTIONS'];
454 del os.environ['LD_PRELOAD'];
455 return rc;
456
457 return self._executeSync(asArgs, fMaySkip = fMaySkip);
458
459 def _maybeUnpackArchive(self, sMaybeArchive, fNonFatal = False):
460 """
461 Attempts to unpack the given build file.
462 Updates _asBuildFiles.
463 Returns True/False. No exceptions.
464 """
465 def unpackFilter(sMember):
466 # type: (string) -> bool
467 """ Skips debug info. """
468 sLower = sMember.lower();
469 if sLower.endswith('.pdb'):
470 return False;
471 return True;
472
473 asMembers = utils.unpackFile(sMaybeArchive, self.sScratchPath, reporter.log,
474 reporter.log if fNonFatal else reporter.error,
475 fnFilter = unpackFilter);
476 if asMembers is None:
477 return False;
478 self._asBuildFiles.extend(asMembers);
479 return True;
480
481
482 def _installVBox(self):
483 """
484 Download / copy the build files into the scratch area and install them.
485 """
486 reporter.testStart('Installing VirtualBox');
487 reporter.log('CWD=%s' % (os.getcwd(),)); # curious
488
489 #
490 # Download the build files.
491 #
492 for i, sBuildUrl in enumerate(self._asBuildUrls):
493 if webutils.downloadFile(sBuildUrl, self._asBuildFiles[i], self.sBuildPath, reporter.log, reporter.log) is not True:
494 reporter.testDone(fSkipped = True);
495 return None; # Failed to get binaries, probably deleted. Skip the test run.
496
497 #
498 # Unpack anything we know what is and append it to the build files
499 # list. This allows us to use VBoxAll*.tar.gz files.
500 #
501 for sFile in list(self._asBuildFiles): # Note! We copy the list as _maybeUnpackArchive updates it.
502 if self._maybeUnpackArchive(sFile, fNonFatal = True) is not True:
503 reporter.testDone(fSkipped = True);
504 return None; # Failed to unpack. Probably local error, like busy
505 # DLLs on windows, no reason for failing the build.
506 self._fUnpackedBuildFiles = True;
507
508 #
509 # Go to system specific installation code.
510 #
511 sHost = utils.getHostOs()
512 if sHost == 'darwin': fRc = self._installVBoxOnDarwin();
513 elif sHost == 'linux': fRc = self._installVBoxOnLinux();
514 elif sHost == 'solaris': fRc = self._installVBoxOnSolaris();
515 elif sHost == 'win': fRc = self._installVBoxOnWindows();
516 else:
517 reporter.error('Unsupported host "%s".' % (sHost,));
518 if fRc is False:
519 reporter.testFailure('Installation error.');
520 elif fRc is not True:
521 reporter.log('Seems installation was skipped. Old version lurking behind? Not the fault of this build/test run!');
522
523 #
524 # Install the extension pack.
525 #
526 if fRc is True and self._fAutoInstallPuelExtPack:
527 fRc = self._installExtPack();
528 if fRc is False:
529 reporter.testFailure('Extension pack installation error.');
530
531 # Some debugging...
532 try:
533 cMbFreeSpace = utils.getDiskUsage(self.sScratchPath);
534 reporter.log('Disk usage after VBox install: %d MB available at %s' % (cMbFreeSpace, self.sScratchPath,));
535 except:
536 reporter.logXcpt('Unable to get disk free space. Ignored. Continuing.');
537
538 reporter.testDone(fRc is None);
539 return fRc;
540
541 def _uninstallVBox(self, fIgnoreError = False):
542 """
543 Uninstall VirtualBox.
544 """
545 reporter.testStart('Uninstalling VirtualBox');
546
547 sHost = utils.getHostOs()
548 if sHost == 'darwin': fRc = self._uninstallVBoxOnDarwin();
549 elif sHost == 'linux': fRc = self._uninstallVBoxOnLinux();
550 elif sHost == 'solaris': fRc = self._uninstallVBoxOnSolaris(True);
551 elif sHost == 'win': fRc = self._uninstallVBoxOnWindows('uninstall');
552 else:
553 reporter.error('Unsupported host "%s".' % (sHost,));
554 if fRc is False and not fIgnoreError:
555 reporter.testFailure('Uninstallation failed.');
556
557 fRc2 = self._uninstallAllExtPacks();
558 if not fRc2 and fRc:
559 fRc = fRc2;
560
561 reporter.testDone(fSkipped = (fRc is None));
562 return fRc;
563
564 def _findFile(self, sRegExp, fMandatory = False):
565 """
566 Returns the first build file that matches the given regular expression
567 (basename only).
568
569 Returns None if no match was found, logging it as an error if
570 fMandatory is set.
571 """
572 oRegExp = re.compile(sRegExp);
573
574 reporter.log('_findFile: %s' % (sRegExp,));
575 for sFile in self._asBuildFiles:
576 if oRegExp.match(os.path.basename(sFile)) and os.path.exists(sFile):
577 return sFile;
578
579 # If we didn't unpack the build files, search all the files in the scratch area:
580 if not self._fUnpackedBuildFiles:
581 for sDir, _, asFiles in os.walk(self.sScratchPath):
582 for sFile in asFiles:
583 #reporter.log('_findFile: considering %s' % (sFile,));
584 if oRegExp.match(sFile):
585 return os.path.join(sDir, sFile);
586
587 if fMandatory:
588 reporter.error('Failed to find a file matching "%s" in %s.' % (sRegExp, self._asBuildFiles,));
589 return None;
590
591 def _waitForTestManagerConnectivity(self, cSecTimeout):
592 """
593 Check and wait for network connectivity to the test manager.
594
595 This is used with the windows installation and uninstallation since
596 these usually disrupts network connectivity when installing the filter
597 driver. If we proceed to quickly, we might finish the test at a time
598 when we cannot report to the test manager and thus end up with an
599 abandonded test error.
600 """
601 cSecElapsed = 0;
602 secStart = utils.timestampSecond();
603 while reporter.checkTestManagerConnection() is False:
604 cSecElapsed = utils.timestampSecond() - secStart;
605 if cSecElapsed >= cSecTimeout:
606 reporter.log('_waitForTestManagerConnectivity: Giving up after %u secs.' % (cSecTimeout,));
607 return False;
608 time.sleep(2);
609
610 if cSecElapsed > 0:
611 reporter.log('_waitForTestManagerConnectivity: Waited %s secs.' % (cSecTimeout,));
612 return True;
613
614
615 #
616 # Darwin (Mac OS X).
617 #
618
619 def _darwinDmgPath(self):
620 """ Returns the path to the DMG mount."""
621 return os.path.join(self.sScratchPath, 'DmgMountPoint');
622
623 def _darwinUnmountDmg(self, fIgnoreError):
624 """
625 Umount any DMG on at the default mount point.
626 """
627 sMountPath = self._darwinDmgPath();
628 if not os.path.exists(sMountPath):
629 return True;
630
631 # Unmount.
632 fRc = self._executeSync(['hdiutil', 'detach', sMountPath ]);
633 if not fRc and not fIgnoreError:
634 # In case it's busy for some reason or another, just retry after a little delay.
635 for iTry in range(6):
636 time.sleep(5);
637 reporter.error('Retry #%s unmount DMT at %s' % (iTry + 1, sMountPath,));
638 fRc = self._executeSync(['hdiutil', 'detach', sMountPath ]);
639 if fRc:
640 break;
641 if not fRc:
642 reporter.error('Failed to unmount DMG at %s' % (sMountPath,));
643
644 # Remove dir.
645 try:
646 os.rmdir(sMountPath);
647 except:
648 if not fIgnoreError:
649 reporter.errorXcpt('Failed to remove directory %s' % (sMountPath,));
650 return fRc;
651
652 def _darwinMountDmg(self, sDmg):
653 """
654 Mount the DMG at the default mount point.
655 """
656 self._darwinUnmountDmg(fIgnoreError = True)
657
658 sMountPath = self._darwinDmgPath();
659 if not os.path.exists(sMountPath):
660 try:
661 os.mkdir(sMountPath, 0o755);
662 except:
663 reporter.logXcpt();
664 return False;
665
666 return self._executeSync(['hdiutil', 'attach', '-readonly', '-mount', 'required', '-mountpoint', sMountPath, sDmg, ]);
667
668 def _generateWithoutKextsChoicesXmlOnDarwin(self):
669 """
670 Generates the choices XML when kernel drivers are disabled.
671 None is returned on failure.
672 """
673 sPath = os.path.join(self.sScratchPath, 'DarwinChoices.xml');
674 oFile = utils.openNoInherit(sPath, 'wt');
675 oFile.write('<?xml version="1.0" encoding="UTF-8"?>\n'
676 '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
677 '<plist version="1.0">\n'
678 '<array>\n'
679 ' <dict>\n'
680 ' <key>attributeSetting</key>\n'
681 ' <integer>0</integer>\n'
682 ' <key>choiceAttribute</key>\n'
683 ' <string>selected</string>\n'
684 ' <key>choiceIdentifier</key>\n'
685 ' <string>choiceVBoxKEXTs</string>\n'
686 ' </dict>\n'
687 '</array>\n'
688 '</plist>\n');
689 oFile.close();
690 return sPath;
691
692 def _installVBoxOnDarwin(self):
693 """ Installs VBox on Mac OS X."""
694
695 # TEMPORARY HACK - START
696 # Don't install the kernel drivers on the testboxes with BigSur and later
697 # Needs a more generic approach but that one needs more effort.
698 sHostName = socket.getfqdn();
699 if sHostName.startswith('testboxmac10') \
700 or sHostName.startswith('testboxmac11'):
701 self._fKernelDrivers = False;
702 # TEMPORARY HACK - END
703
704 sDmg = self._findFile('^VirtualBox-.*\\.dmg$');
705 if sDmg is None:
706 return False;
707
708 # Mount the DMG.
709 fRc = self._darwinMountDmg(sDmg);
710 if fRc is not True:
711 return False;
712
713 # Uninstall any previous vbox version first.
714 sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool');
715 fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]);
716 if fRc is True:
717
718 # Install the package.
719 sPkg = os.path.join(self._darwinDmgPath(), 'VirtualBox.pkg');
720 if self._fKernelDrivers:
721 fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, '-target', '/']);
722 else:
723 sChoicesXml = self._generateWithoutKextsChoicesXmlOnDarwin();
724 if sChoicesXml is not None:
725 fRc, _ = self._sudoExecuteSync(['installer', '-verbose', '-dumplog', '-pkg', sPkg, \
726 '-applyChoiceChangesXML', sChoicesXml, '-target', '/']);
727 else:
728 fRc = False;
729
730 # Unmount the DMG and we're done.
731 if not self._darwinUnmountDmg(fIgnoreError = False):
732 fRc = False;
733 return fRc;
734
735 def _uninstallVBoxOnDarwin(self):
736 """ Uninstalls VBox on Mac OS X."""
737
738 # Is VirtualBox installed? If not, don't try uninstall it.
739 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
740 if sVBox is None:
741 return True;
742
743 # Find the dmg.
744 sDmg = self._findFile('^VirtualBox-.*\\.dmg$');
745 if sDmg is None:
746 return False;
747 if not os.path.exists(sDmg):
748 return True;
749
750 # Mount the DMG.
751 fRc = self._darwinMountDmg(sDmg);
752 if fRc is not True:
753 return False;
754
755 # Execute the uninstaller.
756 sUninstaller = os.path.join(self._darwinDmgPath(), 'VirtualBox_Uninstall.tool');
757 fRc, _ = self._sudoExecuteSync([sUninstaller, '--unattended',]);
758
759 # Unmount the DMG and we're done.
760 if not self._darwinUnmountDmg(fIgnoreError = False):
761 fRc = False;
762 return fRc;
763
764 #
765 # GNU/Linux
766 #
767
768 def _installVBoxOnLinux(self):
769 """ Installs VBox on Linux."""
770 sRun = self._findFile('^VirtualBox-.*\\.run$');
771 if sRun is None:
772 return False;
773 utils.chmodPlusX(sRun);
774
775 # Install the new one.
776 fRc, _ = self._sudoExecuteSync([sRun,]);
777 return fRc;
778
779 def _uninstallVBoxOnLinux(self):
780 """ Uninstalls VBox on Linux."""
781
782 # Is VirtualBox installed? If not, don't try uninstall it.
783 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
784 if sVBox is None:
785 return True;
786
787 # Find the .run file and use it.
788 sRun = self._findFile('^VirtualBox-.*\\.run$', fMandatory = False);
789 if sRun is not None:
790 utils.chmodPlusX(sRun);
791 fRc, _ = self._sudoExecuteSync([sRun, 'uninstall']);
792 return fRc;
793
794 # Try the installed uninstaller.
795 for sUninstaller in [os.path.join(sVBox, 'uninstall.sh'), '/opt/VirtualBox/uninstall.sh', ]:
796 if os.path.isfile(sUninstaller):
797 reporter.log('Invoking "%s"...' % (sUninstaller,));
798 fRc, _ = self._sudoExecuteSync([sUninstaller, 'uninstall']);
799 return fRc;
800
801 reporter.log('Did not find any VirtualBox install to uninstall.');
802 return True;
803
804
805 #
806 # Solaris
807 #
808
809 def _generateAutoResponseOnSolaris(self):
810 """
811 Generates an autoresponse file on solaris, returning the name.
812 None is return on failure.
813 """
814 sPath = os.path.join(self.sScratchPath, 'SolarisAutoResponse');
815 oFile = utils.openNoInherit(sPath, 'wt');
816 oFile.write('basedir=default\n'
817 'runlevel=nocheck\n'
818 'conflict=quit\n'
819 'setuid=nocheck\n'
820 'action=nocheck\n'
821 'partial=quit\n'
822 'instance=unique\n'
823 'idepend=quit\n'
824 'rdepend=quit\n'
825 'space=quit\n'
826 'mail=\n');
827 oFile.close();
828 return sPath;
829
830 def _installVBoxOnSolaris(self):
831 """ Installs VBox on Solaris."""
832 sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = False);
833 if sPkg is None:
834 sTar = self._findFile('^VirtualBox-.*-SunOS-.*\\.tar.gz$', fMandatory = False);
835 if sTar is not None:
836 if self._maybeUnpackArchive(sTar) is not True:
837 return False;
838 sPkg = self._findFile('^VirtualBox-.*\\.pkg$', fMandatory = True);
839 sRsp = self._findFile('^autoresponse$', fMandatory = True);
840 if sPkg is None or sRsp is None:
841 return False;
842
843 # Uninstall first (ignore result).
844 self._uninstallVBoxOnSolaris(False);
845
846 # Install the new one.
847 fRc, _ = self._sudoExecuteSync(['pkgadd', '-d', sPkg, '-n', '-a', sRsp, 'SUNWvbox']);
848 return fRc;
849
850 def _uninstallVBoxOnSolaris(self, fRestartSvcConfigD):
851 """ Uninstalls VBox on Solaris."""
852 reporter.flushall();
853 if utils.processCall(['pkginfo', '-q', 'SUNWvbox']) != 0:
854 return True;
855 sRsp = self._generateAutoResponseOnSolaris();
856 fRc, _ = self._sudoExecuteSync(['pkgrm', '-n', '-a', sRsp, 'SUNWvbox']);
857
858 #
859 # Restart the svc.configd as it has a tendency to clog up with time and
860 # become unresponsive. It will handle SIGHUP by exiting the sigwait()
861 # look in the main function and shut down the service nicely (backend_fini).
862 # The restarter will then start a new instance of it.
863 #
864 if fRestartSvcConfigD:
865 time.sleep(1); # Give it a chance to flush pkgrm stuff.
866 self._sudoExecuteSync(['pkill', '-HUP', 'svc.configd']);
867 time.sleep(5); # Spare a few cpu cycles it to shutdown and restart.
868
869 return fRc;
870
871 #
872 # Windows
873 #
874
875 ## VBox windows services we can query the status of.
876 kasWindowsServices = [ 'vboxsup', 'vboxusbmon', 'vboxnetadp', 'vboxnetflt', 'vboxnetlwf' ];
877
878 def _installVBoxOnWindows(self):
879 """ Installs VBox on Windows."""
880 sExe = self._findFile('^VirtualBox-.*-(MultiArch|Win).exe$');
881 if sExe is None:
882 return False;
883
884 # TEMPORARY HACK - START
885 # It seems that running the NDIS cleanup script upon uninstallation is not
886 # a good idea, so let's run it before installing VirtualBox.
887 #sHostName = socket.getfqdn();
888 #if not sHostName.startswith('testboxwin3') \
889 # and not sHostName.startswith('testboxharp2') \
890 # and not sHostName.startswith('wei01-b6ka-3') \
891 # and utils.getHostOsVersion() in ['8', '8.1', '9', '2008Server', '2008ServerR2', '2012Server']:
892 # reporter.log('Peforming extra NDIS cleanup...');
893 # sMagicScript = os.path.abspath(os.path.join(g_ksValidationKitDir, 'testdriver', 'win-vbox-net-uninstall.ps1'));
894 # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-Command', 'set-executionpolicy unrestricted']);
895 # if not fRc2:
896 # reporter.log('set-executionpolicy failed.');
897 # self._sudoExecuteSync(['powershell.exe', '-Command', 'get-executionpolicy']);
898 # fRc2, _ = self._sudoExecuteSync(['powershell.exe', '-File', sMagicScript]);
899 # if not fRc2:
900 # reporter.log('NDIS cleanup failed.');
901 # TEMPORARY HACK - END
902
903 # Uninstall any previous vbox version first.
904 fRc = self._uninstallVBoxOnWindows('install');
905 if fRc is not True:
906 return None; # There shouldn't be anything to uninstall, and if there is, it's not our fault.
907
908 # Install the new one.
909 asArgs = [sExe, '-vvvv', '--silent', '--logging'];
910 asArgs.extend(['--msiparams', 'REBOOT=ReallySuppress']);
911 sVBoxInstallPath = os.environ.get('VBOX_INSTALL_PATH', None);
912 if sVBoxInstallPath is not None:
913 asArgs.extend(['INSTALLDIR="%s"' % (sVBoxInstallPath,)]);
914
915 fRc2, iRc = self._sudoExecuteSync(asArgs);
916 if fRc2 is False:
917 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
918 reporter.error('Installer required a reboot to complete installation (ERROR_SUCCESS_REBOOT_REQUIRED)');
919 else:
920 reporter.error('Installer failed, exit code: %s' % (iRc,));
921 fRc = False;
922
923 sLogFile = os.path.join(tempfile.gettempdir(), 'VirtualBox', 'VBoxInstallLog.txt');
924 if os.path.isfile(sLogFile):
925 reporter.addLogFile(sLogFile, 'log/installer', "Verbose MSI installation log file");
926 self._waitForTestManagerConnectivity(30);
927 return fRc;
928
929 def _isProcessPresent(self, sName):
930 """ Checks whether the named process is present or not. """
931 for oProcess in utils.processListAll():
932 sBase = oProcess.getBaseImageNameNoExeSuff();
933 if sBase is not None and sBase.lower() == sName:
934 return True;
935 return False;
936
937 def _killProcessesByName(self, sName, sDesc, fChildren = False):
938 """ Kills the named process, optionally including children. """
939 cKilled = 0;
940 aoProcesses = utils.processListAll();
941 for oProcess in aoProcesses:
942 sBase = oProcess.getBaseImageNameNoExeSuff();
943 if sBase is not None and sBase.lower() == sName:
944 reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase));
945 utils.processKill(oProcess.iPid);
946 cKilled += 1;
947
948 if fChildren:
949 for oChild in aoProcesses:
950 if oChild.iParentPid == oProcess.iPid and oChild.iParentPid is not None:
951 reporter.log('Killing %s child process: %s (%s)' % (sDesc, oChild.iPid, sBase));
952 utils.processKill(oChild.iPid);
953 cKilled += 1;
954 return cKilled;
955
956 def _terminateProcessesByNameAndArgSubstr(self, sName, sArg, sDesc):
957 """
958 Terminates the named process using taskkill.exe, if any of its args
959 contains the passed string.
960 """
961 cKilled = 0;
962 aoProcesses = utils.processListAll();
963 for oProcess in aoProcesses:
964 sBase = oProcess.getBaseImageNameNoExeSuff();
965 if sBase is not None and sBase.lower() == sName and any(sArg in s for s in oProcess.asArgs):
966
967 reporter.log('Killing %s process: %s (%s)' % (sDesc, oProcess.iPid, sBase));
968 self._executeSync(['taskkill.exe', '/pid', '%u' % (oProcess.iPid,)]);
969 cKilled += 1;
970 return cKilled;
971
972 def _uninstallVBoxOnWindows(self, sMode):
973 """
974 Uninstalls VBox on Windows, all installations we find to be on the safe side...
975 """
976 assert sMode in ['install', 'uninstall',];
977
978 import win32com.client; # pylint: disable=import-error
979 win32com.client.gencache.EnsureModule('{000C1092-0000-0000-C000-000000000046}', 1033, 1, 0);
980 oInstaller = win32com.client.Dispatch('WindowsInstaller.Installer',
981 resultCLSID = '{000C1090-0000-0000-C000-000000000046}')
982
983 # Search installed products for VirtualBox.
984 asProdCodes = [];
985 for sProdCode in oInstaller.Products:
986 try:
987 sProdName = oInstaller.ProductInfo(sProdCode, "ProductName");
988 except:
989 reporter.logXcpt();
990 continue;
991 #reporter.log('Info: %s=%s' % (sProdCode, sProdName));
992 if sProdName.startswith('Oracle VM VirtualBox') \
993 or sProdName.startswith('Sun VirtualBox'):
994 asProdCodes.append([sProdCode, sProdName]);
995
996 # Before we start uninstalling anything, just ruthlessly kill any cdb,
997 # msiexec, drvinst and some rundll process we might find hanging around.
998 if self._isProcessPresent('rundll32'):
999 cTimes = 0;
1000 while cTimes < 3:
1001 cTimes += 1;
1002 cKilled = self._terminateProcessesByNameAndArgSubstr('rundll32', 'InstallSecurityPromptRunDllW',
1003 'MSI driver installation');
1004 if cKilled <= 0:
1005 break;
1006 time.sleep(10); # Give related drvinst process a chance to clean up after we killed the verification dialog.
1007
1008 if self._isProcessPresent('drvinst'):
1009 time.sleep(15); # In the hope that it goes away.
1010 cTimes = 0;
1011 while cTimes < 4:
1012 cTimes += 1;
1013 cKilled = self._killProcessesByName('drvinst', 'MSI driver installation', True);
1014 if cKilled <= 0:
1015 break;
1016 time.sleep(10); # Give related MSI process a chance to clean up after we killed the driver installer.
1017
1018 if self._isProcessPresent('msiexec'):
1019 cTimes = 0;
1020 while cTimes < 3:
1021 reporter.log('found running msiexec process, waiting a bit...');
1022 time.sleep(20) # In the hope that it goes away.
1023 if not self._isProcessPresent('msiexec'):
1024 break;
1025 cTimes += 1;
1026 ## @todo this could also be the msiexec system service, try to detect this case!
1027 if cTimes >= 6:
1028 cKilled = self._killProcessesByName('msiexec', 'MSI driver installation');
1029 if cKilled > 0:
1030 time.sleep(16); # fudge.
1031
1032 # cdb.exe sometimes stays running (from utils.getProcessInfo), blocking
1033 # the scratch directory. No idea why.
1034 if self._isProcessPresent('cdb'):
1035 cTimes = 0;
1036 while cTimes < 3:
1037 cKilled = self._killProcessesByName('cdb', 'cdb.exe from getProcessInfo');
1038 if cKilled <= 0:
1039 break;
1040 time.sleep(2); # fudge.
1041
1042 # Do the uninstalling.
1043 fRc = True;
1044 sLogFile = os.path.join(self.sScratchPath, 'VBoxUninstallLog.txt');
1045 for sProdCode, sProdName in asProdCodes:
1046 reporter.log('Uninstalling %s (%s)...' % (sProdName, sProdCode));
1047 fRc2, iRc = self._sudoExecuteSync(['msiexec', '/uninstall', sProdCode, '/quiet', '/passive', '/norestart',
1048 '/L*v', '%s' % (sLogFile), ]);
1049 if fRc2 is False:
1050 if iRc == 3010: # ERROR_SUCCESS_REBOOT_REQUIRED
1051 reporter.error('Uninstaller required a reboot to complete uninstallation');
1052 else:
1053 reporter.error('Uninstaller failed, exit code: %s' % (iRc,));
1054 fRc = False;
1055
1056 self._waitForTestManagerConnectivity(30);
1057
1058 # Upload the log on failure. Do it early if the extra cleanups below causes trouble.
1059 if fRc is False and os.path.isfile(sLogFile):
1060 reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file");
1061 sLogFile = None;
1062
1063 # Log driver service states (should ls \Driver\VBox* and \Device\VBox*).
1064 fHadLeftovers = False;
1065 asLeftovers = [];
1066 for sService in reversed(self.kasWindowsServices):
1067 cTries = 0;
1068 while True:
1069 fRc2, _ = self._sudoExecuteSync(['sc.exe', 'query', sService]);
1070 if not fRc2:
1071 break;
1072 fHadLeftovers = True;
1073
1074 cTries += 1;
1075 if cTries > 3:
1076 asLeftovers.append(sService,);
1077 break;
1078
1079 # Get the status output.
1080 try:
1081 sOutput = utils.sudoProcessOutputChecked(['sc.exe', 'query', sService]);
1082 except:
1083 reporter.logXcpt();
1084 else:
1085 if re.search(r'STATE\s+:\s*1\s*STOPPED', sOutput) is None:
1086 reporter.log('Trying to stop %s...' % (sService,));
1087 fRc2, _ = self._sudoExecuteSync(['sc.exe', 'stop', sService]);
1088 time.sleep(1); # fudge
1089
1090 reporter.log('Trying to delete %s...' % (sService,));
1091 self._sudoExecuteSync(['sc.exe', 'delete', sService]);
1092
1093 time.sleep(1); # fudge
1094
1095 if asLeftovers:
1096 reporter.log('Warning! Leftover VBox drivers: %s' % (', '.join(asLeftovers),));
1097 fRc = False;
1098
1099 if fHadLeftovers:
1100 self._waitForTestManagerConnectivity(30);
1101
1102 # Upload the log if we have any leftovers and didn't upload it already.
1103 if sLogFile is not None and (fRc is False or fHadLeftovers) and os.path.isfile(sLogFile):
1104 reporter.addLogFile(sLogFile, 'log/uninstaller', "Verbose MSI uninstallation log file");
1105
1106 return fRc;
1107
1108
1109 #
1110 # Extension pack.
1111 #
1112
1113 def _getVBoxInstallPath(self, fFailIfNotFound):
1114 """ Returns the default VBox installation path. """
1115 sHost = utils.getHostOs();
1116 if sHost == 'win':
1117 sProgFiles = os.environ.get('ProgramFiles', 'C:\\Program Files');
1118 asLocs = [
1119 os.path.join(sProgFiles, 'Oracle', 'VirtualBox'),
1120 os.path.join(sProgFiles, 'OracleVM', 'VirtualBox'),
1121 os.path.join(sProgFiles, 'Sun', 'VirtualBox'),
1122 ];
1123 elif sHost in ('linux', 'solaris',):
1124 asLocs = [ '/opt/VirtualBox', '/opt/VirtualBox-3.2', '/opt/VirtualBox-3.1', '/opt/VirtualBox-3.0'];
1125 elif sHost == 'darwin':
1126 asLocs = [ '/Applications/VirtualBox.app/Contents/MacOS' ];
1127 else:
1128 asLocs = [ '/opt/VirtualBox' ];
1129 if 'VBOX_INSTALL_PATH' in os.environ:
1130 asLocs.insert(0, os.environ.get('VBOX_INSTALL_PATH', None));
1131
1132 for sLoc in asLocs:
1133 if os.path.isdir(sLoc):
1134 return sLoc;
1135 if fFailIfNotFound:
1136 reporter.error('Failed to locate VirtualBox installation: %s' % (asLocs,));
1137 else:
1138 reporter.log2('Failed to locate VirtualBox installation: %s' % (asLocs,));
1139 return None;
1140
1141 def _installExtPack(self):
1142 """ Installs the extension pack. """
1143 sVBox = self._getVBoxInstallPath(fFailIfNotFound = True);
1144 if sVBox is None:
1145 return False;
1146 sExtPackDir = os.path.join(sVBox, 'ExtensionPacks');
1147
1148 if self._uninstallAllExtPacks() is not True:
1149 return False;
1150
1151 sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.vbox-extpack');
1152 if sExtPack is None:
1153 sExtPack = self._findFile('Oracle_VM_VirtualBox_Extension_Pack.*.vbox-extpack');
1154 if sExtPack is None:
1155 return True;
1156
1157 sDstDir = os.path.join(sExtPackDir, 'Oracle_VM_VirtualBox_Extension_Pack');
1158 reporter.log('Installing extension pack "%s" to "%s"...' % (sExtPack, sExtPackDir));
1159 fRc, _ = self._sudoExecuteSync([ self.getBinTool('vts_tar'),
1160 '--extract',
1161 '--verbose',
1162 '--gzip',
1163 '--file', sExtPack,
1164 '--directory', sDstDir,
1165 '--file-mode-and-mask', '0644',
1166 '--file-mode-or-mask', '0644',
1167 '--dir-mode-and-mask', '0755',
1168 '--dir-mode-or-mask', '0755',
1169 '--owner', '0',
1170 '--group', '0',
1171 ]);
1172 return fRc;
1173
1174 def _uninstallAllExtPacks(self):
1175 """ Uninstalls all extension packs. """
1176 sVBox = self._getVBoxInstallPath(fFailIfNotFound = False);
1177 if sVBox is None:
1178 return True;
1179
1180 sExtPackDir = os.path.join(sVBox, 'ExtensionPacks');
1181 if not os.path.exists(sExtPackDir):
1182 return True;
1183
1184 fRc, _ = self._sudoExecuteSync([self.getBinTool('vts_rm'), '-Rfv', '--', sExtPackDir]);
1185 return fRc;
1186
1187
1188
1189if __name__ == '__main__':
1190 sys.exit(VBoxInstallerTestDriver().main(sys.argv));
1191
Note: See TracBrowser for help on using the repository browser.

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