VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

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