VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testboxscript/testboxcommand.py@ 93636

Last change on this file since 93636 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: 13.1 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: testboxcommand.py 93115 2022-01-01 11:31:46Z vboxsync $
3
4"""
5TestBox Script - Command Processor.
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 sys;
34import threading;
35
36# Validation Kit imports.
37from common import constants;
38from common import utils, webutils;
39import testboxcommons;
40from testboxcommons import TestBoxException;
41from testboxscript import TBS_EXITCODE_NEED_UPGRADE;
42from testboxupgrade import upgradeFromZip;
43from testboxtasks import TestBoxExecTask, TestBoxCleanupTask, TestBoxTestDriverTask;
44
45# Figure where we are.
46try: __file__
47except: __file__ = sys.argv[0];
48g_ksTestScriptDir = os.path.dirname(os.path.abspath(__file__));
49
50
51
52class TestBoxCommand(object):
53 """
54 Implementation of Test Box command.
55 """
56
57 ## The time to wait on the current task to abort.
58 kcSecStopTimeout = 360
59 ## The time to wait on the current task to abort before rebooting.
60 kcSecStopBeforeRebootTimeout = 360
61
62 def __init__(self, oTestBoxScript):
63 """
64 Class instance init
65 """
66 self._oTestBoxScript = oTestBoxScript;
67 self._oCurTaskLock = threading.RLock();
68 self._oCurTask = None;
69
70 # List of available commands and their handlers
71 self._dfnCommands = \
72 {
73 constants.tbresp.CMD_IDLE: self._cmdIdle,
74 constants.tbresp.CMD_WAIT: self._cmdWait,
75 constants.tbresp.CMD_EXEC: self._cmdExec,
76 constants.tbresp.CMD_ABORT: self._cmdAbort,
77 constants.tbresp.CMD_REBOOT: self._cmdReboot,
78 constants.tbresp.CMD_UPGRADE: self._cmdUpgrade,
79 constants.tbresp.CMD_UPGRADE_AND_REBOOT: self._cmdUpgradeAndReboot,
80 constants.tbresp.CMD_SPECIAL: self._cmdSpecial,
81 }
82
83 def _cmdIdle(self, oResponse, oConnection):
84 """
85 Idle response, no ACK.
86 """
87 oResponse.checkParameterCount(1);
88
89 # The dispatch loop will delay for us, so nothing to do here.
90 _ = oConnection; # Leave the connection open.
91 return True;
92
93 def _cmdWait(self, oResponse, oConnection):
94 """
95 Gang scheduling wait response, no ACK.
96 """
97 oResponse.checkParameterCount(1);
98
99 # The dispatch loop will delay for us, so nothing to do here.
100 _ = oConnection; # Leave the connection open.
101 return True;
102
103 def _cmdExec(self, oResponse, oConnection):
104 """
105 Execute incoming command
106 """
107
108 # Check if required parameters given and make a little sense.
109 idResult = oResponse.getIntChecked( constants.tbresp.EXEC_PARAM_RESULT_ID, 1);
110 sScriptZips = oResponse.getStringChecked(constants.tbresp.EXEC_PARAM_SCRIPT_ZIPS);
111 sScriptCmdLine = oResponse.getStringChecked(constants.tbresp.EXEC_PARAM_SCRIPT_CMD_LINE);
112 cSecTimeout = oResponse.getIntChecked( constants.tbresp.EXEC_PARAM_TIMEOUT, 30);
113 oResponse.checkParameterCount(5);
114
115 sScriptFile = utils.argsGetFirst(sScriptCmdLine);
116 if sScriptFile is None:
117 raise TestBoxException('Bad script command line: "%s"' % (sScriptCmdLine,));
118 if len(os.path.basename(sScriptFile)) < len('t.py'):
119 raise TestBoxException('Script file name too short: "%s"' % (sScriptFile,));
120 if len(sScriptZips) < len('x.zip'):
121 raise TestBoxException('Script zip name too short: "%s"' % (sScriptFile,));
122
123 # One task at the time.
124 if self.isRunning():
125 raise TestBoxException('Already running other command');
126
127 # Don't bother running the task without the shares mounted.
128 self._oTestBoxScript.mountShares(); # Raises exception on failure.
129
130 # Kick off the task and ACK the command.
131 self._oCurTaskLock.acquire();
132 try:
133 self._oCurTask = TestBoxExecTask(self._oTestBoxScript, idResult = idResult, sScriptZips = sScriptZips,
134 sScriptCmdLine = sScriptCmdLine, cSecTimeout = cSecTimeout);
135 finally:
136 self._oCurTaskLock.release();
137 oConnection.sendAckAndClose(constants.tbresp.CMD_EXEC);
138 return True;
139
140 def _cmdAbort(self, oResponse, oConnection):
141 """
142 Abort background task
143 """
144 oResponse.checkParameterCount(1);
145 oConnection.sendAck(constants.tbresp.CMD_ABORT);
146
147 oCurTask = self._getCurTask();
148 if oCurTask is not None:
149 oCurTask.terminate();
150 oCurTask.flushLogOnConnection(oConnection);
151 oConnection.close();
152 oCurTask.wait(self.kcSecStopTimeout);
153
154 return True;
155
156 def doReboot(self):
157 """
158 Worker common to _cmdReboot and _doUpgrade that performs a system reboot.
159 """
160 # !! Not more exceptions beyond this point !!
161 testboxcommons.log('Rebooting');
162
163 # Stop anything that might be executing at this point.
164 oCurTask = self._getCurTask();
165 if oCurTask is not None:
166 oCurTask.terminate();
167 oCurTask.wait(self.kcSecStopBeforeRebootTimeout);
168
169 # Invoke shutdown command line utility.
170 sOs = utils.getHostOs();
171 asCmd2 = None;
172 if sOs == 'win':
173 asCmd = ['shutdown', '/r', '/t', '0', '/c', '"ValidationKit triggered reboot"', '/d', '4:1'];
174 elif sOs == 'os2':
175 asCmd = ['setboot', '/B'];
176 elif sOs in ('solaris',):
177 asCmd = ['/usr/sbin/reboot', '-p'];
178 asCmd2 = ['/usr/sbin/reboot']; # Hack! S10 doesn't have -p, but don't know how to reliably detect S10.
179 else:
180 asCmd = ['/sbin/shutdown', '-r', 'now'];
181 try:
182 utils.sudoProcessOutputChecked(asCmd);
183 except Exception as oXcpt:
184 if asCmd2 is not None:
185 try:
186 utils.sudoProcessOutputChecked(asCmd2);
187 except Exception as oXcpt:
188 testboxcommons.log('Error executing reboot command "%s" as well as "%s": %s' % (asCmd, asCmd2, oXcpt));
189 return False;
190 testboxcommons.log('Error executing reboot command "%s": %s' % (asCmd, oXcpt));
191 return False;
192
193 # Quit the script.
194 while True:
195 sys.exit(32);
196 return True;
197
198 def _cmdReboot(self, oResponse, oConnection):
199 """
200 Reboot Test Box
201 """
202 oResponse.checkParameterCount(1);
203 oConnection.sendAckAndClose(constants.tbresp.CMD_REBOOT);
204 return self.doReboot();
205
206 def _doUpgrade(self, oResponse, oConnection, fReboot):
207 """
208 Common worker for _cmdUpgrade and _cmdUpgradeAndReboot.
209 Will sys.exit on success!
210 """
211
212 #
213 # The server specifies a ZIP archive with the new scripts. It's ASSUMED
214 # that the zip is of selected files at g_ksValidationKitDir in SVN. It's
215 # further ASSUMED that we're executing from
216 #
217 sZipUrl = oResponse.getStringChecked(constants.tbresp.UPGRADE_PARAM_URL)
218 oResponse.checkParameterCount(2);
219
220 if utils.isRunningFromCheckout():
221 raise TestBoxException('Cannot upgrade when running from the tree!');
222 oConnection.sendAckAndClose(constants.tbresp.CMD_UPGRADE_AND_REBOOT if fReboot else constants.tbresp.CMD_UPGRADE);
223
224 testboxcommons.log('Upgrading...');
225
226 #
227 # Download the file and install it.
228 #
229 sDstFile = os.path.join(g_ksTestScriptDir, 'VBoxTestBoxScript.zip');
230 if os.path.exists(sDstFile):
231 os.unlink(sDstFile);
232 fRc = webutils.downloadFile(sZipUrl, sDstFile, self._oTestBoxScript.getPathBuilds(), testboxcommons.log);
233 if fRc is not True:
234 return False;
235
236 if upgradeFromZip(sDstFile) is not True:
237 return False;
238
239 #
240 # Restart the system or the script (we have a parent script which
241 # respawns us when we quit).
242 #
243 if fReboot:
244 self.doReboot();
245 sys.exit(TBS_EXITCODE_NEED_UPGRADE);
246 return False; # shuts up pylint (it will probably complain later when it learns DECL_NO_RETURN).
247
248 def _cmdUpgrade(self, oResponse, oConnection):
249 """
250 Upgrade Test Box Script
251 """
252 return self._doUpgrade(oResponse, oConnection, False);
253
254 def _cmdUpgradeAndReboot(self, oResponse, oConnection):
255 """
256 Upgrade Test Box Script
257 """
258 return self._doUpgrade(oResponse, oConnection, True);
259
260 def _cmdSpecial(self, oResponse, oConnection):
261 """
262 Reserved for future fun.
263 """
264 oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NOTSUP, constants.tbresp.CMD_SPECIAL);
265 testboxcommons.log('Special command %s not supported...' % (oResponse,));
266 return False;
267
268
269 def handleCommand(self, oResponse, oConnection):
270 """
271 Handles a command from the test manager.
272
273 Some commands will close the connection, others (generally the simple
274 ones) wont, leaving the caller the option to use it for log flushing.
275
276 Returns success indicator.
277 Raises no exception.
278 """
279 try:
280 sCmdName = oResponse.getStringChecked(constants.tbresp.ALL_PARAM_RESULT);
281 except Exception as oXcpt:
282 oConnection.close();
283 return False;
284
285 # Do we know the command?
286 fRc = False;
287 if sCmdName in self._dfnCommands:
288 testboxcommons.log(sCmdName);
289 try:
290 # Execute the handler.
291 fRc = self._dfnCommands[sCmdName](oResponse, oConnection)
292 except Exception as oXcpt:
293 # NACK the command if an exception is raised during parameter validation.
294 testboxcommons.log1Xcpt('Exception executing "%s": %s' % (sCmdName, oXcpt));
295 if oConnection.isConnected():
296 try:
297 oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NACK, sCmdName);
298 except Exception as oXcpt2:
299 testboxcommons.log('Failed to NACK "%s": %s' % (sCmdName, oXcpt2));
300 elif sCmdName in [constants.tbresp.STATUS_DEAD, constants.tbresp.STATUS_NACK]:
301 testboxcommons.log('Received status instead of command: %s' % (sCmdName, ));
302 else:
303 # NOTSUP the unknown command.
304 testboxcommons.log('Received unknown command: %s' % (sCmdName, ));
305 try:
306 oConnection.sendReplyAndClose(constants.tbreq.COMMAND_NOTSUP, sCmdName);
307 except Exception as oXcpt:
308 testboxcommons.log('Failed to NOTSUP "%s": %s' % (sCmdName, oXcpt));
309 return fRc;
310
311 def resumeIncompleteCommand(self):
312 """
313 Resumes an incomplete command at startup.
314
315 The EXEC commands saves essential state information in the scratch area
316 so we can resume them in case the testbox panics or is rebooted.
317 Current "resume" means doing cleanups, but we may need to implement
318 test scenarios involving rebooting the testbox later.
319
320 Returns (idTestBox, sTestBoxName, True) if a command was resumed,
321 otherwise (-1, '', False). Raises no exceptions.
322 """
323
324 try:
325 oTask = TestBoxCleanupTask(self._oTestBoxScript);
326 except:
327 return (-1, '', False);
328
329 self._oCurTaskLock.acquire();
330 self._oCurTask = oTask;
331 self._oCurTaskLock.release();
332
333 return (oTask.idTestBox, oTask.sTestBoxName, True);
334
335 def isRunning(self):
336 """
337 Check if we're running a task or not.
338 """
339 oCurTask = self._getCurTask();
340 return oCurTask is not None and oCurTask.isRunning();
341
342 def flushLogOnConnection(self, oGivenConnection):
343 """
344 Flushes the log of any running task with a log buffer.
345 """
346 oCurTask = self._getCurTask();
347 if oCurTask is not None and isinstance(oCurTask, TestBoxTestDriverTask):
348 return oCurTask.flushLogOnConnection(oGivenConnection);
349 return None;
350
351 def _getCurTask(self):
352 """ Gets the current task in a paranoidly safe manny. """
353 self._oCurTaskLock.acquire();
354 oCurTask = self._oCurTask;
355 self._oCurTaskLock.release();
356 return oCurTask;
357
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