VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testboxscript/testboxupgrade.py@ 92572

Last change on this file since 92572 was 92572, checked in by vboxsync, 4 years ago

ValKit: python3 fixing in the testboxscript upgrade code.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 11.1 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxupgrade.py 92572 2021-11-23 18:51:39Z vboxsync $
3
4"""
5TestBox Script - Upgrade from local file ZIP.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2020 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.215389.xyz. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 92572 $"
30
31# Standard python imports.
32import os
33import shutil
34import sys
35import subprocess
36import threading
37import uuid;
38import zipfile
39
40# Validation Kit imports.
41import testboxcommons
42from testboxscript import TBS_EXITCODE_SYNTAX;
43from common import utils;
44
45# Figure where we are.
46try: __file__
47except: __file__ = sys.argv[0];
48g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__));
49g_ksValidationKitDir = os.path.dirname(g_ksTestScriptDir);
50
51
52def _doUpgradeThreadProc(oStdOut, asBuf):
53 """Thread procedure for the upgrade test drive."""
54 asBuf.append(oStdOut.read());
55 return True;
56
57
58def _doUpgradeCheckZip(oZip):
59 """
60 Check that the essential files are there.
61 Returns list of members on success, None on failure.
62 """
63 asMembers = oZip.namelist();
64 if ('testboxscript/testboxscript/testboxscript.py' not in asMembers) \
65 or ('testboxscript/testboxscript/testboxscript_real.py' not in asMembers):
66 testboxcommons.log('Missing one or both testboxscripts (members: %s)' % (asMembers,));
67 return None;
68
69 for sMember in asMembers:
70 if not sMember.startswith('testboxscript/'):
71 testboxcommons.log('zip file contains member outside testboxscript/: "%s"' % (sMember,));
72 return None;
73 if sMember.find('/../') > 0 or sMember.endswith('/..'):
74 testboxcommons.log('zip file contains member with escape sequence: "%s"' % (sMember,));
75 return None;
76
77 return asMembers;
78
79def _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers):
80 """
81 Unzips the files into sUpdateDir, does chmod(755) on all files and
82 checks that there are no symlinks or special files.
83 Returns True/False.
84 """
85 #
86 # Extract the files.
87 #
88 if os.path.exists(sUpgradeDir):
89 shutil.rmtree(sUpgradeDir);
90 for sMember in asMembers:
91 if sMember.endswith('/'):
92 os.makedirs(os.path.join(sUpgradeDir, sMember.replace('/', os.path.sep)), 0o775);
93 else:
94 oZip.extract(sMember, sUpgradeDir);
95
96 #
97 # Make all files executable and make sure only owner can write to them.
98 # While at it, also check that there are only files and directory, no
99 # symbolic links or special stuff.
100 #
101 for sMember in asMembers:
102 sFull = os.path.join(sUpgradeDir, sMember);
103 if sMember.endswith('/'):
104 if not os.path.isdir(sFull):
105 testboxcommons.log('Not directory: "%s"' % sFull);
106 return False;
107 else:
108 if not os.path.isfile(sFull):
109 testboxcommons.log('Not regular file: "%s"' % sFull);
110 return False;
111 try:
112 os.chmod(sFull, 0o755);
113 except Exception as oXcpt:
114 testboxcommons.log('warning chmod error on %s: %s' % (sFull, oXcpt));
115 return True;
116
117def _doUpgradeTestRun(sUpgradeDir):
118 """
119 Do a testrun of the new script, to make sure it doesn't fail with
120 to run in any way because of old python, missing import or generally
121 busted upgrade.
122 Returns True/False.
123 """
124 asArgs = [os.path.join(sUpgradeDir, 'testboxscript', 'testboxscript', 'testboxscript.py'), '--version' ];
125 testboxcommons.log('Testing the new testbox script (%s)...' % (asArgs[0],));
126 if sys.executable:
127 asArgs.insert(0, sys.executable);
128 oChild = subprocess.Popen(asArgs, shell = False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT);
129
130 asBuf = []
131 oThread = threading.Thread(target=_doUpgradeThreadProc, args=(oChild.stdout, asBuf));
132 oThread.daemon = True;
133 oThread.start();
134 oThread.join(30);
135
136 oChild.wait(5);
137 iStatus = oChild.poll();
138 if iStatus is None:
139 testboxcommons.log('Checking the new testboxscript timed out.');
140 oChild.terminate();
141 oThread.join(5);
142 return False;
143 if iStatus is not TBS_EXITCODE_SYNTAX:
144 testboxcommons.log('The new testboxscript returned %d instead of %d during check.' \
145 % (iStatus, TBS_EXITCODE_SYNTAX));
146 return False;
147
148 sOutput = b''.join(asBuf)).decode('utf-8');
149 sOutput = sOutput.strip();
150 try:
151 iNewVersion = int(sOutput);
152 except:
153 testboxcommons.log('The new testboxscript returned an unparseable version string: "%s"!' % (sOutput,));
154 return False;
155 testboxcommons.log('New script version: %s' % (iNewVersion,));
156 return True;
157
158def _doUpgradeApply(sUpgradeDir, asMembers):
159 """
160 # Apply the directories and files from the upgrade.
161 returns True/False/Exception.
162 """
163
164 #
165 # Create directories first since that's least intrusive.
166 #
167 for sMember in asMembers:
168 if sMember[-1] == '/':
169 sMember = sMember[len('testboxscript/'):];
170 if sMember != '':
171 sFull = os.path.join(g_ksValidationKitDir, sMember);
172 if not os.path.isdir(sFull):
173 os.makedirs(sFull, 0o755);
174
175 #
176 # Move the files into place.
177 #
178 fRc = True;
179 asOldFiles = [];
180 for sMember in asMembers:
181 if sMember[-1] != '/':
182 sSrc = os.path.join(sUpgradeDir, sMember);
183 sDst = os.path.join(g_ksValidationKitDir, sMember[len('testboxscript/'):]);
184
185 # Move the old file out of the way first.
186 sDstRm = None;
187 if os.path.exists(sDst):
188 testboxcommons.log2('Info: Installing "%s"' % (sDst,));
189 sDstRm = '%s-delete-me-%s' % (sDst, uuid.uuid4(),);
190 try:
191 os.rename(sDst, sDstRm);
192 except Exception as oXcpt:
193 testboxcommons.log('Error: failed to rename (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt));
194 try:
195 shutil.copy(sDst, sDstRm);
196 except Exception as oXcpt:
197 testboxcommons.log('Error: failed to copy (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt));
198 break;
199 try:
200 os.unlink(sDst);
201 except Exception as oXcpt:
202 testboxcommons.log('Error: failed to unlink (old) "%s": %s' % (sDst, oXcpt));
203 break;
204
205 # Move/copy the new one into place.
206 testboxcommons.log2('Info: Installing "%s"' % (sDst,));
207 try:
208 os.rename(sSrc, sDst);
209 except Exception as oXcpt:
210 testboxcommons.log('Warning: failed to rename (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt));
211 try:
212 shutil.copy(sSrc, sDst);
213 except:
214 testboxcommons.log('Error: failed to copy (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt));
215 fRc = False;
216 break;
217
218 #
219 # Roll back on failure.
220 #
221 if fRc is not True:
222 testboxcommons.log('Attempting to roll back old files...');
223 for sDstRm in asOldFiles:
224 sDst = sDstRm[:sDstRm.rfind('-delete-me')];
225 testboxcommons.log2('Info: Rolling back "%s" (%s)' % (sDst, os.path.basename(sDstRm)));
226 try:
227 shutil.move(sDstRm, sDst);
228 except:
229 testboxcommons.log('Error: failed to rollback "%s" onto "%s": %s' % (sDstRm, sDst, oXcpt));
230 return False;
231 return True;
232
233def _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers):
234 """
235 Clean up all obsolete files and directories.
236 Returns True (shouldn't fail or raise any exceptions).
237 """
238
239 try:
240 shutil.rmtree(sUpgradeDir, ignore_errors = True);
241 except:
242 pass;
243
244 asKnownFiles = [];
245 asKnownDirs = [];
246 for sMember in asMembers:
247 sMember = sMember[len('testboxscript/'):];
248 if sMember == '':
249 continue;
250 if sMember[-1] == '/':
251 asKnownDirs.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember[:-1])));
252 else:
253 asKnownFiles.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember)));
254
255 for sDirPath, asDirs, asFiles in os.walk(g_ksValidationKitDir, topdown=False):
256 for sDir in asDirs:
257 sFull = os.path.normpath(os.path.join(sDirPath, sDir));
258 if sFull not in asKnownDirs:
259 testboxcommons.log2('Info: Removing obsolete directory "%s"' % (sFull,));
260 try:
261 os.rmdir(sFull);
262 except Exception as oXcpt:
263 testboxcommons.log('Warning: failed to rmdir obsolete dir "%s": %s' % (sFull, oXcpt));
264
265 for sFile in asFiles:
266 sFull = os.path.normpath(os.path.join(sDirPath, sFile));
267 if sFull not in asKnownFiles:
268 testboxcommons.log2('Info: Removing obsolete file "%s"' % (sFull,));
269 try:
270 os.unlink(sFull);
271 except Exception as oXcpt:
272 testboxcommons.log('Warning: failed to unlink obsolete file "%s": %s' % (sFull, oXcpt));
273 return True;
274
275def upgradeFromZip(sZipFile):
276 """
277 Upgrade the testboxscript install using the specified zip file.
278 Returns True/False.
279 """
280
281 # A little precaution.
282 if utils.isRunningFromCheckout():
283 testboxcommons.log('Use "svn up" to "upgrade" your source tree!');
284 return False;
285
286 #
287 # Prepare.
288 #
289 # Note! Don't bother cleaning up files and dirs in the error paths,
290 # they'll be restricted to the one zip and the one upgrade dir.
291 # We'll remove them next time we upgrade.
292 #
293 oZip = zipfile.ZipFile(sZipFile, 'r');
294 asMembers = _doUpgradeCheckZip(oZip);
295 if asMembers is None:
296 return False;
297
298 sUpgradeDir = os.path.join(g_ksTestScriptDir, 'upgrade');
299 testboxcommons.log('Unzipping "%s" to "%s"...' % (sZipFile, sUpgradeDir));
300 if _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers) is not True:
301 return False;
302 oZip.close();
303
304 if _doUpgradeTestRun(sUpgradeDir) is not True:
305 return False;
306
307 #
308 # Execute.
309 #
310 if _doUpgradeApply(sUpgradeDir, asMembers) is not True:
311 return False;
312 _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers);
313 return True;
314
315
316# For testing purposes.
317if __name__ == '__main__':
318 sys.exit(upgradeFromZip(sys.argv[1]));
319
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