diff options
Diffstat (limited to 'src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py')
-rwxr-xr-x | src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py new file mode 100755 index 00000000..949c1e2a --- /dev/null +++ b/src/VBox/ValidationKit/testmanager/webui/wuiadmintestbox.py @@ -0,0 +1,401 @@ +# -*- coding: utf-8 -*- +# $Id: wuiadmintestbox.py $ + +""" +Test Manager WUI - TestBox. +""" + +__copyright__ = \ +""" +Copyright (C) 2012-2019 Oracle Corporation + +This file is part of VirtualBox Open Source Edition (OSE), as +available from http://www.virtualbox.org. This file is free software; +you can redistribute it and/or modify it under the terms of the GNU +General Public License (GPL) as published by the Free Software +Foundation, in version 2 as it comes in the "COPYING" file of the +VirtualBox OSE distribution. VirtualBox OSE is distributed in the +hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + +The contents of this file may alternatively be used under the terms +of the Common Development and Distribution License Version 1.0 +(CDDL) only, as it comes in the "COPYING.CDDL" file of the +VirtualBox OSE distribution, in which case the provisions of the +CDDL are applicable instead of those of the GPL. + +You may elect to license modified versions of this file under the +terms and conditions of either the GPL or the CDDL or both. +""" +__version__ = "$Revision: 127855 $" + + +# Standard python imports. +import socket; + +# Validation Kit imports. +from common import utils, webutils; +from testmanager.webui.wuicontentbase import WuiContentBase, WuiListContentWithActionBase, WuiFormContentBase, WuiLinkBase, \ + WuiSvnLink, WuiTmLink, WuiSpanText, WuiRawHtml; +from testmanager.core.db import TMDatabaseConnection; +from testmanager.core.schedgroup import SchedGroupLogic, SchedGroupData; +from testmanager.core.testbox import TestBoxData, TestBoxDataEx, TestBoxLogic; +from testmanager.core.testset import TestSetData; +from testmanager.core.db import isDbTimestampInfinity; + + + +class WuiTestBoxDetailsLink(WuiTmLink): + """ Test box details link by ID. """ + + def __init__(self, idTestBox, sName = WuiContentBase.ksShortDetailsLink, fBracketed = False, tsNow = None): + from testmanager.webui.wuiadmin import WuiAdmin; + dParams = { + WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idTestBox: idTestBox, + }; + if tsNow is not None: + dParams[WuiAdmin.ksParamEffectiveDate] = tsNow; ## ?? + WuiTmLink.__init__(self, sName, WuiAdmin.ksScriptName, dParams, fBracketed = fBracketed); + self.idTestBox = idTestBox; + + + +class WuiTestBox(WuiFormContentBase): + """ + WUI TestBox Form Content Generator. + """ + + def __init__(self, oData, sMode, oDisp): + if sMode == WuiFormContentBase.ksMode_Add: + sTitle = 'Create TextBox'; + if oData.uuidSystem is not None and len(oData.uuidSystem) > 10: + sTitle += ' - ' + oData.uuidSystem; + elif sMode == WuiFormContentBase.ksMode_Edit: + sTitle = 'Edit TestBox - %s (#%s)' % (oData.sName, oData.idTestBox); + else: + assert sMode == WuiFormContentBase.ksMode_Show; + sTitle = 'TestBox - %s (#%s)' % (oData.sName, oData.idTestBox); + WuiFormContentBase.__init__(self, oData, sMode, 'TestBox', oDisp, sTitle); + + # Try enter sName as hostname (no domain) when creating the testbox. + if sMode == WuiFormContentBase.ksMode_Add \ + and self._oData.sName in [None, ''] \ + and self._oData.ip not in [None, '']: + try: + (self._oData.sName, _, _) = socket.gethostbyaddr(self._oData.ip); + except: + pass; + offDot = self._oData.sName.find('.'); + if offDot > 0: + self._oData.sName = self._oData.sName[:offDot]; + + + def _populateForm(self, oForm, oData): + oForm.addIntRO( TestBoxData.ksParam_idTestBox, oData.idTestBox, 'TestBox ID'); + oForm.addIntRO( TestBoxData.ksParam_idGenTestBox, oData.idGenTestBox, 'TestBox generation ID'); + oForm.addTimestampRO(TestBoxData.ksParam_tsEffective, oData.tsEffective, 'Last changed'); + oForm.addTimestampRO(TestBoxData.ksParam_tsExpire, oData.tsExpire, 'Expires (excl)'); + oForm.addIntRO( TestBoxData.ksParam_uidAuthor, oData.uidAuthor, 'Changed by UID'); + + oForm.addText( TestBoxData.ksParam_ip, oData.ip, 'TestBox IP Address'); ## make read only?? + oForm.addUuid( TestBoxData.ksParam_uuidSystem, oData.uuidSystem, 'TestBox System/Firmware UUID'); + oForm.addText( TestBoxData.ksParam_sName, oData.sName, 'TestBox Name'); + oForm.addText( TestBoxData.ksParam_sDescription, oData.sDescription, 'TestBox Description'); + oForm.addCheckBox( TestBoxData.ksParam_fEnabled, oData.fEnabled, 'Enabled'); + oForm.addComboBox( TestBoxData.ksParam_enmLomKind, oData.enmLomKind, 'Lights-out-management', + TestBoxData.kaoLomKindDescs); + oForm.addText( TestBoxData.ksParam_ipLom, oData.ipLom, 'Lights-out-management IP Address'); + oForm.addInt( TestBoxData.ksParam_pctScaleTimeout, oData.pctScaleTimeout, 'Timeout scale factor (%)'); + + oForm.addListOfSchedGroupsForTestBox(TestBoxDataEx.ksParam_aoInSchedGroups, + oData.aoInSchedGroups, + SchedGroupLogic(TMDatabaseConnection()).fetchOrderedByName(), + 'Scheduling Group'); + # Command, comment and submit button. + if self._sMode == WuiFormContentBase.ksMode_Edit: + oForm.addComboBox(TestBoxData.ksParam_enmPendingCmd, oData.enmPendingCmd, 'Pending command', + TestBoxData.kaoTestBoxCmdDescs); + else: + oForm.addComboBoxRO(TestBoxData.ksParam_enmPendingCmd, oData.enmPendingCmd, 'Pending command', + TestBoxData.kaoTestBoxCmdDescs); + oForm.addMultilineText(TestBoxData.ksParam_sComment, oData.sComment, 'Comment'); + if self._sMode != WuiFormContentBase.ksMode_Show: + oForm.addSubmit('Create TestBox' if self._sMode == WuiFormContentBase.ksMode_Add else 'Change TestBox'); + + return True; + + + def _generatePostFormContent(self, oData): + from testmanager.webui.wuihlpform import WuiHlpForm; + + oForm = WuiHlpForm('testbox-machine-settable', '', fReadOnly = True); + oForm.addTextRO( TestBoxData.ksParam_sOs, oData.sOs, 'TestBox OS'); + oForm.addTextRO( TestBoxData.ksParam_sOsVersion, oData.sOsVersion, 'TestBox OS version'); + oForm.addTextRO( TestBoxData.ksParam_sCpuArch, oData.sCpuArch, 'TestBox OS kernel architecture'); + oForm.addTextRO( TestBoxData.ksParam_sCpuVendor, oData.sCpuVendor, 'TestBox CPU vendor'); + oForm.addTextRO( TestBoxData.ksParam_sCpuName, oData.sCpuName, 'TestBox CPU name'); + if oData.lCpuRevision: + oForm.addTextRO( TestBoxData.ksParam_lCpuRevision, '%#x' % (oData.lCpuRevision,), 'TestBox CPU revision', + sPostHtml = ' (family=%#x model=%#x stepping=%#x)' + % (oData.getCpuFamily(), oData.getCpuModel(), oData.getCpuStepping(),), + sSubClass = 'long'); + else: + oForm.addLongRO( TestBoxData.ksParam_lCpuRevision, oData.lCpuRevision, 'TestBox CPU revision'); + oForm.addIntRO( TestBoxData.ksParam_cCpus, oData.cCpus, 'Number of CPUs, cores and threads'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fCpuHwVirt, oData.fCpuHwVirt, 'VT-x or AMD-V supported'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fCpuNestedPaging, oData.fCpuNestedPaging, 'Nested paging supported'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fCpu64BitGuest, oData.fCpu64BitGuest, '64-bit guest supported'); + oForm.addCheckBoxRO( TestBoxData.ksParam_fChipsetIoMmu, oData.fChipsetIoMmu, 'I/O MMU supported'); + oForm.addMultilineTextRO(TestBoxData.ksParam_sReport, oData.sReport, 'Hardware/software report'); + oForm.addLongRO( TestBoxData.ksParam_cMbMemory, oData.cMbMemory, 'Installed RAM size (MB)'); + oForm.addLongRO( TestBoxData.ksParam_cMbScratch, oData.cMbScratch, 'Available scratch space (MB)'); + oForm.addIntRO( TestBoxData.ksParam_iTestBoxScriptRev, oData.iTestBoxScriptRev, + 'TestBox Script SVN revision'); + sHexVer = oData.formatPythonVersion(); + oForm.addIntRO( TestBoxData.ksParam_iPythonHexVersion, oData.iPythonHexVersion, + 'Python version (hex)', sPostHtml = webutils.escapeElem(sHexVer)); + return [('Machine Only Settables', oForm.finalize()),]; + + + +class WuiTestBoxList(WuiListContentWithActionBase): + """ + WUI TestBox List Content Generator. + """ + + ## Descriptors for the combo box. + kasTestBoxActionDescs = \ + [ \ + [ 'none', 'Select an action...', '' ], + [ 'enable', 'Enable', '' ], + [ 'disable', 'Disable', '' ], + TestBoxData.kaoTestBoxCmdDescs[1], + TestBoxData.kaoTestBoxCmdDescs[2], + TestBoxData.kaoTestBoxCmdDescs[3], + TestBoxData.kaoTestBoxCmdDescs[4], + TestBoxData.kaoTestBoxCmdDescs[5], + ]; + + ## Boxes which doesn't report in for more than 15 min are considered dead. + kcSecMaxStatusDeltaAlive = 15*60 + + def __init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, fnDPrint, oDisp, aiSelectedSortColumns = None): + # type: (list[TestBoxDataForListing], int, int, datetime.datetime, ignore, WuiAdmin) -> None + WuiListContentWithActionBase.__init__(self, aoEntries, iPage, cItemsPerPage, tsEffective, + sTitle = 'TestBoxes', sId = 'users', fnDPrint = fnDPrint, oDisp = oDisp, + aiSelectedSortColumns = aiSelectedSortColumns); + self._asColumnHeaders.extend([ 'Name', 'LOM', 'Status', 'Cmd', + 'Note', 'Script', 'Python', 'Group', + 'OS', 'CPU', 'Features', 'CPUs', 'RAM', 'Scratch', + 'Actions' ]); + self._asColumnAttribs.extend([ 'align="center"', 'align="center"', 'align="center"', 'align="center"' + 'align="center"', 'align="center"', 'align="center"', 'align="center"', + '', '', '', 'align="left"', 'align="right"', 'align="right"', 'align="right"', + 'align="center"' ]); + self._aaiColumnSorting.extend([ + (TestBoxLogic.kiSortColumn_sName,), + None, # LOM + (-TestBoxLogic.kiSortColumn_fEnabled, TestBoxLogic.kiSortColumn_enmState, -TestBoxLogic.kiSortColumn_tsUpdated,), + (TestBoxLogic.kiSortColumn_enmPendingCmd,), + None, # Note + (TestBoxLogic.kiSortColumn_iTestBoxScriptRev,), + (TestBoxLogic.kiSortColumn_iPythonHexVersion,), + None, # Group + (TestBoxLogic.kiSortColumn_sOs, TestBoxLogic.kiSortColumn_sOsVersion, TestBoxLogic.kiSortColumn_sCpuArch,), + (TestBoxLogic.kiSortColumn_sCpuVendor, TestBoxLogic.kiSortColumn_lCpuRevision,), + (TestBoxLogic.kiSortColumn_fCpuNestedPaging,), + (TestBoxLogic.kiSortColumn_cCpus,), + (TestBoxLogic.kiSortColumn_cMbMemory,), + (TestBoxLogic.kiSortColumn_cMbScratch,), + None, # Actions + ]); + assert len(self._aaiColumnSorting) == len(self._asColumnHeaders); + self._aoActions = list(self.kasTestBoxActionDescs); + self._sAction = oDisp.ksActionTestBoxListPost; + self._sCheckboxName = TestBoxData.ksParam_idTestBox; + + def show(self, fShowNavigation = True): + """ Adds some stats at the bottom of the page """ + (sTitle, sBody) = super(WuiTestBoxList, self).show(fShowNavigation); + + # Count boxes in interesting states. + if self._aoEntries: + cActive = 0; + cDead = 0; + for oTestBox in self._aoEntries: + if oTestBox.oStatus is not None: + oDelta = oTestBox.tsCurrent - oTestBox.oStatus.tsUpdated; + if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive: + if oTestBox.fEnabled: + cActive += 1; + else: + cDead += 1; + else: + cDead += 1; + sBody += '<div id="testboxsummary"><p>\n' \ + '%s testboxes of which %s are active and %s dead' \ + '</p></div>\n' \ + % (len(self._aoEntries), cActive, cDead,) + return (sTitle, sBody); + + def _formatListEntry(self, iEntry): # pylint: disable=R0914 + from testmanager.webui.wuiadmin import WuiAdmin; + oEntry = self._aoEntries[iEntry]; + + # Lights outs managment. + if oEntry.enmLomKind == TestBoxData.ksLomKind_ILOM: + aoLom = [ WuiLinkBase('ILOM', 'https://%s/' % (oEntry.ipLom,), fBracketed = False), ]; + elif oEntry.enmLomKind == TestBoxData.ksLomKind_ELOM: + aoLom = [ WuiLinkBase('ELOM', 'http://%s/' % (oEntry.ipLom,), fBracketed = False), ]; + elif oEntry.enmLomKind == TestBoxData.ksLomKind_AppleXserveLom: + aoLom = [ 'Apple LOM' ]; + elif oEntry.enmLomKind == TestBoxData.ksLomKind_None: + aoLom = [ 'none' ]; + else: + aoLom = [ 'Unexpected enmLomKind value "%s"' % (oEntry.enmLomKind,) ]; + if oEntry.ipLom is not None: + if oEntry.enmLomKind in [ TestBoxData.ksLomKind_ILOM, TestBoxData.ksLomKind_ELOM ]: + aoLom += [ WuiLinkBase('(ssh)', 'ssh://%s' % (oEntry.ipLom,), fBracketed = False) ]; + aoLom += [ WuiRawHtml('<br>'), '%s' % (oEntry.ipLom,) ]; + + # State and Last seen. + if oEntry.oStatus is None: + oSeen = WuiSpanText('tmspan-offline', 'Never'); + oState = ''; + else: + oDelta = oEntry.tsCurrent - oEntry.oStatus.tsUpdated; + if oDelta.days <= 0 and oDelta.seconds <= self.kcSecMaxStatusDeltaAlive: + oSeen = WuiSpanText('tmspan-online', u'%s\u00a0s\u00a0ago' % (oDelta.days * 24 * 3600 + oDelta.seconds,)); + else: + oSeen = WuiSpanText('tmspan-offline', u'%s' % (self.formatTsShort(oEntry.oStatus.tsUpdated),)); + + if oEntry.oStatus.idTestSet is None: + oState = str(oEntry.oStatus.enmState); + else: + from testmanager.webui.wuimain import WuiMain; + oState = WuiTmLink(oEntry.oStatus.enmState, WuiMain.ksScriptName, # pylint: disable=R0204 + { WuiMain.ksParamAction: WuiMain.ksActionTestResultDetails, + TestSetData.ksParam_idTestSet: oEntry.oStatus.idTestSet, }, + sTitle = '#%u' % (oEntry.oStatus.idTestSet,), + fBracketed = False); + # Comment + oComment = self._formatCommentCell(oEntry.sComment); + + # Group links. + aoGroups = []; + for oInGroup in oEntry.aoInSchedGroups: + oSchedGroup = oInGroup.oSchedGroup; + aoGroups.append(WuiTmLink(oSchedGroup.sName, WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionSchedGroupEdit, + SchedGroupData.ksParam_idSchedGroup: oSchedGroup.idSchedGroup, }, + sTitle = '#%u' % (oSchedGroup.idSchedGroup,), + fBracketed = len(oEntry.aoInSchedGroups) > 1)); + + # Reformat the OS version to take less space. + aoOs = [ 'N/A' ]; + if oEntry.sOs is not None and oEntry.sOsVersion is not None and oEntry.sCpuArch: + sOsVersion = oEntry.sOsVersion; + if sOsVersion[0] not in [ 'v', 'V', 'r', 'R'] \ + and sOsVersion[0].isdigit() \ + and sOsVersion.find('.') in range(4) \ + and oEntry.sOs in [ 'linux', 'solaris', 'darwin', ]: + sOsVersion = 'v' + sOsVersion; + + sVer1 = sOsVersion; + sVer2 = None; + if oEntry.sOs == 'linux' or oEntry.sOs == 'darwin': + iSep = sOsVersion.find(' / '); + if iSep > 0: + sVer1 = sOsVersion[:iSep].strip(); + sVer2 = sOsVersion[iSep + 3:].strip(); + sVer2 = sVer2.replace('Red Hat Enterprise Linux Server', 'RHEL'); + sVer2 = sVer2.replace('Oracle Linux Server', 'OL'); + elif oEntry.sOs == 'solaris': + iSep = sOsVersion.find(' ('); + if iSep > 0 and sOsVersion[-1] == ')': + sVer1 = sOsVersion[:iSep].strip(); + sVer2 = sOsVersion[iSep + 2:-1].strip(); + elif oEntry.sOs == 'win': + iSep = sOsVersion.find('build'); + if iSep > 0: + sVer1 = sOsVersion[:iSep].strip(); + sVer2 = 'B' + sOsVersion[iSep + 1:].strip(); + aoOs = [ + WuiSpanText('tmspan-osarch', u'%s.%s' % (oEntry.sOs, oEntry.sCpuArch,)), + WuiSpanText('tmspan-osver1', sVer1.replace('-', u'\u2011'),), + ]; + if sVer2 is not None: + aoOs += [ WuiRawHtml('<br>'), WuiSpanText('tmspan-osver2', sVer2.replace('-', u'\u2011')), ]; + + # Format the CPU revision. + oCpu = None; + if oEntry.lCpuRevision is not None and oEntry.sCpuVendor is not None and oEntry.sCpuName is not None: + oCpu = [ + u'%s (fam:%xh\u00a0m:%xh\u00a0s:%xh)' + % (oEntry.sCpuVendor, oEntry.getCpuFamily(), oEntry.getCpuModel(), oEntry.getCpuStepping(),), + WuiRawHtml('<br>'), + oEntry.sCpuName, + ]; + else: + oCpu = []; + if oEntry.sCpuVendor is not None: + oCpu.append(oEntry.sCpuVendor); + if oEntry.lCpuRevision is not None: + oCpu.append('%#x' % (oEntry.lCpuRevision,)); + if oEntry.sCpuName is not None: + oCpu.append(oEntry.sCpuName); + + # Stuff cpu vendor and cpu/box features into one field. + asFeatures = [] + if oEntry.fCpuHwVirt is True: asFeatures.append(u'HW\u2011Virt'); + if oEntry.fCpuNestedPaging is True: asFeatures.append(u'Nested\u2011Paging'); + if oEntry.fCpu64BitGuest is True: asFeatures.append(u'64\u2011bit\u2011Guest'); + if oEntry.fChipsetIoMmu is True: asFeatures.append(u'I/O\u2011MMU'); + sFeatures = u' '.join(asFeatures) if asFeatures else u''; + + # Collection applicable actions. + aoActions = [ + WuiTmLink('Details', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxDetails, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox, + WuiAdmin.ksParamEffectiveDate: self._tsEffectiveDate, } ), + ] + + if self._oDisp is None or not self._oDisp.isReadOnlyUser(): + if isDbTimestampInfinity(oEntry.tsExpire): + aoActions += [ + WuiTmLink('Edit', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxEdit, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox, } ), + WuiTmLink('Remove', WuiAdmin.ksScriptName, + { WuiAdmin.ksParamAction: WuiAdmin.ksActionTestBoxRemovePost, + TestBoxData.ksParam_idTestBox: oEntry.idTestBox }, + sConfirm = 'Are you sure that you want to remove %s (%s)?' % (oEntry.sName, oEntry.ip) ), + ] + + if oEntry.sOs not in [ 'win', 'os2', ] and oEntry.ip is not None: + aoActions.append(WuiLinkBase('ssh', 'ssh://vbox@%s' % (oEntry.ip,),)); + + return [ self._getCheckBoxColumn(iEntry, oEntry.idTestBox), + [ WuiSpanText('tmspan-name', oEntry.sName), WuiRawHtml('<br>'), '%s' % (oEntry.ip,),], + aoLom, + [ + '' if oEntry.fEnabled else 'disabled / ', + oState, + WuiRawHtml('<br>'), + oSeen, + ], + oEntry.enmPendingCmd, + oComment, + WuiSvnLink(oEntry.iTestBoxScriptRev), + oEntry.formatPythonVersion(), + aoGroups, + aoOs, + oCpu, + sFeatures, + oEntry.cCpus if oEntry.cCpus is not None else 'N/A', + utils.formatNumberNbsp(oEntry.cMbMemory) + u'\u00a0MB' if oEntry.cMbMemory is not None else 'N/A', + utils.formatNumberNbsp(oEntry.cMbScratch) + u'\u00a0MB' if oEntry.cMbScratch is not None else 'N/A', + aoActions, + ]; + |