VirtualBox

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

Last change on this file since 89097 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

  • 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 82968 2020-02-04 10:35:17Z 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.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: 82968 $"
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 iStatus = oChild.poll();
137 if iStatus is None:
138 testboxcommons.log('Checking the new testboxscript timed out.');
139 oChild.terminate();
140 oThread.join(5);
141 return False;
142 if iStatus is not TBS_EXITCODE_SYNTAX:
143 testboxcommons.log('The new testboxscript returned %d instead of %d during check.' \
144 % (iStatus, TBS_EXITCODE_SYNTAX));
145 return False;
146
147 sOutput = ''.join(asBuf);
148 sOutput = sOutput.strip();
149 try:
150 iNewVersion = int(sOutput);
151 except:
152 testboxcommons.log('The new testboxscript returned an unparseable version string: "%s"!' % (sOutput,));
153 return False;
154 testboxcommons.log('New script version: %s' % (iNewVersion,));
155 return True;
156
157def _doUpgradeApply(sUpgradeDir, asMembers):
158 """
159 # Apply the directories and files from the upgrade.
160 returns True/False/Exception.
161 """
162
163 #
164 # Create directories first since that's least intrusive.
165 #
166 for sMember in asMembers:
167 if sMember[-1] == '/':
168 sMember = sMember[len('testboxscript/'):];
169 if sMember != '':
170 sFull = os.path.join(g_ksValidationKitDir, sMember);
171 if not os.path.isdir(sFull):
172 os.makedirs(sFull, 0o755);
173
174 #
175 # Move the files into place.
176 #
177 fRc = True;
178 asOldFiles = [];
179 for sMember in asMembers:
180 if sMember[-1] != '/':
181 sSrc = os.path.join(sUpgradeDir, sMember);
182 sDst = os.path.join(g_ksValidationKitDir, sMember[len('testboxscript/'):]);
183
184 # Move the old file out of the way first.
185 sDstRm = None;
186 if os.path.exists(sDst):
187 testboxcommons.log2('Info: Installing "%s"' % (sDst,));
188 sDstRm = '%s-delete-me-%s' % (sDst, uuid.uuid4(),);
189 try:
190 os.rename(sDst, sDstRm);
191 except Exception as oXcpt:
192 testboxcommons.log('Error: failed to rename (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt));
193 try:
194 shutil.copy(sDst, sDstRm);
195 except Exception as oXcpt:
196 testboxcommons.log('Error: failed to copy (old) "%s" to "%s": %s' % (sDst, sDstRm, oXcpt));
197 break;
198 try:
199 os.unlink(sDst);
200 except Exception as oXcpt:
201 testboxcommons.log('Error: failed to unlink (old) "%s": %s' % (sDst, oXcpt));
202 break;
203
204 # Move/copy the new one into place.
205 testboxcommons.log2('Info: Installing "%s"' % (sDst,));
206 try:
207 os.rename(sSrc, sDst);
208 except Exception as oXcpt:
209 testboxcommons.log('Warning: failed to rename (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt));
210 try:
211 shutil.copy(sSrc, sDst);
212 except:
213 testboxcommons.log('Error: failed to copy (new) "%s" to "%s": %s' % (sSrc, sDst, oXcpt));
214 fRc = False;
215 break;
216
217 #
218 # Roll back on failure.
219 #
220 if fRc is not True:
221 testboxcommons.log('Attempting to roll back old files...');
222 for sDstRm in asOldFiles:
223 sDst = sDstRm[:sDstRm.rfind('-delete-me')];
224 testboxcommons.log2('Info: Rolling back "%s" (%s)' % (sDst, os.path.basename(sDstRm)));
225 try:
226 shutil.move(sDstRm, sDst);
227 except:
228 testboxcommons.log('Error: failed to rollback "%s" onto "%s": %s' % (sDstRm, sDst, oXcpt));
229 return False;
230 return True;
231
232def _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers):
233 """
234 Clean up all obsolete files and directories.
235 Returns True (shouldn't fail or raise any exceptions).
236 """
237
238 try:
239 shutil.rmtree(sUpgradeDir, ignore_errors = True);
240 except:
241 pass;
242
243 asKnownFiles = [];
244 asKnownDirs = [];
245 for sMember in asMembers:
246 sMember = sMember[len('testboxscript/'):];
247 if sMember == '':
248 continue;
249 if sMember[-1] == '/':
250 asKnownDirs.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember[:-1])));
251 else:
252 asKnownFiles.append(os.path.normpath(os.path.join(g_ksValidationKitDir, sMember)));
253
254 for sDirPath, asDirs, asFiles in os.walk(g_ksValidationKitDir, topdown=False):
255 for sDir in asDirs:
256 sFull = os.path.normpath(os.path.join(sDirPath, sDir));
257 if sFull not in asKnownDirs:
258 testboxcommons.log2('Info: Removing obsolete directory "%s"' % (sFull,));
259 try:
260 os.rmdir(sFull);
261 except Exception as oXcpt:
262 testboxcommons.log('Warning: failed to rmdir obsolete dir "%s": %s' % (sFull, oXcpt));
263
264 for sFile in asFiles:
265 sFull = os.path.normpath(os.path.join(sDirPath, sFile));
266 if sFull not in asKnownFiles:
267 testboxcommons.log2('Info: Removing obsolete file "%s"' % (sFull,));
268 try:
269 os.unlink(sFull);
270 except Exception as oXcpt:
271 testboxcommons.log('Warning: failed to unlink obsolete file "%s": %s' % (sFull, oXcpt));
272 return True;
273
274def upgradeFromZip(sZipFile):
275 """
276 Upgrade the testboxscript install using the specified zip file.
277 Returns True/False.
278 """
279
280 # A little precaution.
281 if utils.isRunningFromCheckout():
282 testboxcommons.log('Use "svn up" to "upgrade" your source tree!');
283 return False;
284
285 #
286 # Prepare.
287 #
288 # Note! Don't bother cleaning up files and dirs in the error paths,
289 # they'll be restricted to the one zip and the one upgrade dir.
290 # We'll remove them next time we upgrade.
291 #
292 oZip = zipfile.ZipFile(sZipFile, 'r');
293 asMembers = _doUpgradeCheckZip(oZip);
294 if asMembers is None:
295 return False;
296
297 sUpgradeDir = os.path.join(g_ksTestScriptDir, 'upgrade');
298 testboxcommons.log('Unzipping "%s" to "%s"...' % (sZipFile, sUpgradeDir));
299 if _doUpgradeUnzipAndCheck(oZip, sUpgradeDir, asMembers) is not True:
300 return False;
301 oZip.close();
302
303 if _doUpgradeTestRun(sUpgradeDir) is not True:
304 return False;
305
306 #
307 # Execute.
308 #
309 if _doUpgradeApply(sUpgradeDir, asMembers) is not True:
310 return False;
311 _doUpgradeRemoveOldStuff(sUpgradeDir, asMembers);
312 return True;
313
314
315# For testing purposes.
316if __name__ == '__main__':
317 sys.exit(upgradeFromZip(sys.argv[1]));
318
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