VirtualBox

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

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

ValKit/vboxinstaller.py: Do 'sudo /bin/kill -9 pid' if utils.processKill fails and the process still exists.

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