VirtualBox

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

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

scm --update-copyright-year

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