VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/tests/api/tdTreeDepth1.py@ 99112

Last change on this file since 99112 was 99112, checked in by vboxsync, 2 years ago

ValidationKit/tests/api/tdTreeDepth1.py: Invoking Python's garbage
collector directly does trigger the call to Machine::uninit() but
Machine::uninit() can take some time to close up to 64 disks or 200
snapshots which means the test may fail as it did before. Since
Machine::uninit() is asynchronous we do need a small delay after the
garbage collection to allow all of the attached media to be closed.
Also, leverage the test driver's IVirtualBox object rather than using
our own to simplify the code.

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 14.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# $Id: tdTreeDepth1.py 99112 2023-03-22 12:42:20Z vboxsync $
4
5"""
6VirtualBox Validation Kit - Medium and Snapshot Tree Depth Test #1
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2010-2023 Oracle and/or its affiliates.
12
13This file is part of VirtualBox base platform packages, as
14available from https://www.215389.xyz.
15
16This program is free software; you can redistribute it and/or
17modify it under the terms of the GNU General Public License
18as published by the Free Software Foundation, in version 3 of the
19License.
20
21This program is distributed in the hope that it will be useful, but
22WITHOUT ANY WARRANTY; without even the implied warranty of
23MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24General Public License for more details.
25
26You should have received a copy of the GNU General Public License
27along with this program; if not, see <https://www.gnu.org/licenses>.
28
29The contents of this file may alternatively be used under the terms
30of the Common Development and Distribution License Version 1.0
31(CDDL), a copy of it is provided in the "COPYING.CDDL" file included
32in the VirtualBox distribution, in which case the provisions of the
33CDDL are applicable instead of those of the GPL.
34
35You may elect to license modified versions of this file under the
36terms and conditions of either the GPL or the CDDL or both.
37
38SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0
39"""
40__version__ = "$Revision: 99112 $"
41
42
43# Standard Python imports.
44import os
45import sys
46import random
47import time
48import gc
49
50# Only the main script needs to modify the path.
51try: __file__ # pylint: disable=used-before-assignment
52except: __file__ = sys.argv[0]
53g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
54sys.path.append(g_ksValidationKitDir)
55
56# Validation Kit imports.
57from testdriver import base
58from testdriver import reporter
59from testdriver import vboxcon
60
61
62class SubTstDrvTreeDepth1(base.SubTestDriverBase):
63 """
64 Sub-test driver for Medium and Snapshot Tree Depth Test #1.
65 """
66
67 def __init__(self, oTstDrv):
68 base.SubTestDriverBase.__init__(self, oTstDrv, 'tree-depth', 'Media and Snapshot tree depths');
69
70 def testIt(self):
71 """
72 Execute the sub-testcase.
73 """
74 return self.testMediumTreeDepth() \
75 and self.testSnapshotTreeDepth();
76
77 def getNumOfAttachedHDs(self, oVM, sController):
78 """
79 Helper routine for counting the hard disks, including differencing disks,
80 attached to the specified countroller.
81 """
82 try:
83 aoMediumAttachments = oVM.getMediumAttachmentsOfController(sController);
84 except:
85 reporter.errorXcpt('oVM.getMediumAttachmentsOfController("%s") failed' % (sController));
86 return -1;
87
88 cDisks = 0;
89 for oAttachment in aoMediumAttachments:
90 oMedium = oAttachment.medium;
91 if oMedium.deviceType is vboxcon.DeviceType_HardDisk:
92 cDisks = cDisks + 1;
93 reporter.log2('base medium = %s cDisks = %d' % (oMedium.location, cDisks));
94 oParentMedium = oMedium.parent;
95 while oParentMedium is not None:
96 cDisks = cDisks + 1;
97 reporter.log2('parent medium = %s cDisks = %d' % (oParentMedium.location, cDisks));
98 oParentMedium = oParentMedium.parent;
99 return cDisks;
100
101 def unregisterVM(self, oVM, enmCleanupMode):
102 """
103 Helper routine to unregister the VM using the CleanupMode specified.
104 """
105 reporter.log('unregistering VM using %s' % enmCleanupMode);
106 if enmCleanupMode == 'DetachAllReturnHardDisksOnly':
107 try:
108 aoHDs = oVM.unregister(vboxcon.CleanupMode_DetachAllReturnHardDisksOnly);
109 except:
110 return reporter.logXcpt('unregister(CleanupMode_DetachAllReturnHardDisksOnly) failed');
111 for oHD in aoHDs:
112 reporter.log2('closing medium = %s' % (oHD.location));
113 oHD.close();
114 aoHDs = None;
115 elif enmCleanupMode == 'UnregisterOnly':
116 try:
117 aoHDs = oVM.unregister(vboxcon.CleanupMode_UnregisterOnly);
118 except:
119 return reporter.logXcpt('unregister(CleanupMode_UnregisterOnly) failed');
120 if aoHDs:
121 return reporter.error('unregister(CleanupMode_UnregisterOnly) failed: returned %d disks' %
122 len(aoHDs));
123 else:
124 return reporter.error('unregisterVM: unexpected CleanupMode "%s"' % enmCleanupMode);
125
126 return True;
127
128 def openAndRegisterMachine(self, sSettingsFile):
129 """
130 Helper routine which opens a VM and registers it.
131 """
132 reporter.log('opening VM using configuration file = %s, testing config reading' % (sSettingsFile));
133 try:
134 if self.oTstDrv.fpApiVer >= 7.0:
135 # Needs a password parameter since 7.0.
136 oVM = self.oTstDrv.oVBox.openMachine(sSettingsFile, "");
137 else:
138 oVM = self.oTstDrv.oVBox.openMachine(sSettingsFile);
139 except:
140 reporter.logXcpt('openMachine(%s) failed' % (sSettingsFile));
141 return None;
142
143 if not oVM:
144 return None;
145
146 try:
147 self.oTstDrv.oVBox.registerMachine(oVM);
148 except:
149 reporter.logXcpt('registerMachine(%s) failed' % (sSettingsFile));
150 return None;
151
152 return oVM;
153
154 #
155 # Test execution helpers.
156 #
157
158 def testMediumTreeDepth(self):
159 """
160 Test medium tree depth.
161 """
162 reporter.testStart('mediumTreeDepth')
163
164 oVM = self.oTstDrv.createTestVMOnly('test-medium', 'Other')
165 if oVM is None:
166 return False;
167
168 # Save the path to the VM's settings file while oVM is valid (needed for
169 # openMachine() later when oVM is gone).
170 sSettingsFile = oVM.settingsFilePath
171
172 oSession = self.oTstDrv.openSession(oVM)
173 if oSession is None:
174 return False;
175
176 # create chain with up to 64 disk images (medium tree depth limit)
177 cImages = random.randrange(1, 64);
178 reporter.log('Creating chain with %d disk images' % (cImages))
179 for i in range(1, cImages + 1):
180 sHddPath = os.path.join(self.oTstDrv.sScratchPath, 'Test' + str(i) + '.vdi')
181 if i == 1:
182 oHd = oSession.createBaseHd(sHddPath, cb=1024*1024)
183 else:
184 oHd = oSession.createDiffHd(oHd, sHddPath)
185 if oHd is None:
186 return False;
187
188 # modify the VM config, attach HDD
189 sController='SATA Controller';
190 fRc = oSession.attachHd(sHddPath, sController, fImmutable=False, fForceResource=False);
191 if fRc:
192 fRc = oSession.saveSettings(fClose=True);
193 oSession = None;
194 if not fRc:
195 return False;
196
197 # Verify that the number of hard disks attached to the VM's only storage
198 # controller equals the number of disks created above.
199 cDisks = self.getNumOfAttachedHDs(oVM, sController);
200 reporter.log('Initial state: Number of hard disks attached = %d (should equal disks created = %d)' % (cDisks, cImages));
201 if cImages != cDisks:
202 reporter.error('Created %d disk images but found %d disks attached' % (cImages, cDisks));
203
204 # unregister the VM using "DetachAllReturnHardDisksOnly" and confirm all
205 # hard disks were returned and subsequently closed
206 fRc = self.unregisterVM(oVM, 'DetachAllReturnHardDisksOnly');
207 if not fRc:
208 return False;
209 oVM = None;
210
211 # If there is no base image (expected) then there are no leftover
212 # child images either.
213 cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox, 'hardDisks'))
214 reporter.log('After unregister(DetachAllReturnHardDisksOnly): API reports %d base images' % (cBaseImages));
215 if cBaseImages != 0:
216 reporter.error('Got %d initial base images, expected zero (0)' % (cBaseImages));
217
218 # re-register to test loading of settings
219 oVM = self.openAndRegisterMachine(sSettingsFile);
220 if oVM is None:
221 return False;
222
223 # Verify that the number of hard disks attached to the VM's only storage
224 # controller equals the number of disks created above.
225 cDisks = self.getNumOfAttachedHDs(oVM, sController);
226 reporter.log('After openMachine()+registerMachine(): Number of hard disks attached = %d '
227 '(should equal disks created = %d)' % (cDisks, cImages));
228 if cImages != cDisks:
229 reporter.error('Created %d disk images but after openMachine()+registerMachine() found %d disks attached' %
230 (cImages, cDisks));
231
232 fRc = self.unregisterVM(oVM, 'UnregisterOnly');
233 if not fRc:
234 return False;
235 oVM = None;
236
237 # When unregistering a VM with CleanupMode_UnregisterOnly the associated medium's
238 # objects will be closed when the corresponding Machine object ('oVM') is
239 # uninitialized. Assigning the 'None' object to 'oVM' will cause Python to delete
240 # the object when the garbage collector runs however this can take several seconds
241 # so we invoke the Python garbage collector manually here so we don't have to wait.
242 try:
243 gc.collect();
244 except:
245 reporter.logXcpt();
246
247 reporter.log('Waiting three seconds for Machine::uninit() to be called to close the attached disks');
248 # Fudge factor: Machine::uninit() will be invoked when the oVM object is processed
249 # by the garbage collector above but it may take a few moments to close up to 64
250 # disks.
251 time.sleep(3);
252
253 cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox, 'hardDisks'))
254 reporter.log('After unregister(UnregisterOnly): API reports %d base images' % (cBaseImages));
255 if cBaseImages != 0:
256 reporter.error('Got %d base images after unregistering, expected zero (0)' % (cBaseImages));
257
258 return reporter.testDone()[1] == 0;
259
260 def testSnapshotTreeDepth(self):
261 """
262 Test snapshot tree depth.
263 """
264 reporter.testStart('snapshotTreeDepth')
265
266 oVM = self.oTstDrv.createTestVMOnly('test-snap', 'Other');
267 if oVM is None:
268 return False;
269
270 # Save the path to the VM's settings file while oVM is valid (needed for
271 # openMachine() later when oVM is gone).
272 sSettingsFile = oVM.settingsFilePath
273
274 # modify the VM config, create and attach empty HDD
275 oSession = self.oTstDrv.openSession(oVM)
276 if oSession is None:
277 return False;
278 sHddPath = os.path.join(self.oTstDrv.sScratchPath, 'TestSnapEmpty.vdi')
279 sController='SATA Controller';
280 fRc = oSession.createAndAttachHd(sHddPath, cb=1024*1024, sController=sController, fImmutable=False);
281 fRc = fRc and oSession.saveSettings();
282 if not fRc:
283 return False;
284
285 # take up to 200 snapshots (250 is the snapshot tree depth limit (settings.h:SETTINGS_SNAPSHOT_DEPTH_MAX))
286 cSnapshots = random.randrange(1, 200);
287 reporter.log('Taking %d snapshots' % (cSnapshots));
288 for i in range(1, cSnapshots + 1):
289 fRc = oSession.takeSnapshot('Snapshot ' + str(i));
290 if not fRc:
291 return False;
292 fRc = oSession.close() and fRc and True;
293 oSession = None;
294 reporter.log('API reports %d snapshots (should equal snapshots created = %d)' % (oVM.snapshotCount, cSnapshots));
295 if oVM.snapshotCount != cSnapshots:
296 reporter.error('Got %d initial snapshots, expected %d' % (oVM.snapshotCount, cSnapshots));
297
298 # unregister the VM using "DetachAllReturnHardDisksOnly" and confirm all
299 # hard disks were returned and subsequently closed
300 fRc = self.unregisterVM(oVM, 'DetachAllReturnHardDisksOnly');
301 if not fRc:
302 return False;
303 oVM = None;
304
305 # If there is no base image (expected) then there are no leftover
306 # child images either.
307 cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox, 'hardDisks'))
308 reporter.log('After unregister(DetachAllReturnHardDisksOnly): API reports %d base images' % (cBaseImages));
309 fRc = fRc and cBaseImages == 0
310 if cBaseImages != 0:
311 reporter.error('Got %d initial base images, expected zero (0)' % (cBaseImages));
312
313 # re-register to test loading of settings
314 oVM = self.openAndRegisterMachine(sSettingsFile);
315 if oVM is None:
316 return False;
317
318 # Verify that the number of hard disks attached to the VM's only storage
319 # controller equals the number of disks created above.
320 reporter.log('After openMachine()+registerMachine(): Number of snapshots of VM = %d '
321 '(should equal snapshots created = %d)' % (oVM.snapshotCount, cSnapshots));
322 if oVM.snapshotCount != cSnapshots:
323 reporter.error('Created %d snapshots but after openMachine()+registerMachine() found %d snapshots' %
324 (cSnapshots, oVM.snapshotCount));
325
326 fRc = self.unregisterVM(oVM, 'UnregisterOnly');
327 if not fRc:
328 return False;
329 oVM = None;
330
331 # When unregistering a VM with CleanupMode_UnregisterOnly the associated medium's
332 # objects will be closed when the corresponding Machine object ('oVM') is
333 # uninitialized. Assigning the 'None' object to 'oVM' will cause Python to delete
334 # the object when the garbage collector runs however this can take several seconds
335 # so we invoke the Python garbage collector manually here so we don't have to wait.
336 try:
337 gc.collect();
338 except:
339 reporter.logXcpt();
340
341 reporter.log('Waiting three seconds for Machine::uninit() to be called to close the attached disks');
342 # Fudge factor: Machine::uninit() will be invoked when the oVM object is processed
343 # by the garbage collector above but it may take a few moments to close up to 200
344 # snapshots.
345 time.sleep(3);
346
347 cBaseImages = len(self.oTstDrv.oVBoxMgr.getArray(self.oTstDrv.oVBox, 'hardDisks'))
348 reporter.log('After unregister(UnregisterOnly): API reports %d base images' % (cBaseImages));
349 if cBaseImages != 0:
350 reporter.error('Got %d base images after unregistering, expected zero (0)' % (cBaseImages));
351
352 return reporter.testDone()[1] == 0
353
354
355if __name__ == '__main__':
356 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
357 from tdApi1 import tdApi1; # pylint: disable=relative-import
358 sys.exit(tdApi1([SubTstDrvTreeDepth1]).main(sys.argv))
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