VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py@ 65787

Last change on this file since 65787 was 65423, checked in by vboxsync, 8 years ago

WuiListContentBase: More testbox sorting (weekly meeting again).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.6 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: wuiadmintestbox.py 65423 2017-01-24 14:40:08Z vboxsync $
3
4"""
5Test Manager WUI - TestBox.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2016 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: 65423 $"
30
31
32# Standard python imports.
33import socket;
34
35# Validation Kit imports.
36from common import utils, webutils;
37from testmanager.webui.wuicontentbase import WuiContentBase, WuiListContentWithActionBase, WuiFormContentBase, WuiLinkBase, \
38 WuiSvnLink, WuiTmLink, WuiSpanText, WuiRawHtml;
39from testmanager.core.db import TMDatabaseConnection;
40from testmanager.core.schedgroup import SchedGroupLogic, SchedGroupData;
41from testmanager.core.testbox import TestBoxData, TestBoxDataEx, TestBoxLogic;
42from testmanager.core.testset import TestSetData;
43from testmanager.core.db import isDbTimestampInfinity;
44
45
46
47class WuiTestBoxDetailsLink(WuiTmLink):
48 """ Test box details link by ID. """
49
50 def __init__(self, idTestBox, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False, tsNow = None):
51 from testmanager.webui.wuiadmin import WuiAdmin;
52 dParams = {
53 WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails,
54 TestBoxData.ksParam_idTestBox: idTestBox,
55 };
56 if tsNow is not None:
57 dParams[WuiAdmin.ksParamEffectiveDate] = tsNow; ## ??
58 WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams, fBracketed = fBracketed);
59 self.idTestBox = idTestBox;
60
61
62
63class WuiTestBox(WuiFormContentBase):
64 """
65 WUI TestBox Form Content Generator.
66 """
67
68 def __init__(self, oData, sMode, oDisp):
69 if sMode == WuiFormContentBase.ksMode_Add:
70 sTitle = 'Create TextBox';
71 if oData.uuidSystem is not None and len(oData.uuidSystem) > 10:
72 sTitle += ' - ' + oData.uuidSystem;
73 elif sMode == WuiFormContentBase.ksMode_Edit:
74 sTitle = 'Edit TestBox - %s (#%s)' % (oData.sName, oData.idTestBox);
75 else:
76 assert sMode == WuiFormContentBase.ksMode_Show;
77 sTitle = 'TestBox - %s (#%s)' % (oData.sName, oData.idTestBox);
78 WuiFormContentBase.__init__(self, oData, sMode, 'TestBox', oDisp, sTitle);
79
80 # Try enter sName as hostname (no domain) when creating the testbox.
81 if sMode == WuiFormContentBase.ksMode_Add \
82 and self._oData.sName in [None, ''] \
83 and self._oData.ip not in [None, '']:
84 try:
85 (self._oData.sName, _, _) = socket.gethostbyaddr(self._oData.ip);
86 except:
87 pass;
88 offDot = self._oData.sName.find('.');
89 if offDot > 0:
90 self._oData.sName = self._oData.sName[:offDot];
91
92
93 def _populateForm(self, oForm, oData):
94 oForm.addIntRO( TestBoxData.ksParam_idTestBox, oData.idTestBox, 'TestBox ID');
95 oForm.addIntRO( TestBoxData.ksParam_idGenTestBox, oData.idGenTestBox, 'TestBox generation ID');
96 oForm.addTimestampRO(TestBoxData.ksParam_tsEffective, oData.tsEffective, 'Last changed');
97 oForm.addTimestampRO(TestBoxData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)');
98 oForm.addIntRO( TestBoxData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID');
99
100 oForm.addText( TestBoxData.ksParam_ip, oData.ip, 'TestBox IP Address'); ## make read only??
101 oForm.addUuid( TestBoxData.ksParam_uuidSystem, oData.uuidSystem, 'TestBox System/Firmware UUID');
102 oForm.addText( TestBoxData.ksParam_sName, oData.sName, 'TestBox Name');
103 oForm.addText( TestBoxData.ksParam_sDescription, oData.sDescription, 'TestBox Description');
104 oForm.addCheckBox( TestBoxData.ksParam_fEnabled, oData.fEnabled, 'Enabled');
105 oForm.addComboBox( TestBoxData.ksParam_enmLomKind, oData.enmLomKind, 'Lights-out-management',
106 TestBoxData.kaoLomKindDescs);
107 oForm.addText( TestBoxData.ksParam_ipLom, oData.ipLom, 'Lights-out-management IP Address');
108 oForm.addInt( TestBoxData.ksParam_pctScaleTimeout, oData.pctScaleTimeout, 'Timeout scale factor (%)');
109
110 oForm.addListOfSchedGroupsForTestBox(TestBoxDataEx.ksParam_aoInSchedGroups,
111 oData.aoInSchedGroups,
112 SchedGroupLogic(TMDatabaseConnection()).fetchOrderedByName(),
113 'Scheduling Group');
114 # Command, comment and submit button.
115 if self._sMode == WuiFormContentBase.ksMode_Edit:
116 oForm.addComboBox(TestBoxData.ksParam_enmPendingCmd, oData.enmPendingCmd, 'Pending command',
117 TestBoxData.kaoTestBoxCmdDescs);
118 else:
119 oForm.addComboBoxRO(TestBoxData.ksParam_enmPendingCmd, oData.enmPendingCmd, 'Pending command',
120 TestBoxData.kaoTestBoxCmdDescs);
121 oForm.addMultilineText(TestBoxData.ksParam_sComment, oData.sComment, 'Comment');
122 if self._sMode != WuiFormContentBase.ksMode_Show:
123 oForm.addSubmit('Create TestBox' if self._sMode == WuiFormContentBase.ksMode_Add else 'Change TestBox');
124
125 return True;
126
127
128 def _generatePostFormContent(self, oData):
129 from testmanager.webui.wuihlpform import WuiHlpForm;
130
131 oForm = WuiHlpForm('testbox-machine-settable', '', fReadOnly = True);
132 oForm.addTextRO( TestBoxData.ksParam_sOs, oData.sOs, 'TestBox OS');
133 oForm.addTextRO( TestBoxData.ksParam_sOsVersion, oData.sOsVersion, 'TestBox OS version');
134 oForm.addTextRO( TestBoxData.ksParam_sCpuArch, oData.sCpuArch, 'TestBox OS kernel architecture');
135 oForm.addTextRO( TestBoxData.ksParam_sCpuVendor, oData.sCpuVendor, 'TestBox CPU vendor');
136 oForm.addTextRO( TestBoxData.ksParam_sCpuName, oData.sCpuName, 'TestBox CPU name');
137 if oData.lCpuRevision:
138 oForm.addTextRO( TestBoxData.ksParam_lCpuRevision, '%#x' % (oData.lCpuRevision,), 'TestBox CPU revision',
139 sPostHtml = ' (family=%#x model=%#x stepping=%#x)'
140 % (oData.getCpuFamily(), oData.getCpuModel(), oData.getCpuStepping(),),
141 sSubClass = 'long');
142 else:
143 oForm.addLongRO( TestBoxData.ksParam_lCpuRevision, oData.lCpuRevision, 'TestBox CPU revision');
144 oForm.addIntRO( TestBoxData.ksParam_cCpus, oData.cCpus, 'Number of CPUs, cores and threads');
145 oForm.addCheckBoxRO( TestBoxData.ksParam_fCpuHwVirt, oData.fCpuHwVirt, 'VT-x or AMD-V supported');
146 oForm.addCheckBoxRO( TestBoxData.ksParam_fCpuNestedPaging, oData.fCpuNestedPaging, 'Nested paging supported');
147 oForm.addCheckBoxRO( TestBoxData.ksParam_fCpu64BitGuest, oData.fCpu64BitGuest, '64-bit guest supported');
148 oForm.addCheckBoxRO( TestBoxData.ksParam_fChipsetIoMmu, oData.fChipsetIoMmu, 'I/O MMU supported');
149 oForm.addMultilineTextRO(TestBoxData.ksParam_sReport, oData.sReport, 'Hardware/software report');
150 oForm.addLongRO( TestBoxData.ksParam_cMbMemory, oData.cMbMemory, 'Installed RAM size (MB)');
151 oForm.addLongRO( TestBoxData.ksParam_cMbScratch, oData.cMbScratch, 'Available scratch space (MB)');
152 oForm.addIntRO( TestBoxData.ksParam_iTestBoxScriptRev, oData.iTestBoxScriptRev,
153 'TestBox Script SVN revision');
154 sHexVer = oData.formatPythonVersion();
155 oForm.addIntRO( TestBoxData.ksParam_iPythonHexVersion, oData.iPythonHexVersion,
156 'Python version (hex)', sPostHtml = webutils.escapeElem(sHexVer));
157 return [('Machine Only Settables', oForm.finalize()),];
158
159
160
161class WuiTestBoxList(WuiListContentWithActionBase):
162 """
163 WUI TestBox List Content Generator.
164 """
165
166 ## Descriptors for the combo box.
167 kasTestBoxActionDescs = \
168 [ \
169 [ 'none', 'Select an action...', '' ],
170 [ 'enable', 'Enable', '' ],
171 [ 'disable', 'Disable', '' ],
172 TestBoxData.kaoTestBoxCmdDescs[1],
173 TestBoxData.kaoTestBoxCmdDescs[2],
174 TestBoxData.kaoTestBoxCmdDescs[3],
175 TestBoxData.kaoTestBoxCmdDescs[4],
176 TestBoxData.kaoTestBoxCmdDescs[5],
177 ];
178
179 ## Boxes which doesn't report in for more than 15 min are considered dead.
180 kcSecMaxStatusDeltaAlive = 15*60
181
182 def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None):
183 # type: (list[TestBoxDataForListing], int, int, datetime.datetime, ignore, WuiAdmin) -> None
184 WuiListContentWithActionBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective,
185 sTitle = 'TestBoxes', sId = 'users', fnDPrint = fnDPrint, oDisp = oDisp,
186 aiSelectedSortColumns = aiSelectedSortColumns);
187 self._asColumnHeaders.extend([ 'Name', 'LOM', 'Status', 'Cmd',
188 'Note', 'Script', 'Python', 'Group',
189 'OS', 'CPU', 'Features', 'CPUs', 'RAM', 'Scratch',
190 'Actions' ]);
191 self._asColumnAttribs.extend([ 'align="center"', 'align="center"', 'align="center"', 'align="center"'
192 'align="center"', 'align="center"', 'align="center"', 'align="center"',
193 '', '', '', 'align="left"', 'align="right"', 'align="right"', 'align="right"',
194 'align="center"' ]);
195 self._aaiColumnSorting.extend([
196 (TestBoxLogic.kiSortColumn_sName,),
197 None, # LOM
198 (-TestBoxLogic.kiSortColumn_fEnabled, TestBoxLogic.kiSortColumn_enmState, -TestBoxLogic.kiSortColumn_tsUpdated,),
199 (TestBoxLogic.kiSortColumn_enmPendingCmd,),
200 None, # Note
201 (TestBoxLogic.kiSortColumn_iTestBoxScriptRev,),
202 (TestBoxLogic.kiSortColumn_iPythonHexVersion,),
203 None, # Group
204 (TestBoxLogic.kiSortColumn_sOs, TestBoxLogic.kiSortColumn_sOsVersion, TestBoxLogic.kiSortColumn_sCpuArch,),
205 (TestBoxLogic.kiSortColumn_sCpuVendor, TestBoxLogic.kiSortColumn_lCpuRevision,),
206 (TestBoxLogic.kiSortColumn_fCpuNestedPaging,),
207 (TestBoxLogic.kiSortColumn_cCpus,),
208 (TestBoxLogic.kiSortColumn_cMbMemory,),
209 (TestBoxLogic.kiSortColumn_cMbScratch,),
210 None, # Actions
211 ]);
212 assert len(self._aaiColumnSorting) == len(self._asColumnHeaders);
213 self._aoActions = list(self.kasTestBoxActionDescs);
214 self._sAction = oDisp.ksActionTestBoxListPost;
215 self._sCheckboxName = TestBoxData.ksParam_idTestBox;
216
217 def show(self, fShowNavigation = True):
218 """ Adds some stats at the bottom of the page """
219 (sTitle, sBody) = super(WuiTestBoxList, self).show(fShowNavigation);
220
221 # Count boxes in interesting states.
222 if len(self._aoEntries) > 0:
223 cActive = 0;
224 cDead = 0;
225 for oTestBox in self._aoEntries:
226 if oTestBox.oStatus is not None:
227 oDelta = oTestBox.tsCurrent - oTestBox.oStatus.tsUpdated;
228 if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive:
229 if oTestBox.fEnabled:
230 cActive += 1;
231 else:
232 cDead += 1;
233 else:
234 cDead += 1;
235 sBody += '<div id="testboxsummary"><p>\n' \
236 '%s testboxes of which %s are active and %s dead' \
237 '</p></div>\n' \
238 % (len(self._aoEntries), cActive, cDead,)
239 return (sTitle, sBody);
240
241 def _formatListEntry(self, iEntry): # pylint: disable=R0914
242 from testmanager.webui.wuiadmin import WuiAdmin;
243 oEntry = self._aoEntries[iEntry];
244
245 # Lights outs managment.
246 if oEntry.enmLomKind == TestBoxData.ksLomKind_ILOM:
247 aoLom = [ WuiLinkBase('ILOM', 'https://%s/' % (oEntry.ipLom,), fBracketed = False), ];
248 elif oEntry.enmLomKind == TestBoxData.ksLomKind_ELOM:
249 aoLom = [ WuiLinkBase('ELOM', 'http://%s/' % (oEntry.ipLom,), fBracketed = False), ];
250 elif oEntry.enmLomKind == TestBoxData.ksLomKind_AppleXserveLom:
251 aoLom = [ 'Apple LOM' ];
252 elif oEntry.enmLomKind == TestBoxData.ksLomKind_None:
253 aoLom = [ 'none' ];
254 else:
255 aoLom = [ 'Unexpected enmLomKind value "%s"' % (oEntry.enmLomKind,) ];
256 if oEntry.ipLom is not None:
257 if oEntry.enmLomKind in [ TestBoxData.ksLomKind_ILOM, TestBoxData.ksLomKind_ELOM ]:
258 aoLom += [ WuiLinkBase('(ssh)', 'ssh://%s' % (oEntry.ipLom,), fBracketed = False) ];
259 aoLom += [ WuiRawHtml('<br>'), '%s' % (oEntry.ipLom,) ];
260
261 # State and Last seen.
262 if oEntry.oStatus is None:
263 oSeen = WuiSpanText('tmspan-offline', 'Never');
264 oState = '';
265 else:
266 oDelta = oEntry.tsCurrent - oEntry.oStatus.tsUpdated;
267 if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive:
268 oSeen = WuiSpanText('tmspan-online', u'%s\u00a0s\u00a0ago' % (oDelta.days * 24 * 3600 + oDelta.seconds,));
269 else:
270 oSeen = WuiSpanText('tmspan-offline', u'%s' % (self.formatTsShort(oEntry.oStatus.tsUpdated),));
271
272 if oEntry.oStatus.idTestSet is None:
273 oState = str(oEntry.oStatus.enmState);
274 else:
275 from testmanager.webui.wuimain import WuiMain;
276 oState = WuiTmLink(oEntry.oStatus.enmState, WuiMain.ksScriptName, # pylint: disable=R0204
277 { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails,
278 TestSetData.ksParam_idTestSet: oEntry.oStatus.idTestSet, },
279 sTitle = '#%u' % (oEntry.oStatus.idTestSet,),
280 fBracketed = False);
281 # Comment
282 oComment = self._formatCommentCell(oEntry.sComment);
283
284 # Group links.
285 aoGroups = [];
286 for oInGroup in oEntry.aoInSchedGroups:
287 oSchedGroup = oInGroup.oSchedGroup;
288 aoGroups.append(WuiTmLink(oSchedGroup.sName, WuiAdmin.ksScriptName,
289 { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupEdit,
290 SchedGroupData.ksParam_idSchedGroup: oSchedGroup.idSchedGroup, },
291 sTitle = '#%u' % (oSchedGroup.idSchedGroup,),
292 fBracketed = len(oEntry.aoInSchedGroups) > 1));
293
294 # Reformat the OS version to take less space.
295 aoOs = [ 'N/A' ];
296 if oEntry.sOs is not None and oEntry.sOsVersion is not None and oEntry.sCpuArch:
297 sOsVersion = oEntry.sOsVersion;
298 if sOsVersion[0] not in [ 'v', 'V', 'r', 'R'] \
299 and sOsVersion[0].isdigit() \
300 and sOsVersion.find('.') in range(4) \
301 and oEntry.sOs in [ 'linux', 'solaris', 'darwin', ]:
302 sOsVersion = 'v' + sOsVersion;
303
304 sVer1 = sOsVersion;
305 sVer2 = None;
306 if oEntry.sOs == 'linux' or oEntry.sOs == 'darwin':
307 iSep = sOsVersion.find(' / ');
308 if iSep > 0:
309 sVer1 = sOsVersion[:iSep].strip();
310 sVer2 = sOsVersion[iSep + 3:].strip();
311 sVer2 = sVer2.replace('Red Hat Enterprise Linux Server', 'RHEL');
312 sVer2 = sVer2.replace('Oracle Linux Server', 'OL');
313 elif oEntry.sOs == 'solaris':
314 iSep = sOsVersion.find(' (');
315 if iSep > 0 and sOsVersion[-1] == ')':
316 sVer1 = sOsVersion[:iSep].strip();
317 sVer2 = sOsVersion[iSep + 2:-1].strip();
318 aoOs = [
319 WuiSpanText('tmspan-osarch', u'%s.%s' % (oEntry.sOs, oEntry.sCpuArch,)),
320 WuiSpanText('tmspan-osver1', sVer1.replace('-', u'\u2011'),),
321 ];
322 if sVer2 is not None:
323 aoOs += [ WuiRawHtml('<br>'), WuiSpanText('tmspan-osver2', sVer2.replace('-', u'\u2011')), ];
324
325 # Format the CPU revision.
326 oCpu = None;
327 if oEntry.lCpuRevision is not None and oEntry.sCpuVendor is not None and oEntry.sCpuName is not None:
328 oCpu = [
329 u'%s (fam:%xh\u00a0m:%xh\u00a0s:%xh)'
330 % (oEntry.sCpuVendor, oEntry.getCpuFamily(), oEntry.getCpuModel(), oEntry.getCpuStepping(),),
331 WuiRawHtml('<br>'),
332 oEntry.sCpuName,
333 ];
334 else:
335 oCpu = [];
336 if oEntry.sCpuVendor is not None:
337 oCpu.append(oEntry.sCpuVendor);
338 if oEntry.lCpuRevision is not None:
339 oCpu.append('%#x' % (oEntry.lCpuRevision,));
340 if oEntry.sCpuName is not None:
341 oCpu.append(oEntry.sCpuName);
342
343 # Stuff cpu vendor and cpu/box features into one field.
344 asFeatures = []
345 if oEntry.fCpuHwVirt is True: asFeatures.append(u'HW\u2011Virt');
346 if oEntry.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging');
347 if oEntry.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest');
348 if oEntry.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU');
349 sFeatures = u' '.join(asFeatures) if len(asFeatures) > 0 else u'';
350
351 # Collection applicable actions.
352 aoActions = [
353 WuiTmLink('Details', WuiAdmin.ksScriptName,
354 { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails,
355 TestBoxData.ksParam_idTestBox: oEntry.idTestBox,
356 WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ),
357 ]
358
359 if isDbTimestampInfinity(oEntry.tsExpire):
360 aoActions += [
361 WuiTmLink('Edit', WuiAdmin.ksScriptName,
362 { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxEdit,
363 TestBoxData.ksParam_idTestBox: oEntry.idTestBox, } ),
364 WuiTmLink('Remove', WuiAdmin.ksScriptName,
365 { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxRemovePost,
366 TestBoxData.ksParam_idTestBox: oEntry.idTestBox },
367 sConfirm = 'Are you sure that you want to remove %s (%s)?' % (oEntry.sName, oEntry.ip) ),
368 ]
369
370 if oEntry.sOs not in [ 'win', 'os2', ] and oEntry.ip is not None:
371 aoActions.append(WuiLinkBase('ssh', 'ssh://vbox@%s' % (oEntry.ip,),));
372
373 return [ self._getCheckBoxColumn(iEntry, oEntry.idTestBox),
374 [ WuiSpanText('tmspan-name', oEntry.sName), WuiRawHtml('<br>'), '%s' % (oEntry.ip,),],
375 aoLom,
376 [
377 '' if oEntry.fEnabled else 'disabled / ',
378 oState,
379 WuiRawHtml('<br>'),
380 oSeen,
381 ],
382 oEntry.enmPendingCmd,
383 oComment,
384 WuiSvnLink(oEntry.iTestBoxScriptRev),
385 oEntry.formatPythonVersion(),
386 aoGroups,
387 aoOs,
388 oCpu,
389 sFeatures,
390 oEntry.cCpus if oEntry.cCpus is not None else 'N/A',
391 utils.formatNumberNbsp(oEntry.cMbMemory) + u'\u00a0MB' if oEntry.cMbMemory is not None else 'N/A',
392 utils.formatNumberNbsp(oEntry.cMbScratch) + u'\u00a0MB' if oEntry.cMbScratch is not None else 'N/A',
393 aoActions,
394 ];
395
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