""" CCP4JobControlGui.py: CCP4 GUI Project Copyright (C) 2016STFC This library is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 3, modified in accordance with the provisions of the license to address the requirements of UK law.sstac You should have received a copy of the modified GNU Lesser General Public License along with this library. If not, copies may be downloaded from http://www.ccp4.ac.uk/ccp4license.php This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. """ """ Liz Potterton april 2016 - Gui for remote running """ import os import glob import time import functools from PyQt4 import QtGui,QtCore from core.CCP4ErrorHandling import * from core import CCP4Modules from core import CCP4Annotation from qtgui import CCP4Widgets from core import CCP4JobServer class CServerParamsDialog(QtGui.QDialog): insts = None guiDef = { 'machine' : [ 'Server' , 'machine', 'Choose a server'], 'username': [ 'Username on server' , 'Your username on server'], 'password' : [ 'Password on server','Your password on server' ], 'keyFilename' : [ 'SSH key filename', 'Local file containing SSH public keys' ], 'ccp4Dir' : [ 'CCP4 directory on server', 'Full path of CCP4 on the server machine' ], 'tempDir' : [ 'Work directory on server', 'Full path of temporary work directory server machine' ], 'sge_root' : [ 'SGE_ROOT (Sun gridengine dir)', 'Full path of Sun grid engine root directory server machine' ], 'queueOptionsFile' : [ 'Queue option file', 'File containing options for the job queue' ], 'timeout' : [ 'Timeout in seconds', 'Wait for connection' ] } def userGuiOptions(self,mechanism,validate=None,customCodeFile=None): if mechanism in ['ssh','ssh_shared','qsub_remote','qsub_shared']: if validate is None or validate == 'password': ret = ['username','password','machine'] elif validate == 'key_filename': ret = ['username','keyFilename','machine'] elif validate == 'pass_key_filename': ret = ['username','keyFilename','password','machine'] if mechanism in ['ssh','qsub_remote']: ret.append('tempDir') #if mechanism in ['qsub_shared','qsub_remote']: ret.append('sge_root') elif mechanism == 'qsub_local': ret = [] elif mechanism == 'custom': if customCodeFile is not None: return CCP4Modules.JOBCONTROLLER().customHandler(customCodeFile=customCodeFile).SHOW_USER_PARAMS else: return ['username','password'] if mechanism in ['qsub_local','qsub_remote','qsub_shared']: ret.append('queueOptionsFile') return ret def getParams(self): p = CCP4JobServer.CServerParams(machine = self.get('machine'), username = self.get('username'), password = self.get('password'), keyFilename = self.get('keyFilename'), mechanism = self.get('mechanism'), validate= self.get('validate'), ccp4Dir = self.get('ccp4Dir'), tempDir = self.get('tempDir'), sge_root = self.get('sge_root'), customCodeFile = self.get('customCodeFile'), queueOptionsFile = self.get('queueOptionsFile'), serverGroup=self.get('serverGroup')) print 'CServerParamsDialog.getParams',p return p def get(self,param): if not self.widgets.has_key(param): serverGroup = self.container.get('SERVERGROUP'+str(self.modeGroup.checkedId())) if param == 'serverGroup': return str(serverGroup.name) elif param == 'machine': return str(getattr(serverGroup,'serverList')[0]) elif param == 'timeout': if serverGroup.timeout.isSet(): return float(serverGroup.timeout) else: return None elif param not in ['username','password']: return str(getattr(serverGroup,param)) else: return '' if param in ['queueOptionsFile']: self.widgets['queueOptionsFile'].updateModelFromView() return str(self.queueOptionsFile) else: if isinstance(self.widgets[param],QtGui.QLineEdit): if param == 'timeout': if len(self.widgets[param].text()) == 0: return None else: return float(self.widgets[param].text()) return str(self.widgets[param].text()) else: return str(self.widgets[param].currentText()) def setInfo(self,info=None): if info is None: self.widgets['info'].setText('') else: self.widgets['info'].setText(info) def valid(self): return True def __init__(self,parent=None,params={}): QtGui.QDialog.__init__(self,parent) try: self.loadConfig() except: print 'Failed loading server config file from CCP4I2/local_setup/servers_config.params.xml' self.setWindowTitle('Choose server to run job') layout = QtGui.QVBoxLayout() self.setLayout(layout) self.setMinimumWidth(500) frame = QtGui.QFrame(self) frame.setLayout(QtGui.QVBoxLayout()) frame.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Plain) MARGIN = 6 frame.layout().setSpacing(MARGIN) frame.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN) frame.layout().addWidget(CCP4Widgets.CItalicLabel('Choose group of servers to use..',self)) self.layout().addWidget(frame) self.modeGroup = QtGui.QButtonGroup(self) default = 0 row = 0 for idx in range(1,10): group = self.container.get('SERVERGROUP'+str(idx)) #if group is not None: print 'SERVERGROUP',idx,group, group.isSet(), len(group.serverList) if group is not None and len(group.serverList)>0: but = QtGui.QRadioButton(str(group.name),self) self.modeGroup.addButton(but,idx) frame.layout().addWidget(but) row = row + 1 if default==0: default = idx #print 'default modeGroup',default try: self.modeGroup.button(default).setChecked(True) except: print 'Failed to set modeGroup',default self.customFrame = QtGui.QFrame(self) self.customFrame.setLayout(QtGui.QVBoxLayout()) self.layout().addWidget(self.customFrame) butBox = QtGui.QDialogButtonBox(self) but = butBox.addButton("Submit job",QtGui.QDialogButtonBox.AcceptRole) but.clicked.connect(self.accept) but = butBox.addButton("Help",QtGui.QDialogButtonBox.HelpRole) but.clicked.connect(self.help) but = butBox.addButton("Cancel",QtGui.QDialogButtonBox.RejectRole) but.clicked.connect(self.reject) self.layout().addWidget(butBox) self.connect(self.modeGroup,QtCore.SIGNAL('buttonReleased(int)'),self.loadServers) self.loadServers() def drawDetails(self,serverGroup,params={}): self.widgets = { } self.labels = { } #params['ccp4Dir'] = params.get('ccp4Dir',CCP4Utils.getCCP4Dir()) #params['username'] = params.get('username',CCP4Utils.getUserId()) self.detailsFrame = QtGui.QFrame(self) layout = QtGui.QGridLayout() self.detailsFrame.setLayout(layout) self.detailsFrame.setFrameStyle(QtGui.QFrame.Box | QtGui.QFrame.Plain) layout.addWidget(CCP4Widgets.CItalicLabel('Details',self),0,0,1,2) row = 1 #for param in ['machine', 'ccp4Dir', 'username', 'password','keyFilename','queueOptionsFile']: #for param in CServerParamsDialog.guiDef.keys(): for param in self.userGuiOptions(mechanism=str(serverGroup.mechanism),validate=str(serverGroup.validate), customCodeFile=str(serverGroup.customCodeFile)): self.labels[param] = QtGui.QLabel(CServerParamsDialog.guiDef[param][0],self) layout.addWidget(self.labels[param] ,row,0) if param == 'machine': self.widgets[param] = QtGui.QComboBox(self) elif param == 'queueOptionsFile': from core import CCP4File self.queueOptionsFile = CCP4File.CDataFile(parent=self) self.widgets[param] = CCP4Widgets.CDataFileView(self,model=self.queueOptionsFile) else: self.widgets[param] = QtGui.QLineEdit(self) self.widgets[param].setText(params.get(param,'')) self.widgets[param].setToolTip(CServerParamsDialog.guiDef[param][1]) if param == 'password' : self.widgets[param].setEchoMode(QtGui.QLineEdit.Password) layout.addWidget(self.widgets[param],row,1) row = row + 1 if serverGroup.mechanism == 'custom': for i in range(self.customFrame.layout().count()): try: w = self.customFrame.layout().takeAt(0).widget() w.hide() w.deleteLater() except: pass guiFunction = getattr(CCP4Modules.JOBCONTROLLER().customHandler(customCodeFile=serverGroup.customCodeFile),'guiFrame',None) if guiFunction is not None and callable(guiFunction): try: guiFunction(parentFrame=self.customFrame) except Exception as e: print 'Failed drawing custom widgets',str(e) self.customFrame.show() else: self.customFrame.hide() def loadServers(self,mode=None): serverGroup = self.container.get('SERVERGROUP'+str(self.modeGroup.checkedId())) if serverGroup is None: warningMess = QtGui.QMessageBox.warning(self,'Run on server','''To run on server you must first configure servers. See under Utilities -> System administrator tools''') self.hide() return if hasattr(self,'detailsFrame'): idx = self.layout().indexOf(self.detailsFrame) self.layout().takeAt(idx) self.detailsFrame.hide() self.detailsFrame.deleteLater() else: idx = 1 self.drawDetails(serverGroup) self.layout().insertWidget(idx,self.detailsFrame) #print 'loadServers',self.modeGroup.checkedId(),serverGroup,len(serverGroup.serverList) if self.widgets.has_key('machine'): self.widgets['machine'].setEditable(bool(serverGroup.userExtensible)) self.widgets['machine'].clear() for item in serverGroup.serverList: self.widgets['machine'].addItem(str(item)) for key in ['ccp4Dir','keyFilename','tempDir']: if self.widgets.has_key(key): if getattr(serverGroup,key).isSet(): self.widgets[key].setText(str(getattr(serverGroup,key))) else: self.widgets[key].setText('') if self.widgets.has_key('password'): if serverGroup.validate is None or str(serverGroup.validate) == 'password': self.labels['password'].setText('Your password on server') self.widgets['password'].setToolTip('Your password on server') elif str(serverGroup.validate) == 'key_filename': pass elif str(serverGroup.validate) == 'pass_key_filename': self.labels['password'].setText('Your key file password') self.widgets['password'].setToolTip('Your key file password') def loadConfig(self): self.container = CCP4Modules.SERVERSETUP() def accept(self): QtGui.QDialog.accept(self) def reject(self): QtGui.QDialog.reject(self) def setInfo(self,text): if not self.widgets.has_key('info'): self.widgets['info'] = QtGui.QLabel(self) self.widgets['info'].setObjectName('warning') self.layout().insertWidget(0,self.widgets['info']) if text is None: self.widgets['info'].setText('') else: self.widgets['info'].setText(text) def help(self): CCP4Modules.WEBBROWSER().loadWebPage(helpFileName='servers.html',newTab=True) class CServerGroupView(CCP4Widgets.CComplexLineWidget): MODEL_CLASS = CCP4Annotation.CServerGroup def __init__(self,parent=None,model=None,qualifiers={}): qualis = { 'vboxLayout' : True } qualis.update(qualifiers) CCP4Widgets.CComplexLineWidget.__init__(self,parent=parent,qualifiers=qualis) self.layout().takeAt(0).widget().deleteLater() self.frames = {} self.widgets['name'] = CCP4Widgets.CStringView(self) qualis self.widgets['mechanism'] = CCP4Widgets.CStringView( self,qualifiers=model.mechanism.qualifiers() ) self.widgets['validate'] = CCP4Widgets.CRadioButtonGroup(self) self.widgets['validate'].addRadioButton('password','by password') self.widgets['validate'].addRadioButton('key_filename','by key file') self.widgets['validate'].addRadioButton('pass_key_filename','by passworded key file') self.connect(self.widgets['mechanism'].widget,QtCore.SIGNAL('currentIndexChanged(int)'),self.changeMechanism) self.connect(self.widgets['validate'],QtCore.SIGNAL('buttonReleased(int)'),self.changeValidate) ''' if model is not None: self.widgets['mechanism'].populate(menuItems=model.mechanism.qualifiers('enumerators'),menuText=model.mechanism.qualifiers('menuText')) ''' self.widgets['serverList'] = CCP4Widgets.CListView(self, qualifiers= { 'editorClassName' : 'CStringView' }) self.widgets['userExtensible'] = CCP4Widgets.CBooleanView(self) self.widgets['ccp4Dir'] = CCP4Widgets.CStringView(self,qualifiers={ 'allowUndefined' : True } ) self.widgets['tempDir'] = CCP4Widgets.CStringView(self,qualifiers={ 'allowUndefined' : True } ) self.widgets['sge_root'] = CCP4Widgets.CStringView(self,qualifiers={ 'allowUndefined' : True } ) self.widgets['keyFilename'] = CCP4Widgets.CStringView(self,qualifiers={ 'allowUndefined' : True } ) self.widgets['timeout'] = CCP4Widgets.CFloatView(self,qualifiers={ 'allowUndefined' : True } ) self.widgets['maxTries'] = CCP4Widgets.CIntView(self,qualifiers={ 'allowUndefined' : False } ) self.widgets['customCodeFile'] = CCP4Widgets.CDataFileView(self,qualifiers={ 'allowUndefined' : True , 'jobCombo' : False, 'browseDb': False } ) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Name for server group',self)) line.addWidget(self.widgets['name']) line.addWidget(QtGui.QLabel('using mechanism',self)) line.addWidget(self.widgets['mechanism']) self.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(QtGui.QLabel('Validate by',self)) for item in ['password','key_filename','pass_key_filename']: line.addWidget(self.widgets['validate'].getButton(item)) line.addStretch(2) self.layout().addLayout(line) line = QtGui.QHBoxLayout() self.widgets['serverList'].setLineWidth(1) self.widgets['serverList'].setFrameStyle(QtGui.QFrame.Box|QtGui.QFrame.Plain) line.addWidget(self.widgets['serverList']) self.layout().addLayout(line) self.widgets['serverList'].setListVisible(True) line = QtGui.QHBoxLayout() line.addWidget(self.widgets['userExtensible']) line.addWidget(QtGui.QLabel('Allow users to add alternative servers at run time',self)) line.addStretch(2) self.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(CCP4Widgets.CItalicLabel('CCP4 distro directory on these servers',self)) self.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(self.widgets['ccp4Dir']) self.layout().addLayout(line) frame = self.makeFrame('tempDir') line = QtGui.QHBoxLayout() line.addWidget(CCP4Widgets.CItalicLabel('Temporary directory (eg /tmp/$USER)',self)) frame.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(self.widgets['tempDir']) frame.layout().addLayout(line) frame = self.makeFrame('sge_root') line = QtGui.QHBoxLayout() line.addWidget(CCP4Widgets.CItalicLabel('SGE_ROOT (Sun Grid Engine dir)',self)) frame.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(self.widgets['sge_root']) frame.layout().addLayout(line) frame = self.makeFrame('key_filename') frame.layout().addWidget(CCP4Widgets.CItalicLabel('Local SSH key filename (eg $HOME/.ssh/id_rsa)',self)) frame.layout().addWidget(self.widgets['keyFilename']) line = self.makeFrame('timeout',vertical=False) line.layout().addWidget(CCP4Widgets.CItalicLabel('Timeout (seconds)',self)) line.layout().addWidget(self.widgets['timeout']) line.layout().addWidget(CCP4Widgets.CItalicLabel('Maximum tries',self)) line.layout().addWidget(self.widgets['maxTries']) self.layout().addWidget(line) frame = self.makeFrame('customCodeFile') line = QtGui.QHBoxLayout() line.addWidget(CCP4Widgets.CItalicLabel('Custom server code file',self)) frame.layout().addLayout(line) line = QtGui.QHBoxLayout() line.addWidget(self.widgets['customCodeFile']) frame.layout().addLayout(line) line = QtGui.QHBoxLayout() self.testButton = QtGui.QToolButton(self) self.testButton.setText('Test server group') self.deleteButton = QtGui.QToolButton(self) self.deleteButton.setText('Delete server group') line.addStretch(5) line.addWidget(self.testButton ) line.addWidget(self.deleteButton ) self.layout().addLayout(line) self.setModel(model=model) self.updateViewFromModel() self.widgets['serverList'].updateViewFromModel() self.widgets['serverList'].handleRowChange(0,force=True) self.widgets['mechanism'].updateViewFromModel() self.changeMechanism(0) def makeFrame(self,name,vertical=True): self.frames[name] = QtGui.QFrame() if vertical: self.frames[name].setLayout(QtGui.QVBoxLayout()) else: self.frames[name].setLayout(QtGui.QHBoxLayout()) self.frames[name].layout().setSpacing(1) self.frames[name].layout().setContentsMargins(1,1,1,1) self.layout().addWidget(self.frames[name]) return self.frames[name] def updateViewFromModel(self): # Exclude the serverList which is behaving oddly because # dataChanged signal from serverList object is relayed by # CServerGroup and causes this method to be invoked for item in ['name','userExtensible','ccp4Dir','tempDir','keyFilename','customCodeFile','timeout','maxTries']: self.widgets[item].updateViewFromModel() self.widgets['validate'].setValue(str(self.model.validate)) self.changeValidate(self.model.validate.qualifiers('enumerators').index(str(self.model.validate))) def updateModelFromView(self): for item in ['name','userExtensible','ccp4Dir','tempDir','keyFilename','serverList','customCodeFile','timeout','maxTries']: self.widgets[item].updateModelFromView() self.model.validate.set(self.widgets['validate'].getValue()) #print 'CServerGroupView.updateModelFromView',self.model.validate def changeMechanism(self,indx): mechanism = self.widgets['mechanism'].getWidgetText() #print 'CServerGroupView.changeMechanism',indx,mechanism if mechanism in ['custom']: self.frames['customCodeFile'].show() else: self.frames['customCodeFile'].hide() if mechanism in ['ssh','ssh_shared','qsub_remote','qsub_shared']: self.frames['timeout'].show() else: self.frames['timeout'].hide() if mechanism in ['ssh','qsub_remote','custom']: self.frames['tempDir'].show() else: self.frames['tempDir'].hide() if mechanism in ['qsub_local','qsub_remote','qsub_shared']: self.frames['sge_root'].show() else: self.frames['sge_root'].hide() def changeValidate(self,id): #print 'changeValidate',id self.model.validate.set(self.widgets['validate'].getValue()) if id in [0]: self.frames['key_filename'].hide() else: self.frames['key_filename'].show() class CServerSetupWindow(QtGui.QDialog): ''' This is the glue to get display the CTaskServerSetup as an independent window ''' def __init__(self,parent): QtGui.QDialog.__init__(self,parent) self.setWindowTitle('Configure servers for remote running') #self.setMaximumWidth(650) #self.setMinimumHeight(500) self.setLayout(QtGui.QVBoxLayout()) MARGIN = 2 self.layout().setSpacing(MARGIN) self.layout().setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN) self.widgets= {} self.container = CCP4Modules.SERVERSETUP() self.container.load() line = QtGui.QHBoxLayout() self.sourceLabel = QtGui.QLabel('Server setup loaded from ',self) line.addWidget(self.sourceLabel) self.layout().addLayout(line) self.altSourceBut = QtGui.QPushButton('Load alternate setup',self) line.addWidget(self.altSourceBut) self.connect(self.altSourceBut,QtCore.SIGNAL('clicked()'),self.handleLoadAlt) self.scrollArea= QtGui.QScrollArea(self) self.scrollArea.setMaximumWidth(570) self.scrollArea.setMinimumWidth(570) self.scrollArea.setMinimumHeight(500) self.layout().addWidget(self.scrollArea) self.contentFrame = QtGui.QFrame(self) self.scrollArea.setWidget(self.contentFrame) self.scrollArea.setWidgetResizable(1) self.scrollArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.contentLayout = QtGui.QVBoxLayout() self.contentLayout.setSpacing(MARGIN) self.contentLayout.setContentsMargins(MARGIN,MARGIN,MARGIN,MARGIN) self.contentFrame.setLayout(self.contentLayout) self.makeAllWidgets() line = QtGui.QHBoxLayout() self.layout().addLayout(line) addButton = QtGui.QToolButton(self) addButton.setText('Add another server group') line.addStretch(4) line.addWidget(addButton) self.layout().addLayout(line) self.connect(addButton,QtCore.SIGNAL('clicked()'),self.handleAdd) self.buttons = QtGui.QDialogButtonBox(self) but = self.buttons.addButton(QtGui.QDialogButtonBox.Help) self.connect(but,QtCore.SIGNAL('clicked()'),self.help) if self.container.writeAccess('installation'): but = self.buttons.addButton('Save for CCP4 installation',QtGui.QDialogButtonBox.ApplyRole) self.connect(but,QtCore.SIGNAL('clicked()'),functools.partial(self.doApply,'installation')) but = self.buttons.addButton('Save for me only',QtGui.QDialogButtonBox.ApplyRole) self.connect(but,QtCore.SIGNAL('clicked()'),functools.partial(self.doApply,'user')) but = self.buttons.addButton(QtGui.QDialogButtonBox.Close) self.connect(but,QtCore.SIGNAL('clicked()'),self.close) line = QtGui.QHBoxLayout() line.addStretch(0.2) line.addWidget(self.buttons) line.addStretch(0.2) self.layout().addLayout(line) def makeAllWidgets(self): #print 'makeAllWidgets',self.container,self.container._value,self.getNServerGroups() n = 1 while self.container.get('SERVERGROUP'+str(n)) is not None: self.makeWidget(n) n = n + 1 def clear(self): for key,w in self.widgets.items(): w.hide() w.deleteLater() self.widgets = {} def makeWidget(self,n): self.widgets['SERVERGROUP'+str(n)] = CServerGroupView(self,self.container.get('SERVERGROUP'+str(n))) self.widgets['SERVERGROUP'+str(n)].setMaximumWidth(550) self.widgets['SERVERGROUP'+str(n)].setMinimumWidth(550) self.connect(self.widgets['SERVERGROUP'+str(n)].deleteButton,QtCore.SIGNAL('clicked()'),functools.partial(self.handleDelete,n)) self.connect(self.widgets['SERVERGROUP'+str(n)].testButton,QtCore.SIGNAL('clicked()'),functools.partial(self.handleTest,n)) #self.layout().addWidget(self.widgets['SERVERGROUP'+str(n)]) ''' self.widgets['SERVERGROUP'+str(n)].updateViewFromModel() self.widgets['SERVERGROUP'+str(n)].widgets['serverList'].updateViewFromModel() self.widgets['SERVERGROUP'+str(n)].widgets['serverList'].handleRowChange(0,force=True) ''' self.contentLayout.addWidget(self.widgets['SERVERGROUP'+str(n)]) def show(self): self.setAltSourceButton() QtGui.QDialog.show(self) def setAltSourceButton(self): if self.container.source is not None: self.sourceLabel.setText('Server setup loaded from '+str(self.container.source)) altSource = ['user','installation'][1 - ['user','installation'].index( self.container.source )] if os.path.exists(self.container.preferencesFile(altSource)[0]): self.altSourceBut.setText('Load '+altSource+' setup') self.altSourceBut.setEnabled(True) return self.altSourceBut.setText('Load alternate setup') self.altSourceBut.setEnabled(False) def handleLoadAlt(self): altSource = ['user','installation'][1 - ['user','installation'].index( self.container.source )] #print 'handleAltSource',altSource self.clear() self.container.clear() self.container.load(altSource) self.makeAllWidgets() self.setAltSourceButton() def getNServerGroups(self): n = 0 while self.container.get('SERVERGROUP'+str(n+1)) is not None: n = n + 1 return n def handleAdd(self): n = self.getNServerGroups() + 1 self.container.setContents( { 'SERVERGROUP'+str(n) : { 'class' :CCP4Annotation.CServerGroup }} ) self.container.get('SERVERGROUP'+str(n)).setObjectName('SERVERGROUP'+str(n)) self.makeWidget(n) def handleDelete(self,n): #print 'CServerSetupWindow.handleDelete',n,self.getNServerGroups() nTot = self.getNServerGroups() if nTot==1: return self.widgets['SERVERGROUP'+str(n)].hide() self.widgets['SERVERGROUP'+str(n)].deleteLater() del self.widgets['SERVERGROUP'+str(n)] self.container.get('SERVERGROUP'+str(n)).blockSignals(True) self.container.deleteObject('SERVERGROUP'+str(n)) if n < nTot: for ii in range(n,nTot): self.widgets['SERVERGROUP'+str(ii+1)].setObjectName('SERVERGROUP'+str(ii)) self.widgets['SERVERGROUP'+str(ii)] = self.widgets.pop('SERVERGROUP'+str(ii+1)) self.container.renameObject('SERVERGROUP'+str(ii+1),'SERVERGROUP'+str(ii)) def handleTest(self,indx): if hasattr(self,'testDialog'): self.testDialog.hide() self.testDialog.deleteLater() self.testDialog = QtGui.QDialog(self) self.testDialog.setWindowTitle('Test remote server') self.testDialog.setLayout(QtGui.QVBoxLayout()) self.testDialog.layout().addWidget(QtGui.QLabel('Test existance of CCP4 directory and temporary directory on each machine',self)) line = QtGui.QHBoxLayout() self.testDialog.layout().addLayout(line) self.testDialogUsername = QtGui.QLineEdit(self.testDialog) line.addWidget(QtGui.QLabel('Test with username',self.testDialog)) line.addWidget(self.testDialogUsername) line = QtGui.QHBoxLayout() self.testDialog.layout().addLayout(line) self.testDialogPassword = QtGui.QLineEdit(self.testDialog) self.testDialogPassword.setEchoMode(QtGui.QLineEdit.Password) line.addWidget(QtGui.QLabel('Test with password',self.testDialog)) line.addWidget(self.testDialogPassword) buttons = QtGui.QDialogButtonBox(self.testDialog) but = buttons.addButton(QtGui.QDialogButtonBox.Cancel) self.connect(but,QtCore.SIGNAL('clicked()'),self.testDialog.close) but = buttons.addButton(QtGui.QDialogButtonBox.Apply) self.connect(but,QtCore.SIGNAL('clicked()'),functools.partial(self.showTests,indx)) line = QtGui.QHBoxLayout() line.addStretch(0.2) line.addWidget(buttons) line.addStretch(0.2) self.testDialog.layout().addLayout(line) self.testDialog.show() self.testDialog.raise_() def showTests(self,indx): self.testDialog.close() if hasattr(self,'testMessage'): self.testMessage.hide() self.testMessage.deleteLater() else: self.connect(self,QtCore.SIGNAL('testMessage'),self.updateTestReport) message = '' self.testMessage = QtGui.QDialog(self) self.testMessage.setWindowTitle('Testing remote server') self.testMessage.setLayout(QtGui.QVBoxLayout()) self.testMessage.layout().addWidget(QtGui.QLabel('Test existance of CCP4 directory and temp directory',self.testMessage)) self.testReport = QtGui.QTextEdit(self.testDialog) self.testMessage.layout().addWidget(self.testReport) buttons = QtGui.QDialogButtonBox(self.testDialog) but = buttons.addButton(QtGui.QDialogButtonBox.Cancel) self.testMessage.layout().addWidget(buttons) self.connect(but,QtCore.SIGNAL('clicked()'),functools.partial(self.cancelTests,indx,True)) self.testMessage.show() self.testMessage.raise_() self.connect(CCP4Modules.JOBCONTROLLER(),QtCore.SIGNAL('testMessage'),self.updateTestReport) import UtilityThread if not hasattr(self,'testThreads'): self.testThreads = {} self.testThreads[indx] = UtilityThread.UtilityThread(functools.partial(self.runTests,indx)) self.testThreads[indx].finished.connect(functools.partial(self.cancelTests,indx)) self.testThreads[indx].start() #self.runTests(indx) def updateTestReport(self,message): self.testReport.setReadOnly(False) mess=self.testReport.toPlainText() + '\n' + message self.testReport.setPlainText(mess) self.testReport.setReadOnly(True) self.testReport.repaint() def cancelTests(self,indx,force=False): if getattr(self,'testThreads',None) is not None and len(self.testThreads)>indx: if force: self.testThreads[indx].exit() del self.testThreads[indx] if force: self.testMessage.close() def runTests(self,indx): sG = self.widgets['SERVERGROUP'+str(indx)] remoteFileList=[sG.widgets['ccp4Dir'].model.__str__()] if sG.widgets['mechanism'].model.__str__() in ['ssh','qsub_remote']: remoteFileList.append(sG.widgets['tempDir'].model.__str__()) if sG.widgets['timeout'].model.isSet(): timeout = float(sG.widgets['timeout'].model) else: timeout = None maxTries = int(sG.widgets['maxTries'].model) for server in sG.widgets['serverList'].model: #print 'testing server',server self.emit(QtCore.SIGNAL('testMessage'), 'Testing '+str(server)+'\n') try: ret = CCP4Modules.JOBCONTROLLER().testRemoteFiles(machine=str(server),username=str(self.testDialogUsername.text()), password = str(self.testDialogPassword.text()), remoteFileList=remoteFileList,timeout=timeout,maxTries=maxTries) message = '' labelList = ['CCP4 distro','Tmp dir'] for ii in range(len(ret)): message = message + labelList[ii] + ': ' + str(ret[ii]) +'\n' except CException as e: message = ' Failed connection\n ' + e.description()[0] + '\n ' + str(e._reports[0]['details']) +'\n' except Exception as e: message = ' Failed connection \n'+str(e) self.emit(QtCore.SIGNAL('testMessage'), message) def doApply(self,source): #print 'CServerSetupWindow.doApply', self.widgets['SERVERGROUP1'].widgets['serverList'].listWidget.count() invalidList = [] for key in self.widgets.keys(): self.widgets[key].updateModelFromView() #print 'CServerSetupWindow.doApply',key,self.widgets[key].widgets['mechanism'].currentIndex(),self.container.get(key).mechanism self.widgets[key].validate() if self.widgets[key].isValid is not None and not self.widgets[key].isValid: invalidList.append(key) #print 'CServerSetupWindow.doApply invalidList',invalidList if len(invalidList)>0: QtGui.QMessageBox.warning(self,'Server setup','Invalid data - not saved') else: self.container.save(source) #QtGui.QDialog.close(self) self.setAltSourceButton() from core import CCP4Modules CCP4Modules.JOBCONTROLLER().resetServersEnabled() def close(self): QtGui.QDialog.close(self) def help(self): CCP4Modules.WEBBROWSER().loadWebPage(helpFileName='servers.html',newTab=True) class CRemoteStatus(QtGui.QDialog): def __init__(self,parent): QtGui.QDialog.__init__(self,parent) self.setModal(False) self.setLayout(QtGui.QVBoxLayout()) self.advice = QtGui.QLabel(self) self.layout().addWidget(self.advice) self.jobList = QtGui.QListWidget(self) self.layout().addWidget(self.jobList ) butLayout = QtGui.QHBoxLayout() for label,connect in [['Close',self.close]]: but = QtGui.QPushButton(label,self) self.connect(but,QtCore.SIGNAL('released()'),connect) butLayout.addWidget(but) self.layout().addLayout(butLayout) def load(self,text='',title=None,advice=None): if title is not None: self.setWindowTitle(title) else: self.setWindowTitle('') if advice is not None: self.advice.setText(advice) else: self.advice.setText('') self.jobList.clear() lineList = text.split('\n') for line in lineList: self.jobList.addItem(line) class CPasswordEntry(QtGui.QDialog): def __init__(self,parent,label='Please reenter password',closeMessage='Ignore remote jobs'): QtGui.QDialog.__init__(self,parent) self.setModal(False) self.setLayout(QtGui.QVBoxLayout()) self.layout().addWidget(QtGui.QLabel(label)) self.passwordEntry = QtGui.QLineEdit(self) self.passwordEntry.setEchoMode(QtGui.QLineEdit.Password) self.layout().addWidget(self.passwordEntry) butLayout = QtGui.QHBoxLayout() for label,connect in [['Apply',self.sendPass],[closeMessage,self.close]]: but = QtGui.QPushButton(label,self) butLayout.addWidget(but) self.connect(but,QtCore.SIGNAL('released()'),connect) self.layout().addLayout(butLayout) def sendPass(self): passw = str(self.passwordEntry.text()) #print 'sendPass',passw self.emit(QtCore.SIGNAL('passwordEntered'),passw) self.close() class CListProcesses(QtGui.QDialog): COLUMNHEADERS = [ 'Machine/Project','Job','Taskname','Running process','Pid','Running time','Executable' ] def __init__(self,parent): QtGui.QDialog.__init__(self,parent) self.setWindowTitle('CCP4i2 Running jobs and processes') self.setLayout(QtGui.QVBoxLayout()) self.treeWidget = QtGui.QTreeWidget(self) self.treeWidget.setColumnCount(len(self.COLUMNHEADERS)) self.treeWidget.setMinimumWidth(400) self.treeWidget.setColumnWidth(1,50) self.treeWidget.setColumnWidth(4,50) self.treeWidget.setColumnWidth(6,250) self.layout().addWidget(self.treeWidget) self.connect(self.treeWidget,QtCore.SIGNAL('itemSelectionChanged()'),self.handleSelectionChanged) butLayout = QtGui.QHBoxLayout() butLayout.addStretch(3) for label,connect in [['Update',self.load], ['Help',self.help], ['Close',self.close], ['Kill job',self.killJob], ['Mark as finished',functools.partial(self.markFinished,False)], ['Mark as failed',self.markFinished]]: but = QtGui.QPushButton(label,self) self.connect(but,QtCore.SIGNAL('released()'),connect) butLayout.addWidget(but) butLayout.addStretch(3) self.killButton = butLayout.itemAt(4).widget() self.markFinishedButton = butLayout.itemAt(5).widget() self.markFailedButton = butLayout.itemAt(6).widget() self.killButton.setEnabled(False) self.markFinishedButton.setEnabled(False) self.markFailedButton.setEnabled(False) self.layout().addLayout(butLayout) self.projectNameCache = {} self.connect(CCP4Modules.JOBCONTROLLER(),QtCore.SIGNAL('remoteProcessesList'),self.handleRemoteProcessesList) def projectName(self,projectId): if not self.projectNameCache.has_key(projectId): self.projectNameCache[projectId] = CCP4Modules.PROJECTSMANAGER().db().getProjectInfo(projectId,'projectname') return self.projectNameCache[projectId] def load(self): self.treeWidget.clear() #self.tableWidget.setRowCount(0) self.treeWidget.setHeaderLabels(self.COLUMNHEADERS) # load local info import time now = time.time() procDict = CCP4Modules.JOBCONTROLLER().listLocalProcesses(containsList=['ccp4']) #print 'CListProcesses.load',procDict runningJobs = CCP4Modules.PROJECTSMANAGER().db().getRunningJobs() from core import CCP4Utils self.drawTree(CCP4Utils.getHostName(),now,procDict,runningJobs) CCP4Modules.JOBCONTROLLER().listRemoteProcesses() def handleRemoteProcessesList(self,machine,jobDict,procDict,atTime): #print 'handleRemoteProcessesList',machine,procDict,procDict,atTime #print 'handleRemoteProcessesList processes keys',procDict.keys() runningJobs = CCP4Modules.PROJECTSMANAGER().db().getRunningJobs(remote=True) #print 'handleRemoteProcessesList runningJobs',runningJobs self.drawTree(machine,atTime,procDict,runningJobs) def drawTree(self,machine,now,procDict,runningJobs): machineItem = QtGui.QTreeWidgetItem([machine,'','','','','','']) machineItem.setFlags(QtCore.Qt.ItemIsEnabled) font = machineItem.font(0) font.setBold(True) machineItem.setFont(0,font) self.treeWidget.addTopLevelItem(machineItem) self.treeWidget.expandItem(machineItem) for jobId,jobNumber,taskName,projectId,processId,parentJobId in runningJobs: if parentJobId is None and processId is not None: taskTitle = CCP4Modules.TASKMANAGER().getTitle(taskName) #print 'CListProcesses.load',jobId,jobNumber,processId projectName = self.projectName(projectId) if procDict.has_key(processId): procDict[processId]['used'] = True taskItem = self.makeItem(now,projectName,jobNumber,taskTitle,procDict[processId],italic=True,jobId=jobId) taskItem.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEnabled) machineItem.addChild(taskItem) for child in procDict[processId]['children']: if procDict.has_key(child): procDict[child]['used'] = True item = self.makeItem(now,'','','',procDict[child]) item.setFlags(QtCore.Qt.ItemIsEnabled) taskItem.addChild(item) self.treeWidget.expandItem(taskItem) else: machineItem.addChild(self.makeItem(now,projectName,jobNumber,taskTitle,None,bold=True,jobId=jobId)) # List any unaccounted ccp4 processes mypid = os.getpid() for key,pDict in procDict.items(): if not pDict.has_key('used') and not pDict['pid'] == mypid: item = self.makeItem(now,'Not i2 process','','',pDict) item.setFlags(QtCore.Qt.ItemIsEnabled) machineItem.addChild(item) def makeItem(self,now,projectName,jobNumber,taskName,procDict,bold=False,italic=False,jobId=None): if procDict is None: treeItem = QtGui.QTreeWidgetItem([projectName,jobNumber,taskName,'No process found','','','']) else: treeItem = QtGui.QTreeWidgetItem([projectName,jobNumber,taskName,str(procDict['name']), str(procDict['pid']),'%.1f' % (now-procDict['create_time']),str(procDict['exe'])]) if bold or italic: font = treeItem.font(0) if bold: font.setBold(True) if italic: font.setItalic(True) for ii in range(7): treeItem.setFont(ii,font) if jobId is not None: treeItem.setData(0,QtCore.Qt.UserRole,jobId) return treeItem def help(self): CCP4Modules.WEBBROWSER().loadWebPage(helpFileName='tutorial',target='running') def handleSelectionChanged(self): selItems = self.treeWidget.selectedItems() if len(selItems) == 0: self.markFinishedButton.setEnabled(False) self.markFailedButton.setEnabled(False) self.killButton.setEnabled(False) else: ifRunning = selItems[0].data(3,QtCore.Qt.DisplayRole).toString().__str__() != 'No process found' self.markFinishedButton.setEnabled((not ifRunning)) self.markFailedButton.setEnabled((not ifRunning)) self.killButton.setEnabled(ifRunning) def killJob(self): from dbapi import CCP4DbApi jobId = self.treeWidget.selectedItems()[0].data(0,QtCore.Qt.UserRole).toString().__str__() print 'CListProcesses.killJob',jobId err = CCP4Modules.JOBCONTROLLER().killJobProcess(jobId=jobId) CCP4Modules.PROJECTSMANAGER().db().updateJobStatus(jobId,CCP4DbApi.JOB_STATUS_FAILED) self.load() def markFinished(self,failed=True): from dbapi import CCP4DbApi jobId = self.treeWidget.selectedItems()[0].data(0,QtCore.Qt.UserRole).toString().__str__() if failed: CCP4Modules.PROJECTSMANAGER().db().updateJobStatus(jobId,CCP4DbApi.JOB_STATUS_FAILED) else: CCP4Modules.PROJECTSMANAGER().db().updateJobStatus(jobId,CCP4DbApi.JOB_STATUS_FINISHED) self.load()