VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/tests/api/tdSnapshots1.py

Last change on this file was 108803, checked in by vboxsync, 6 weeks ago

ValidationKit/tests/api/tdSnapshots1.py: Add a new test scenario to
tdSnapshots1.py which verifies that when IMachine::deleteSnapshot()
removes a nested online snapshot it also removes its corresponding saved
state file. Changeset r150959 introduced a regression which left the
saved state (.sav) files beind when deleting a nested live snapshot.

  • Property svn:eol-style set to LF
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 14.4 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# $Id: tdSnapshots1.py 108803 2025-03-31 17:47:02Z vboxsync $
4
5"""
6VirtualBox Validation Kit - Nested Snapshot Restoration Test #1
7"""
8
9__copyright__ = \
10"""
11Copyright (C) 2023-2024 Oracle and/or its affiliates.
12
13This file is part of VirtualBox base platform packages, as
14available from https://www.virtualbox.org.
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: 108803 $"
41
42
43# Standard Python imports.
44import os
45import sys
46
47# Only the main script needs to modify the path.
48try: __file__ # pylint: disable=used-before-assignment
49except: __file__ = sys.argv[0]
50g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
51sys.path.append(g_ksValidationKitDir)
52
53# Validation Kit imports.
54from testdriver import base
55from testdriver import reporter
56from testdriver import vbox
57from testdriver import vboxcon
58
59
60class SubTstDrvNestedLiveSnapshots1(base.SubTestDriverBase):
61 """
62 Sub-test driver for nested snapshot testing.
63 """
64 def __init__(self, oTstDrv):
65 base.SubTestDriverBase.__init__(self, oTstDrv, 'nested-live-snapshots', 'Nested Live Snapshot Testing');
66 self.sVmName = 'tst-nested-live-snapshots';
67 # Note that any VM can be used here as these tests simply involve taking
68 # online snapshots and restoring them. This OL6 image was chosen based purely
69 # on its location being in the 7.1 directory.
70 self.asRsrcs = ['7.1/ol-6u10-x86.vdi'];
71
72 #
73 # Overridden methods specified in the TestDriverBase class (testdriver/base.py).
74 #
75
76 # Handle the action to execute the test itself.
77 def testIt(self):
78 """
79 Execute the sub-testcases.
80 """
81 return self.testRestoreNestedLiveSnapshot() \
82 and self.testDeleteNestedLiveSnapshot() \
83 and self.testDeleteLiveSnapshots();
84
85 #
86 # Test execution helpers.
87 #
88 def testRestoreNestedLiveSnapshot(self):
89 """
90 The scenario being exercised here is referenced in xTracker 10252 Comment #9
91 where the sequence of restoring a nested live snapshot and then booting that restored
92 VM would accidentally delete that snapshot's saved state file during boot. So
93 here we perform the following steps to exercise this functionality:
94 + Take three online snapshots of the VM: 'alpha', 'beta' and 'gamma' (IMachine::takeSnapshot())
95 + Restore snapshot 'beta' (IMachine::restoreSnapshot())
96 + Boot and then poweroff the VM
97 + Verify snapshot 'beta' still exists (IMachine::findSnapshot())
98 """
99 reporter.testStart('testRestoreNestedLiveSnapshot');
100 reporter.log('Verify saved state file exists after nested snapshot restore');
101
102 # Restoring an online snapshot requires an updated TXS (r157880 or later) for the
103 # TCP keep alive support added in r157875 thus it is essential that the
104 # ValidationKit ISO be mounted in the VM so that TXS can auto-update if needed.
105 reporter.log('Creating test VM: \'%s\'' % self.sVmName);
106 oVM = self.oTstDrv.createTestVM(self.sVmName, 1, sHd = '7.1/ol-6u10-x86.vdi',
107 sKind = 'Oracle', fIoApic = True,
108 sDvdImage = self.oTstDrv.sVBoxValidationKitIso);
109 if oVM is None:
110 reporter.error('Error creating test VM: \'%s\'' % self.sVmName);
111
112 reporter.log('Starting test VM \'%s\' for the first time' % self.sVmName);
113 oSession, oTxsSession = self.oTstDrv.startVmAndConnectToTxsViaTcp(self.sVmName, fCdWait=True,
114 fNatForwardingForTxs = False);
115 if oSession is None or oTxsSession is None:
116 return reporter.error('Failed to start test VM: \'%s\'' % self.sVmName);
117
118 reporter.log('Guest VM \'%s\' successfully started. Connection to TXS service established.' % self.sVmName);
119 self.oTstDrv.addTask(oTxsSession);
120
121 # Take three online snapshots.
122 reporter.log('Taking three online snapshots of test VM: \'%s\'' % self.sVmName);
123 fRc = oSession.takeSnapshot('alpha');
124 fRc = fRc and oSession.takeSnapshot('beta');
125 fRc = fRc and oSession.takeSnapshot('gamma');
126 if not fRc:
127 return reporter.error('Failed to take online snapshot of test VM: \'%s\'' % self.sVmName);
128
129 # Shutdown the VM and cleanup.
130 self.oTstDrv.txsDisconnect(oSession, oTxsSession)
131 reporter.log('Shutting down test VM: \'%s\'' % self.sVmName);
132 self.oTstDrv.removeTask(oTxsSession);
133 self.oTstDrv.terminateVmBySession(oSession);
134 fRc = oSession.close() and fRc and True; # pychecker hack.
135 oSession = None;
136 oTxsSession = None;
137 if not fRc:
138 return reporter.error('Failed to take online snapshot of test VM: \'%s\'' % self.sVmName);
139
140 oVM = self.oTstDrv.getVmByName(self.sVmName);
141 oSession = self.oTstDrv.openSession(oVM);
142 if oSession is None:
143 return reporter.error('Failed to create session for test VM: \'%s\'' % self.sVmName);
144
145 oSnapshot = oSession.findSnapshot('beta');
146 if oSnapshot is None:
147 return reporter.testFailure('Failed to find snapshot \'beta\' of test VM: \'%s\'' % self.sVmName);
148
149 reporter.log('Restoring nested snapshot \'%s\' ({%s}) of test VM: \'%s\'' %
150 (oSnapshot.name, oSnapshot.id, self.sVmName));
151 fRc = oSession.restoreSnapshot(oSnapshot);
152 if not fRc:
153 return reporter.error('Failed to restore snapshot \'%s\' of test VM: \'%s\'' % (oSnapshot.name, self.sVmName));
154
155 fRc = oSession.close() and fRc and True; # pychecker hack.
156 oSession = None;
157 if not fRc:
158 return reporter.error('Failed to close session of test VM: \'%s\'' % self.sVmName);
159
160 reporter.log('Starting test VM after snapshot restore: \'%s\'' % self.sVmName);
161
162 oSession, oTxsSession = self.oTstDrv.startVmAndConnectToTxsViaTcp(self.sVmName, fCdWait=True,
163 fNatForwardingForTxs = False);
164 if oSession is None or oTxsSession is None:
165 return reporter.error('Failed to start test VM: \'%s\'' % self.sVmName);
166
167 # Display the version of TXS running in the guest VM to confirm that it is r157880 or later.
168 sTxsVer = self.oTstDrv.txsVer(oSession, oTxsSession, cMsTimeout=1000*30*60, fIgnoreErrors = True);
169 if sTxsVer is not False:
170 reporter.log('startVmAndConnectToTxsViaTcp: TestExecService version %s' % (sTxsVer));
171 else:
172 reporter.log('startVmAndConnectToTxsViaTcp: Unable to retrieve TestExecService version');
173
174 reporter.log('Guest VM \'%s\' successfully started after restoring nested snapshot.' % self.sVmName);
175 self.oTstDrv.addTask(oTxsSession);
176
177 # Shutdown the VM and cleanup.
178 reporter.log('Shutting down test VM: \'%s\'' % self.sVmName);
179 self.oTstDrv.removeTask(oTxsSession);
180 self.oTstDrv.terminateVmBySession(oSession);
181 fRc = oSession.close() and fRc and True; # pychecker hack.
182 oSession = None;
183 oTxsSession = None;
184 if not fRc:
185 return reporter.testFailure('Failed to close session of test VM: \'%s\'' % self.sVmName);
186
187 reporter.log('Verifying nested online snapshot \'beta\' still exists.');
188 oVM = self.oTstDrv.getVmByName(self.sVmName);
189 oSession = self.oTstDrv.openSession(oVM);
190 if oSession is None:
191 return reporter.error('Failed to create session for test VM: \'%s\'' % self.sVmName);
192
193 oSnapshot = oSession.findSnapshot('beta');
194 if oSnapshot is None:
195 return reporter.testFailure('Failed to find snapshot \'beta\' of test VM: \'%s\'' % self.sVmName);
196
197 return reporter.testDone()[1] == 0;
198
199
200 def testDeleteNestedLiveSnapshot(self):
201 """
202 The scenario being exercised here is to verify that removing a nested online
203 snapshot also removes its corresponding saved state ('.sav') file. A regression
204 caused IMachine::deleteSnapshot() to remove a live snapshot from the VM's
205 settings but left the '.sav' file behind. So here we perform the following steps to
206 exercise this functionality:
207 + Delete live snapshot 'gamma' (IMachine::deleteSnapshot())
208 + Check that the 'gamma' snapshot was removed
209 + Verify that the 'gamma' snapshot's '.sav' file was removed as well
210 """
211
212 reporter.testStart('testDeleteNestedLiveSnapshot');
213 reporter.log('Verify IMachine::deleteSnapshot() deletes a nested live snapshot along with its .sav file');
214 oVM = self.oTstDrv.getVmByName(self.sVmName);
215 oSession = self.oTstDrv.openSession(oVM);
216 if oSession is None:
217 return reporter.error('Failed to create session for test VM: \'%s\'' % self.sVmName);
218
219 oSnapshot = oSession.findSnapshot('gamma');
220 if oSnapshot is None:
221 return reporter.testFailure('Failed to find snapshot \'gamma\' of test VM: \'%s\'' % self.sVmName);
222
223 # Save the path to gamma's '.sav' file while the snapshot still exists for querying later.
224 strSaveFilePath = oSnapshot.machine.stateFilePath;
225 reporter.log('Calling IMachine::deleteSnapshot() to delete snapshot \'gamma\'');
226 # Call IMachine::deleteSnapshot() (or its historic equivalent) to remove the
227 # live snapshot named 'gamma'
228 fRc = oSession.deleteSnapshot(oSnapshot.id, cMsTimeout = 120 * 1000);
229 if not fRc:
230 return reporter.error('Failed to delete snapshot \'gamma\'');
231
232 # Verify that the snapshot was indeed removed as well as its corresponding saved
233 # state file.
234 reporter.log('Verifying snapshot \'gamma\' was deleted');
235 try:
236 oSnapshot = oSession.findSnapshot('gamma');
237 except vbox.ComException as oXcpt:
238 if vbox.ComError.notEqual(oXcpt, vbox.ComError.VBOX_E_OBJECT_NOT_FOUND):
239 return reporter.testFailure('Failed to delete snapshot \'gamma\' of test VM: \'%s\'' % self.sVmName);
240
241 reporter.log('Verifying that the \'gamma\' snapshot\'s \'.sav\' file was deleted');
242 if os.path.exists(strSaveFilePath):
243 return reporter.error('The saved state file of snapshot \'gamma\' was not deleted');
244
245 return reporter.testDone()[1] == 0;
246
247
248 def testDeleteLiveSnapshots(self):
249 """
250 The scenario being exercised here is also referenced in xTracker 10252 Comment #9
251 where unregistering and deleting a VM which contained one or more live snapshots would
252 neglect to delete the snapshot(s). So here we perform the following steps to
253 exercise this functionality which conveniently also tidies up our test setup:
254 + Unregister our test VM (IMachine::unregister())
255 + Delete the VM and the attached media including snapshots (IMachine::deleteConfig())
256 + Check that the snapshots are truly gone.
257 """
258
259 reporter.testStart('testDeleteLiveSnapshots');
260 reporter.log('Verify IMachine::unregister()+IMachine::deleteConfig() deletes snapshots');
261 oVM = self.oTstDrv.getVmByName(self.sVmName);
262 # IMachine::stateFilePath() isn't implemented in the testdriver so we manually
263 # retrieve the paths to the snapshots.
264 asStateFilesList = [];
265 sSnapshotFolder = oVM.snapshotFolder;
266 for sFile in os.listdir(sSnapshotFolder):
267 if sFile.endswith(".sav"):
268 sSnapshotFullPath = os.path.normcase(os.path.join(sSnapshotFolder, sFile));
269 asStateFilesList.append(sSnapshotFullPath)
270 reporter.log("Snapshot path = %s" % (sSnapshotFullPath))
271
272 reporter.log('Calling IMachine::unregister() and IMachine::deleteConfig()');
273 # Call IMachine::unregister() and IMachine::deleteConfig() (or their historic
274 # equivalents) to remove all vestiges of the VM from the system.
275 if self.oTstDrv.fpApiVer >= 4.0:
276 try:
277 oVM.unregister(vboxcon.CleanupMode_Full);
278 except:
279 return reporter.error('Failed to unregister VM \'%s\'' % self.sVmName);
280 try:
281 if self.oTstDrv.fpApiVer >= 4.3:
282 oProgress = oVM.deleteConfig([]);
283 else:
284 oProgress = oVM.delete([]);
285 except:
286 return reporter.error('Failed to delete configuration of VM \'%s\'' % self.sVmName);
287 self.oTstDrv.waitOnProgress(oProgress);
288 else:
289 try:
290 self.oTstDrv.oVBox.unregisterMachine(oVM.id);
291 except:
292 return reporter.error('Failed to unregister VM \'%s\'' % self.sVmName);
293 try:
294 oVM.deleteSettings();
295 except:
296 return reporter.error('Failed to delete configuration of VM \'%s\'' % self.sVmName);
297
298 # Verify that all of the snapshots were removed as part of the
299 # IMachine::deleteConfig() call.
300 reporter.log('Verifying no snapshots remain after IMachine::deleteConfig()');
301 for sFile in os.listdir(sSnapshotFolder):
302 if os.path.exists(sFile):
303 return reporter.error('Snapshot \'%s\' was not deleted' % sFile);
304
305 return reporter.testDone()[1] == 0;
306
307if __name__ == '__main__':
308 sys.path.append(os.path.dirname(os.path.abspath(__file__)));
309 from tdApi1 import tdApi1; # pylint: disable=relative-import
310 sys.exit(tdApi1([SubTstDrvNestedLiveSnapshots1]).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