00001 #!/usr/local/bin/python
00002 #
00003 # Copyright 2003
00004 # by
00005 # The Board of Trustees of the
00006 # Leland Stanford Junior University.
00007 # All rights reserved.
00008 #
00009
00010 __facility__ = "Online"
00011 __abstract__ = "GLAST LAT run control common class"
00012 __author__ = "R. Claus <Claus@SLAC.Stanford.edu> SLAC - GLAST LAT I&T/Online"
00013 __date__ = "2/5/2003"
00014 __version__ = "$Revision: 2.46 $"
00015 __credits__ = "SLAC"
00016
00017 import LATTE.copyright_SLAC
00018
00019
00020 import sys
00021 import os
00022 import time
00023 import types
00024 import atexit
00025 import socket
00026 import logging as log
00027 import logging.handlers
00028 from rcLoginImpl import rcLoginImpl
00029 from rcSecurityMan import rcSecurityMan
00030 from rcPreferencesManager import rcPreferencesManager
00031 from rcPreferencesGUIImpl import rcPreferencesGUIImpl
00032
00033 from LATTE.client.gOptions import Options
00034 from LATTE.client.gCmdCli import CmdCli
00035 from LATTE.client.gEvtCli import EvtCli
00036 from LATTE.database.gLAT import GLAT
00037 from LATTE.database.gXBR import GXBRD
00038 from LATTE.database.gOCS import GOCS
00039 from LATTE.logger.MSGLogGUIImpl import MSGLogGUIImpl
00040 from LATTE.database.gVersions import Gversions
00041
00042
00043 class GDBN(dict):
00044 """\brief Database node record.
00045
00046 The database node contains session level variables in addition
00047 to preferences and entries used for diagnostics purposes.
00048 """
00049 def __init__(self):
00050 """\brief Initialize the dictionary.
00051
00052 """
00053 dict.__init__(self)
00054
00055
00056 class RunControlSocketHandler(logging.handlers.SocketHandler):
00057 def __init__(self, *args):
00058 apply(logging.handlers.SocketHandler.__init__, (self,) + args)
00059
00060 def handleError(self, record):
00061 if self.closeOnError and self.sock:
00062 self.sock.shutdown(2)
00063 self.sock.close()
00064 self.sock = None #try to reconnect next time
00065 else:
00066 raise IOError, sys.exc_info()[1]
00067
00068 class ErrorLogCounterHandler(logging.Handler):
00069 def __init__(self, common):
00070 logging.Handler.__init__(self)
00071 self.__common = common
00072
00073 def emit(self, record):
00074 self.__common.incrementErrorLogCount()
00075
00076 class RunControlFileHandler(log.FileHandler):
00077 """\brief FileHandler class for handling multiple line log entries
00078
00079 If a log entry contains multiple lines, such as the case for
00080 tracebacks or exceptions a begin and end delimiter is added
00081 to the log entry so that it can be parsed by the MessageLogger
00082 """
00083 def __init__(self, filename, mode):
00084 log.FileHandler.__init__(self, filename, mode)
00085
00086 def emit(self, record):
00087 try:
00088 msg = self.format(record)
00089 (msg1, msg2) = msg.split(' - ',1)
00090 if msg2.find('\n') > 0:
00091 msg = msg1 + " - <
00092 if not hasattr(types, "UnicodeType"): #if no unicode support...
00093 self.stream.write("%s\n" % msg)
00094 else:
00095 try:
00096 self.stream.write("%s\n" % msg)
00097 except UnicodeError:
00098 self.stream.write("%s\n" % msg.encode("UTF-8"))
00099 self.flush()
00100 except:
00101 self.handleError(record)
00102
00103 class RunControlCommon(object):
00104 """Common class for providing a run control helper functions
00105 """
00106 def __init__(self):
00107 self.MSGLogger = None
00108 self.__startGMT = time.gmtime()
00109 self.__options = Options(['config', 'server'],
00110 ['appdir','cmddebug','evtdebug','fswmsglevel',
00111 'noreload','schema','securedir','app',
00112 'ocsport'],
00113 ['initforce','noerrfile','norcoverride',
00114 'ocsverbose','oesforce',
00115 'playback','paramverify','noplumbing'])
00116 if len(sys.argv) > 1 and sys.argv[1] == '--help':
00117 self.__options.usage()
00118 print self.__usage()
00119 sys.exit(0)
00120 self.__lat = None
00121 self.__xbrd = None
00122 self.__dds = None
00123 self.__server = None
00124 try:
00125 self.__options.parse()
00126 except Exception, msg:
00127 self.__options.usage(self.__usage())
00128 raise Exception, msg
00129
00130 if self.__options.ocsport is not None:
00131 self.__cmdCli = CmdCli(port=int(self.__options.ocsport))
00132 self.__evtCli = EvtCli(port=int(self.__options.ocsport))
00133 else:
00134 self.__cmdCli = CmdCli()
00135 self.__evtCli = EvtCli()
00136 self.__ocs = GOCS(self.__cmdCli)
00137 self.__securityMan = None
00138 self.__dbn = GDBN()
00139 self.__prefMan = None
00140 self.__prefGUI = None
00141 self.__errLogCnt = 0
00142 self.__hwVersions = None
00143
00144 try:
00145 import qt
00146 self.__qApp = qt.QApplication([])
00147 self.__qApp.addLibraryPath(os.path.join(os.environ['ONLINE_ROOT'],
00148 'LATTE/ext/plugins'))
00149 except ImportError:
00150 self.__qApp = None
00151
00152 # Arrange for logging to be properly shutdown at program exit
00153 atexit.register(log.shutdown)
00154
00155 def initialize(self, startup=False):
00156 self.__prefMan = rcPreferencesManager(self.__options.config)
00157 if (self.__prefMan.loadConfig()):
00158 if self.__options.cmddebug is not None:
00159 self.__cmdCli.setDebug(int(self.__options.cmddebug))
00160
00161 if self.__options.evtdebug is not None:
00162 self.__evtCli.setDebug(int(self.__options.evtdebug))
00163
00164 self.startLogger(startup)
00165
00166 if self.__options.securedir is not None:
00167 self.__securityMan = rcSecurityMan(os.path.abspath(self.__options.securedir),
00168 self.__prefMan.preferences()["users"])
00169 self.__securityMan.readPermissions()
00170 self.__dbn['_securityMan'] = self.__securityMan
00171 if self.__options.noreload is not None:
00172 import string
00173 self.__dbn["_dontReload"] = map(string.strip, file(self.__options.noreload).readlines())
00174 else:
00175 raise RuntimeError, "Can not find configuration file: %s" % self.__options.config
00176
00177 def __usage(self):
00178 return """
00179
00180 Detailed explanation:
00181
00182 Mandatory options:
00183 --server to specify the hostname or the IP address of the teststand crate
00184 to connect to.
00185 --config to specify the run control configuration file to use
00186
00187 Optional options:
00188 --app Specify the standalone application or suite to run.
00189 Typically used by test or suite runners.
00190 --appdir Specify the application directory
00191 --cmddebug Specify command client debug mode (defaults to 0).
00192 If this option is specified then all register accesses from the
00193 command client is logged to the logger.
00194 --evtdebug Specify event client debug mode (defaults to 0)
00195 The following debug modes are available and can be added together
00196 for combinations:
00197 0: No Debug
00198 1: Debug All
00199 2: Event Dump
00200 4: Warnings
00201 8: Parser Output
00202 16: Error Output
00203 --fswmsglevel Specify the FSW message level for the OCS manager task.
00204 --initforce Force command-response and event fabrics initialization on the SBC.
00205 --noerrfile Disable error archive file output.
00206 --norcoverride Prevents the run conditions to be overridden with NOT-DEFINED.
00207 --noreload Specify a file that contains prefixes of modules
00208 which should not be reloaded when a user script is selected.
00209 This list gets appended to the default list which currently contains
00210 the following prefixes 'scipy', 'weave', 'qwt', 'xml', 'pyexpat', 'win32'.
00211 The file should contain one prefix per line.
00212 --ocsverbose Enable fabrics initialization messages on the SBC.
00213 --noplumbing Do not automatically plumb the LAT upon connection.
00214 --ocsport Define the port used to connect to OCS
00215 --oesforce Force our OES socket to be the default event server for the SBC.
00216 --paramverify Force the parameter verifier to come up even if running in non-secure mode
00217 --playback Indicate that Run Control should command the
00218 event server to play back the next event from its file. Since
00219 this is done using the GLT's self trigger command, the state of
00220 the internal trigger enable bit deterimines whether the
00221 application or Run Control will issue the self trigger command.
00222 No events will be delivered if the application has the internal
00223 trigger enabled, but doesn't issue CMD_SELF_TRIGGERs.
00224 --schema Specify the schema file to load.
00225 --securedir The full path to the secure directory where the password file
00226 ("passwords") and the user permissions file ("security.cfg") is stored.
00227 If this switch is specified then RunControl is started in secure mode.
00228 """
00229
00230
00231
00232
00233 def connect(self, force=False):
00234 self.__cmdCli.connect(self.__options.server)
00235 self.__evtCli.connect(self.__options.server, force)
00236 self.__evtCli.purge()
00237
00238 def disconnect(self):
00239 self.__cmdCli.disconnect()
00240 self.__evtCli.disconnect()
00241
00242 def readSchema(self, filename):
00243 self.__lat = GLAT(self.__cmdCli)
00244 self.__lat.readSchema(filename)
00245 # Do not load the schema again if the GXBRD node does not exist.
00246 # See LTE-325 for more info.
00247 if self.__lat.xbrdExists():
00248 self.__xbrd = GXBRD(self.__cmdCli)
00249 self.__xbrd = self.__xbrd.readSchema(filename)
00250 else:
00251 self.__xbrd = None
00252 self.__dbn['_LATSchema'] = self.__lat
00253 self.__dbn['_LATSchemaFile'] = filename
00254 self.__dbn['_XBRDSchema'] = self.__xbrd
00255 self.__dbn['_XBRDchemaFile'] = filename
00256
00257 def elogWebsite(self):
00258 import webbrowser
00259 webbrowser.open(url=self.__prefMan.preferences()["elogurl"], new=1)
00260
00261 def EBFdistribution(self):
00262 if int(self.__prefMan.preferences()["servernbl"]) == 0:
00263 if self.__dds is not None:
00264 self.__dds.disconnect()
00265 log.info("Disconnected from the data distribution server since it was disabled in preferences.")
00266 self.__dds = None
00267 return
00268 dataType = 'LAT_EBFdata'
00269 server = self.__prefMan.preferences()["server"].strip()
00270 if server != '':
00271 try:
00272 import LATTE.tools.DataDistributor as DD
00273 except:
00274 log.error("Can't find DataDistributor module, is it in PYTHONPATH?")
00275 else:
00276 if self.__server is None:
00277 self.__dds = DD.DataDistributorServer(dataType)
00278 try:
00279 self.__dds.connect(server)
00280 log.info("Connected to the data distribution server at %s." % server)
00281 #atexit.register(self.__dds.disconnect) # Already done somewhere?
00282 except:
00283 log.warn("Unable to connect to data distribution server %s: serving of %s disabled" % (self.__prefMan.preferences()["server"], dataType))
00284 self.__dds = None
00285 elif server != self.__server:
00286 if self.__dds is not None:
00287 self.__dds.disconnect()
00288 log.info("Disconnected from the data distribution server.")
00289 self.__dds = DD.DataDistributorServer(dataType)
00290 try:
00291 self.__dds.connect(server)
00292 log.info("Reconnected to the data distribution server since the server changed in preferences.")
00293 #atexit.register(self.__dds.disconnect) # Already done somewhere?
00294 except:
00295 log.warn("Unable to connect to data distribution server %s: serving of %s disabled" % (self.__prefMan.preferences()["server"], dataType))
00296 self.__dds = None
00297 self.__server = self.__prefMan.preferences()["server"].strip()
00298
00299 def setLoggingLevel(self, logLevel=None):
00300 if logLevel is None: logLevel = self.__prefMan.preferences()["loglevel"]
00301 # Set the log levels at the handler level so that
00302 # file based handlers can still record DEBUG logs.
00303 log.getLogger("").setLevel(0)
00304 # First handler is always the default stdout handler
00305 log.getLogger("").handlers[0].setLevel(eval("log."+logLevel))
00306 for hdlr in log.getLogger("").handlers[1:]:
00307 if hdlr.__class__.__name__ == 'RunControlSocketHandler':
00308 hdlr.setLevel(eval("log."+logLevel))
00309 elif hdlr.__class__.__name__ == 'ErrorLogCounterHandler':
00310 hdlr.setLevel(eval("log.ERROR"))
00311
00312 def startLogger(self, startup=True):
00313 # Start up the message logger
00314 log.basicConfig()
00315 loghost = self.__prefMan.preferences()["loghost"]
00316 logport = self.__prefMan.preferences()["logport"]
00317 local = socket.gethostname().lower() == loghost.lower() or socket.gethostbyname(loghost) == '127.0.0.1'
00318 if int(self.__prefMan.preferences()["lognbl"]) == 1:
00319 if local and startup:
00320 if self.MSGLogger is not None:
00321 return
00322 self.MSGLogger = MSGLogGUIImpl(logport)
00323 self.MSGLogger.startListenerThread()
00324 msgPath = self.__prefMan.preferences()["logdir"]
00325 # Note that if the file already exists, it will be appended to
00326 ts = time.strftime('%y%m%d%H%M%S', self.__startGMT)
00327 fileName = "msg" + ts + ".log"
00328 self.__prefMan.preferences()["logfilename"] = fileName
00329 fmt = log.Formatter("%(asctime)s(UT) %(filename)s:%(lineno)d %(levelname)-5s - %(message)s")
00330 fmt.converter = time.gmtime
00331 self.fileHdlr = RunControlFileHandler(os.path.join(msgPath, fileName), "a")
00332 self.fileHdlr.setFormatter(fmt)
00333 log.getLogger("").addHandler(self.fileHdlr)
00334 self.sockHdlr = RunControlSocketHandler(loghost,
00335 logport)
00336 self.sockHdlr.setFormatter(fmt)
00337 log.getLogger("").addHandler(self.sockHdlr)
00338 self.errLogCounter = ErrorLogCounterHandler(self)
00339 log.getLogger("").addHandler(self.errLogCounter)
00340 self.setLoggingLevel()
00341 try:
00342 log.info('Trying to connect to the message logger')
00343 except:
00344 log.getLogger("").removeHandler(self.sockHdlr)
00345 log.exception("Can't connect to the message logger, disabling logging to socket")
00346 else:
00347 log.info('Successfully connected to the message logger')
00348 elif self.MSGLogger is not None:
00349 log.info('Disconnecting from the message logger')
00350 if local:
00351 self.MSGLogger.close()
00352 self.MSGLogger = None
00353 log.getLogger("").removeHandler(self.errLogCounter)
00354 log.getLogger("").removeHandler(self.sockHdlr)
00355 log.getLogger("").removeHandler(self.fileHdlr)
00356 log.shutdown()
00357 else:
00358 # Make sure error log counter handler is added
00359 # even when logs are disabled
00360 self.errLogCounter = ErrorLogCounterHandler(self)
00361 log.getLogger("").addHandler(self.errLogCounter)
00362 self.setLoggingLevel()
00363
00364
00365 def stopRunLog(self, fileHdlr):
00366 fileHdlr.close()
00367 log.getLogger("").removeHandler(fileHdlr)
00368
00369 def startRunLog(self, fileName):
00370 fileHdlr = RunControlFileHandler(fileName, "w")
00371 fmt = log.Formatter("%(asctime)s(UT) %(filename)s:%(lineno)d %(levelname)-5s - %(message)s")
00372 fmt.converter = time.gmtime
00373 fileHdlr.setFormatter(fmt)
00374 log.getLogger("").addHandler(fileHdlr)
00375 return fileHdlr
00376
00377 def options(self):
00378 return self.__options
00379
00380 def prefMan(self):
00381 return self.__prefMan
00382
00383 def prefGUI(self):
00384 if self.__prefGUI is None:
00385 self.__prefGUI = rcPreferencesGUIImpl(self)
00386 return self.__prefGUI
00387
00388 def getMSGLogger(self):
00389 return self.MSGLogger
00390
00391 def getDDS(self):
00392 return self.__dds
00393
00394 def getCmdCli(self):
00395 return self.__cmdCli
00396
00397 def getEvtCli(self):
00398 return self.__evtCli
00399
00400 def getLAT(self):
00401 return self.__lat
00402
00403 def getXBRD(self):
00404 return self.__xbrd
00405
00406 def getOCS(self):
00407 return self.__ocs
00408
00409 def getDBN(self):
00410 return self.__dbn
00411
00412 def getSecurityMan(self):
00413 return self.__securityMan
00414
00415 def getQtApp(self):
00416 return self.__qApp
00417
00418 def getLoginId(self):
00419 """Returns the login id for the last successful login
00420 """
00421 return self.__loginId
00422
00423 def getErrorLogCount(self):
00424 return self.__errLogCnt
00425
00426 def incrementErrorLogCount(self):
00427 self.__errLogCnt += 1
00428
00429 def resetErrorLogCount(self):
00430 self.__errLogCnt = 0
00431
00432 def showLogin(self, parent):
00433 login = rcLoginImpl(parent=parent)
00434 status = login.exec_loop()
00435 if status == login.Accepted:
00436 loginId = str(login.txtLoginId.text()).strip()
00437 password = str(login.txtPassword.text()).strip()
00438 authResult = self.__securityMan.authenticateUser(loginId, password)
00439 if authResult == 0:
00440 self.__loginId = loginId
00441 log.info("User %s successfully logged in" % loginId)
00442 return 1
00443 elif authResult == -1:
00444 log.error("User authentication failed, loginId=%s" % loginId)
00445 return authResult
00446 else:
00447 log.error("Password database is not accessible.")
00448 return authResult
00449 else:
00450 return None
00451
00452 def getHardwareVersions(self, lat=None, xbrd=None):
00453 if self.__hwVersions is None:
00454 if lat is None: lat = self.getLAT()
00455 if xbrd is None: xbrd = self.getXBRD()
00456 self.__hwVersions = Gversions.getHardwareVersions(lat, xbrd)
00457 return self.__hwVersions
00458
00459 def clearHardwareVersionsCache(self):
00460 self.__hwVersions = None