Ejemplo n.º 1
0
class BookkeepingWatchAgent(AgentModule, TransformationAgentsUtilities):
  """ LHCbDIRAC only agent. A threaded agent.
  """

  def __init__(self, *args, **kwargs):
    """ c'tor
    """
    AgentModule.__init__(self, *args, **kwargs)
    TransformationAgentsUtilities.__init__(self)

    self.bkQueriesToBeChecked = Queue.Queue()
    self.bkQueriesInCheck = []

    self.fullUpdatePeriod = 86400
    self.bkUpdateLatency = 7200
    self.debug = False

    self.transInThread = {}

    self.pickleFile = 'BookkeepingWatchAgent.pkl'
    self.chunkSize = 1000

    self.pluginsWithNoRunInfo = ['LHCbStandard', 'ReplicateDataset', 'ArchiveDataset',
                                 'LHCbMCDSTBroadcastRandom', 'ReplicateToLocalSE',
                                 'RemoveReplicas', 'RemoveReplicasWhenProcessed',
                                 'RemoveReplicasWithAncestors', 'ReplicateWithAncestors',
                                 'ReduceReplicas', 'RemoveDatasetFromDisk',
                                 'DestroyDataset', 'DestroyDatasetWhenProcessed',
                                 'BySize', 'Standard']

    self.timeLog = {}
    self.fullTimeLog = {}
    self.bkQueries = {}

    self.transClient = None
    self.bkClient = None

  def initialize(self):
    """ Make the necessary initializations.
        The ThreadPool is created here, the _execute() method is what each thread will execute.
    """

    self.fullUpdatePeriod = self.am_getOption('FullUpdatePeriod', self.fullUpdatePeriod)
    self.bkUpdateLatency = self.am_getOption('BKUpdateLatency', self.bkUpdateLatency)
    self.debug = self.am_getOption('verbose', self.debug)

    self.pickleFile = os.path.join(self.am_getWorkDirectory(), self.pickleFile)
    self.chunkSize = self.am_getOption('maxFilesPerChunk', self.chunkSize)

    self.pluginsWithNoRunInfo = Operations().getValue('TransformationPlugins/PluginsWithNoRunInfo',
                                                      self.pluginsWithNoRunInfo)

    self._logInfo('Full Update Period: %d seconds' % self.fullUpdatePeriod)
    self._logInfo('BK update latency : %d seconds' % self.bkUpdateLatency)
    self._logInfo('Plugins with no run info: %s' % ', '.join(self.pluginsWithNoRunInfo))

    self.transClient = TransformationClient()
    self.bkClient = BookkeepingClient()

    try:
      with open(self.pickleFile, 'r') as pf:
        self.timeLog = pickle.load(pf)
        self.fullTimeLog = pickle.load(pf)
        self.bkQueries = pickle.load(pf)
      self._logInfo("successfully loaded Log from", self.pickleFile, "initialize")
    except (EOFError, IOError):
      self._logInfo("failed loading Log from", self.pickleFile, "initialize")
      self.timeLog = {}
      self.fullTimeLog = {}
      self.bkQueries = {}

    maxNumberOfThreads = self.am_getOption('maxThreadsInPool', 1)
    threadPool = ThreadPool(maxNumberOfThreads, maxNumberOfThreads)

    for i in xrange(maxNumberOfThreads):
      threadPool.generateJobAndQueueIt(self._execute, [i])

    gMonitor.registerActivity("Iteration", "Agent Loops", AGENT_NAME, "Loops/min", gMonitor.OP_SUM)
    return S_OK()

  @gSynchro
  def __dumpLog(self):
    """ dump the log in the pickle file
    """
    if self.pickleFile:
      try:
        with open(self.pickleFile, 'w') as pf:
          pickle.dump(self.timeLog, pf)
          pickle.dump(self.fullTimeLog, pf)
          pickle.dump(self.bkQueries, pf)
        self._logVerbose("successfully dumped Log into %s" % self.pickleFile)
      except IOError as e:
        self._logError("fail to open %s: %s" % (self.pickleFile, e))
      except pickle.PickleError as e:
        self._logError("fail to dump %s: %s" % (self.pickleFile, e))
      except ValueError as e:
        self._logError("fail to close %s: %s" % (self.pickleFile, e))

  ################################################################################

  def execute(self):
    """ Main execution method. Just fills a list, and a queue, with BKKQueries ID.
    """

    gMonitor.addMark('Iteration', 1)
    # Get all the transformations
    result = self.transClient.getTransformations(condDict={'Status': ['Active', 'Idle']})
    if not result['OK']:
      self._logError("Failed to get transformations.", result['Message'])
      return S_OK()
    transIDsList = [long(transDict['TransformationID']) for transDict in result['Value']]
    res = self.transClient.getTransformationsWithBkQueries(transIDsList)
    if not res['OK']:
      self._logError("Failed to get transformations with Bk Queries.", res['Message'])
      return S_OK()
    transIDsWithBkQueriesList = res['Value']

    _count = 0
    # Process each transformation
    for transID in transIDsWithBkQueriesList:
      if transID in self.bkQueriesInCheck:
        continue
      self.bkQueriesInCheck.append(transID)
      self.bkQueriesToBeChecked.put(transID)
      _count += 1

    self._logInfo("Out of %d transformations, %d put in thread queue" % (len(result['Value']), _count))

    self.__dumpLog()
    return S_OK()

  def _execute(self, threadID):
    """ Real executor. This is what is executed by the single threads - so do not return here! Just continue
    """

    while True:  # not self.bkQueriesToBeChecked.empty():

      transID = None

      try:

        transID = self.bkQueriesToBeChecked.get()
        self.transInThread[transID] = ' [Thread%d] [%s] ' % (threadID, str(transID))

        startTime = time.time()
        self._logInfo("Processing transformation %s." % transID, transID=transID)

        res = self.transClient.getTransformation(transID, extraParams=False)
        if not res['OK']:
          self._logError("Failed to get transformation", res['Message'], transID=transID)
          continue
        transPlugin = res['Value']['Plugin']

        res = self.transClient.getBookkeepingQuery(transID)
        if not res['OK']:
          self._logError("Failed to get BkQuery", res['Message'], transID=transID)
          continue
        bkQuery = res['Value']

        # Determine the correct time stamp to use for this transformation
        now = datetime.datetime.utcnow()
        self.__timeStampForTransformation(transID, bkQuery, now)

        try:
          files = self.__getFiles(transID, bkQuery, now)
        except RuntimeError as e:
          # In case we failed a full query, we should retry full query until successful
          if 'StartDate' not in bkQuery:
            self.bkQueries.pop(transID, None)
          self._logError("Failed to get response from the Bookkeeping: %s" % e, "", "__getFiles", transID)
          continue

        runDict = {}
        filesMetadata = {}
        # get the files metadata
        for lfnChunk in breakListIntoChunks(files, self.chunkSize):
          start = time.time()
          res = self.bkClient.getFileMetadata(lfnChunk)
          self._logVerbose("Got metadata from BK for %d files" % len(lfnChunk), transID=transID, reftime=start)
          if not res['OK']:
            self._logError("Failed to get BK metadata for %d files" % len(lfnChunk),
                           res['Message'], transID=transID)
            # No need to return as we only consider files that are successful...
          else:
            filesMetadata.update(res['Value']['Successful'])

        # There is no need to add the run information for a transformation that doesn't need it
        if transPlugin not in self.pluginsWithNoRunInfo:
          for lfn, metadata in filesMetadata.iteritems():
            runID = metadata.get('RunNumber', None)
            if isinstance(runID, (basestring, int, long)):
              runDict.setdefault(int(runID), []).append(lfn)
          try:
            self.__addRunsMetadata(transID, runDict.keys())
          except RuntimeError as e:
            self._logException("Failure adding runs metadata",
                               method="__addRunsMetadata",
                               lException=e,
                               transID=transID)
        else:
          runDict[None] = filesMetadata.keys()

        # Add all new files to the transformation
        for runID in sorted(runDict):
          lfnList = runDict[runID]
          # We enter all files of a run at once, otherwise do it by chunks
          lfnChunks = [lfnList] if runID else breakListIntoChunks(lfnList, self.chunkSize)
          for lfnChunk in lfnChunks:
            # Add the files to the transformation
            self._logVerbose('Adding %d lfns for transformation' % len(lfnChunk), transID=transID)
            result = self.transClient.addFilesToTransformation(transID, lfnChunk)
            if not result['OK']:
              self._logError("Failed to add %d lfns to transformation" % len(lfnChunk), result['Message'],
                             transID=transID)
              return result
            else:
              # Handle errors
              errors = {}
              for lfn, error in result['Value']['Failed'].iteritems():
                errors.setdefault(error, []).append(lfn)
              for error, lfns in errors.iteritems():
                self._logWarn("Failed to add files to transformation", error, transID=transID)
                self._logVerbose("\n\t".join([''] + lfns))
              # Add the metadata and RunNumber to the newly inserted files
              addedLfns = [lfn for (lfn, status) in result['Value']['Successful'].iteritems() if status == 'Added']
              if addedLfns:
                # Add files metadata: size and file type
                lfnDict = dict((lfn, {'Size': filesMetadata[lfn]['FileSize'],
                                      'FileType': filesMetadata[lfn]['FileType']})
                               for lfn in addedLfns)
                res = self.transClient.setParameterToTransformationFiles(transID, lfnDict)
                if not res['OK']:
                  self._logError("Failed to set transformation files metadata", res['Message'])
                  return res
                # Add run information if it exists
                if runID:
                  self._logInfo("Added %d files to transformation for run %d, now including run information"
                                % (len(addedLfns), runID), transID=transID)
                  self._logVerbose("Associating %d files to run %d" % (len(addedLfns), runID), transID=transID)
                  res = self.transClient.addTransformationRunFiles(transID, runID, addedLfns)
                  if not res['OK']:
                    self._logError("Failed to associate %d files to run %d" % (len(addedLfns), runID),
                                   res['Message'], transID=transID)
                    return res
                else:
                  self._logInfo("Added %d files to transformation" % len(addedLfns), transID=transID)

      except Exception as x:  # pylint: disable=broad-except
        self._logException('Exception while adding files to transformation',
                           lException=x,
                           method='_execute',
                           transID=transID)
      finally:
        self._logInfo("Processed transformation", transID=transID, reftime=startTime)
        if transID in self.bkQueriesInCheck:
          self.bkQueriesInCheck.remove(transID)
        self.transInThread.pop(transID, None)

    return S_OK()

  @gSynchro
  def __timeStampForTransformation(self, transID, bkQuery, now):
    """ Determine the correct time stamp to use for this transformation
    """

    fullTimeLog = self.fullTimeLog.setdefault(transID, now)
    bkQueryLog = self.bkQueries.setdefault(transID, {})

    bkQueryLog.pop('StartDate', None)
    self.bkQueries[transID] = bkQuery.copy()
    if transID in self.timeLog \
            and bkQueryLog == bkQuery \
            and (now - fullTimeLog) < datetime.timedelta(seconds=self.fullUpdatePeriod):
      # If it is more than a day since the last reduced query, make a full query just in case
      timeStamp = self.timeLog[transID]
      delta = datetime.timedelta(seconds=self.bkUpdateLatency)
      bkQuery['StartDate'] = (timeStamp - delta).strftime('%Y-%m-%d %H:%M:%S')
    if 'StartDate' not in bkQuery:
      self.fullTimeLog[transID] = now

  def __getFiles(self, transID, bkQuery, now):
    """ Perform the query to the Bookkeeping
    """
    self._logInfo("Using BK query for transformation: %s" % str(bkQuery), transID=transID)
    start = time.time()
    result = self.bkClient.getFiles(bkQuery)
    self._logVerbose("BK query time: %.2f seconds." % (time.time() - start), transID=transID)
    if not result['OK']:
      raise RuntimeError(result['Message'])
    else:
      self.__updateTimeStamp(transID, now)
      if result['Value']:
        self._logInfo("Obtained %d files from BK" % len(result['Value']), transID=transID)
      return result['Value']

  @gSynchro
  def __updateTimeStamp(self, transID, now):
    """
    Update time stamp for current transformation to now
    """
    self.timeLog[transID] = now

  def __addRunsMetadata(self, transID, runsList):
    """ Add the run metadata
    """
    runsInCache = self.transClient.getRunsInCache({'Name': ['TCK', 'CondDb', 'DDDB']})
    if not runsInCache['OK']:
      raise RuntimeError(runsInCache['Message'])
    newRuns = list(set(runsList) - set(runsInCache['Value']))
    if newRuns:
      self._logVerbose("Associating run metadata to %d runs" % len(newRuns), transID=transID)
      res = self.bkClient.getRunInformation({'RunNumber': newRuns, 'Fields': ['TCK', 'CondDb', 'DDDB']})
      if not res['OK']:
        raise RuntimeError(res['Message'])
      else:
        for run, runMeta in res['Value'].iteritems():
          res = self.transClient.addRunsMetadata(run, runMeta)
          if not res['OK']:
            raise RuntimeError(res['Message'])
    # Add run duration to the metadata
    runsInCache = self.transClient.getRunsInCache({'Name': ['Duration']})
    if not runsInCache['OK']:
      raise RuntimeError(runsInCache['Message'])
    newRuns = list(set(runsList) - set(runsInCache['Value']))
    if newRuns:
      self._logVerbose("Associating run duration to %d runs" % len(newRuns), transID=transID)
      res = self.bkClient.getRunInformation({'RunNumber': newRuns, 'Fields': ['JobStart', 'JobEnd']})
      if not res['OK']:
        raise RuntimeError(res['Message'])
      else:
        for run, runMeta in res['Value'].iteritems():
          duration = (runMeta['JobEnd'] - runMeta['JobStart']).seconds
          res = self.transClient.addRunsMetadata(run, {'Duration': duration})
          if not res['OK']:
            raise RuntimeError(res['Message'])

  def finalize(self):
    """ Gracious finalization
    """
    if self.bkQueriesInCheck:
      self._logInfo("Wait for queue to get empty before terminating the agent (%d tasks)" % len(self.transInThread))
      self.bkQueriesInCheck = []
      while self.transInThread:
        time.sleep(2)
      self.log.info("Threads are empty, terminating the agent...")
    return S_OK()
Ejemplo n.º 2
0
class ModuleBase(object):
  """ Base class for Modules - works only within DIRAC workflows
  """

  #############################################################################

  def __init__(self, loggerIn=None, operationsHelperIn=None, bkClientIn=None, dm=None):
    """ Initialization of module base.
    """

    if loggerIn is None:
      self.log = gLogger.getSubLogger('ModuleBase')
    else:
      self.log = loggerIn

    if operationsHelperIn is None:
      self.opsH = Operations()
    else:
      self.opsH = operationsHelperIn

    if bkClientIn is None:
      self.bkClient = BookkeepingClient()
    else:
      self.bkClient = bkClientIn

    if dm is None:
      self.dataManager = DataManager()
    else:
      self.dataManager = dm

    self.requestValidator = RequestValidator()

    self.production_id = ''
    self.prod_job_id = ''
    self.jobID = 0
    self.step_number = ''
    self.step_id = ''

    self.fileReport = None
    self.jobReport = None
    self.request = None

    self.workflowStatus = None
    self.stepStatus = None
    self.workflow_commons = None
    self.step_commons = None

    self.debugSE = 'CERN-DEBUG'

    self.executable = 'gaudirun.py'
    self.applicationName = 'Unknown'
    self.applicationVersion = 'Unknown'
    self.applicationLog = ''
    self.applicationType = None
    self.systemConfig = None
    self.extraPackages = None
    self.bkConfigName = None
    self.BKstepID = None
    self.condDBTag = None
    self.DDDBTag = None
    self.dqTag = None
    self.CPUe = None
    self.eventType = ''
    self.gaudiSteps = None
    self.InputData = ''
    self.inputDataList = []
    self.inputDataType = None
    self.histoName = "Hist.root"
    self.optionsFile = None
    self.optionsFormat = None
    self.optionsLine = None
    self.extraOptionsLine = None
    self.jobType = None
    self.logFilePath = None
    self.onlineCondDBTag = None
    self.onlineDDDBTag = None
    self.outputSEs = {}
    self.outputDataFileMask = None
    self.numberOfEvents = -1
    self.maxNumberOfEvents = None
    self.TCK = None
    self.mcTCK = None
    self.multicoreJob = None
    self.multicoreStep = None
    self.poolXMLCatName = 'pool_xml_catalog.xml'
    self.persistency = ''
    self.processingPass = None
    self.runNumber = 'Unknown'
    self.runTimeProjectName = None
    self.runTimeProjectVersion = None
    self.simDescription = ''
    self.siteName = None
    self.stepName = None
    self.stepInputData = None
    self.XMLSummary = ''  # name of the file, not the object
    self.stepProcPass = None
    self.outputFilePrefix = ''

  #############################################################################

  def execute(self, version=None,
              production_id=None, prod_job_id=None, wms_job_id=None,
              workflowStatus=None, stepStatus=None,
              wf_commons=None, step_commons=None,
              step_number=None, step_id=None):
    """ Function called by all super classes
    """

    if version:
      self.log.info('===== Executing ' + version + ' ===== ')

    self.log.verbose("Executing directory for job is %s" % os.getcwd())

    if production_id:
      self.production_id = production_id
    else:
      self.production_id = self.workflow_commons['PRODUCTION_ID']  # This is a string, like '00051753'

    if prod_job_id:
      self.prod_job_id = prod_job_id
    else:
      self.prod_job_id = self.workflow_commons['JOB_ID']

    if 'JOBID' in os.environ:
      self.jobID = os.environ['JOBID']

    if wms_job_id:
      self.jobID = wms_job_id

    if workflowStatus:
      self.workflowStatus = workflowStatus

    if stepStatus:
      self.stepStatus = stepStatus

    if wf_commons:
      self.workflow_commons = wf_commons

    if step_commons:
      self.step_commons = step_commons

    if step_number:
      self.step_number = step_number
    else:
      self.step_number = self.STEP_NUMBER  # pylint: disable=no-member

    if step_id:
      self.step_id = step_id
    else:
      self.step_id = '%s_%s_%s' % (self.production_id, self.prod_job_id, self.step_number)

    self.siteName = siteName()

  #############################################################################

  def finalize(self, version=None):
    """ Just finalizing
    """

    self.log.flushAllMessages(0)

    if version:
      self.log.info('===== Terminating ' + version + ' ===== ')

  #############################################################################

  def setApplicationStatus(self, status, sendFlag=True):
    """Wraps around setJobApplicationStatus of state update client
    """
    if not self._WMSJob():
      return 0  # e.g. running locally prior to submission

    if self._checkWFAndStepStatus(noPrint=True):
      # The application status won't be updated in case the workflow or the step is failed already
      if not isinstance(status, str):
        status = str(status)
      self.log.verbose('setJobApplicationStatus(%d, %s)' % (self.jobID, status))
      jobStatus = self.jobReport.setApplicationStatus(status, sendFlag)
      if not jobStatus['OK']:
        self.log.warn(jobStatus['Message'])

  #############################################################################

  def setJobParameter(self, name, value, sendFlag=True):
    """Wraps around setJobParameter of state update client
    """
    if not self._WMSJob():
      return 0  # e.g. running locally prior to submission

    self.log.verbose('setJobParameter(%d,%s,%s)' % (self.jobID, name, value))

    jobParam = self.jobReport.setJobParameter(str(name), str(value), sendFlag)
    if not jobParam['OK']:
      self.log.warn(jobParam['Message'])

  #############################################################################

  def _resolveInputVariables(self):
    """ By convention the module input parameters are resolved here.
    """

    self.log.verbose("workflow_commons = ", self.workflow_commons)
    self.log.verbose("step_commons = ", self.step_commons)

    self.fileReport = self._getFileReporter()
    self.jobReport = self._getJobReporter()
    self.request = self._getRequestContainer()

    self.__resolveInputWorkflow()

  #############################################################################

  def __resolveInputWorkflow(self):
    """ Resolve the input variables that are in the workflow_commons
    """

    self.runNumber = self.workflow_commons.get('runNumber', self.runNumber)

    self.persistency = self.workflow_commons.get('persistency', self.persistency)

    self.jobType = self.workflow_commons.get('JobType', self.jobType)

    self.poolXMLCatName = self.workflow_commons.get('poolXMLCatName', self.poolXMLCatName)

    self.InputData = self.workflow_commons.get('InputData', self.InputData)

    if 'ParametricInputData' in self.workflow_commons:
      pID = copy.deepcopy(self.workflow_commons['ParametricInputData'])
      if pID:
        if isinstance(pID, list):
          pID = ';'.join(pID)
  #      self.InputData += ';' + pID
        self.InputData = pID
        self.InputData = self.InputData.rstrip(';')

    if self.InputData == ';':
      self.InputData = ''

    self.inputDataList = [lfn.strip('LFN:') for lfn in self.InputData.split(';') if lfn]

    # only required until the stripping is the same for MC / data
    self.bkConfigName = self.workflow_commons.get('configName', self.bkConfigName)

    self.simDescription = self.workflow_commons.get('simDescription', self.simDescription)

    if 'runMetadata' in self.workflow_commons:
      runMetadataDict = eval(self.workflow_commons['runMetadata'])
      self.onlineDDDBTag = runMetadataDict['DDDB']
      self.onlineCondDBTag = runMetadataDict['CondDb']
      self.TCK = runMetadataDict['TCK']

    if 'outputDataFileMask' in self.workflow_commons:
      self.outputDataFileMask = self.workflow_commons['outputDataFileMask']
      if not isinstance(self.outputDataFileMask, list):
        self.outputDataFileMask = [i.lower().strip() for i in self.outputDataFileMask.split(';')]

    self.gaudiSteps = self.workflow_commons.get('gaudiSteps', self.gaudiSteps)

    if 'CPUe' in self.workflow_commons:
      self.CPUe = int(round(float(self.workflow_commons['CPUe'])))

    self.multicoreJob = self.workflow_commons.get('multicore', self.multicoreJob)

    self.processingPass = self.workflow_commons.get('processingPass', self.processingPass)

    self.logFilePath = self.workflow_commons.get('LogFilePath', self.logFilePath)
    if isinstance(self.logFilePath, list):
      self.logFilePath = self.logFilePath[0]
    else:
      if 'PRODUCTION_ID' and 'JOB_ID' and 'configVersion' and 'configName' in self.workflow_commons:
        self.log.info('LogFilePath parameter not found, creating on the fly')
        result = getLogPath(self.workflow_commons, self.bkClient)
        if not result['OK']:
          self.log.error('Could not create LogFilePath', result['Message'])
          raise RuntimeError(result['Message'])
        self.logFilePath = result['Value']['LogFilePath'][0]

    if 'maxNumberOfEvents' in self.workflow_commons:
      self.maxNumberOfEvents = int(self.workflow_commons['maxNumberOfEvents'])

    self.eventType = self.workflow_commons.get('eventType', self.eventType)

    self.numberOfEvents = int(self.workflow_commons.get('numberOfEvents', self.numberOfEvents))

    if 'outputSEs' in self.workflow_commons:
      self.outputSEs = self.workflow_commons['outputSEs']
    else:
      # this is here for backward compatibility
      histogramSE = self.opsH.getValue('Productions/HistogramSE', 'CERN-HIST')
      histoTypes = self.opsH.getValue('Productions/HistogramTypes', ['HIST', 'BRUNELHIST', 'DAVINCIHIST',
                                                                     'GAUSSHIST'])
      self.outputSEs = dict((ht, histogramSE) for ht in histoTypes)
    # for older productions we construct it based on what should be found in the steps
    if 'listoutput' in self.step_commons:
      listOutputStep = self.step_commons['listoutput']
      for lOutput in listOutputStep:
        try:
          for outputDataType in lOutput['outputDataType'].split(';'):
            if outputDataType:
              self.outputSEs.setdefault(outputDataType.upper(), lOutput['outputDataSE'])
        except KeyError:
          continue
        self.workflow_commons['outputSEs'] = self.outputSEs

  #############################################################################

  def _resolveInputStep(self):
    """ Resolve the input variables for an application step
    """

    prodID = self.workflow_commons.get('PRODUCTION_ID', '')
    jobID = self.workflow_commons.get('JOB_ID', '')
    stepInstanceNumber = self.step_commons.get('STEP_NUMBER', '')

    self.stepName = self.step_commons['STEP_INSTANCE_NAME']

    self.executable = self.step_commons.get('executable', self.executable)

    self.applicationName = self.step_commons.get('applicationName', self.applicationName)

    self.applicationVersion = self.step_commons.get('applicationVersion', self.applicationVersion)

    self.BKstepID = self.step_commons.get('BKStepID', self.BKstepID)

    self.stepProcPass = self.step_commons.get('StepProcPass', self.stepProcPass)

    # this is only for production jobs and for application steps
    if prodID and jobID and stepInstanceNumber and 'listoutput' in self.step_commons:
      self.outputFilePrefix = "%s_%s_%s" % (prodID, jobID, stepInstanceNumber)
      self.applicationLog = self.applicationName + '_' + self.outputFilePrefix + '.log'
      self.XMLSummary = 'summary' + self.applicationName + '_' + self.outputFilePrefix + '.xml'
      self.histoName = self.applicationName + '_' + self.outputFilePrefix + '.Hist.root'

      for fileTypeDict in self.step_commons['listoutput']:  # this is a dict like {'outputDataType': 'sim'}
        # for non histo-merging prods
        if 'hist' in fileTypeDict['outputDataType'].lower() and self.jobType.lower() != 'merge':
          # Watch out: this assumes that:
          # - 'hist' is always in the file type name
          # - merging jobs won't produce histograms
          # - the only merging jobs that produce output types with hist are histomerging productions
          if 'outputDataName' not in fileTypeDict:
            fileTypeDict['outputDataName'] = self.histoName
        else:
          fileTypeDict['outputDataName'] = self.outputFilePrefix + '.' + fileTypeDict['outputDataType']
    else:
      self.applicationLog = self.step_commons.get('applicationLog', self.applicationLog)

    self.inputDataType = self.step_commons.get('inputDataType', self.inputDataType)

    self.applicationType = self.step_commons.get('applicationType', self.applicationType)

    self.optionsFile = self.step_commons.get('optionsFile', self.optionsFile)

    self.optionsLine = self.step_commons.get('optionsLine', self.optionsLine)

    self.extraOptionsLine = self.step_commons.get('extraOptionsLine', self.extraOptionsLine)

    if 'runTimeProjectName' in self.step_commons:
      self.runTimeProjectName = self.step_commons['runTimeProjectName']
      self.runTimeProjectVersion = self.step_commons['runTimeProjectVersion']

    if 'extraPackages' in self.step_commons:
      self.extraPackages = self.step_commons['extraPackages']
      if self.extraPackages:
        if isinstance(self.extraPackages, basestring):
          eps = self.extraPackages.split(';')  # pylint: disable=no-member
          epList = []
          for ep in eps:
            epList.append(tuple(ep.split('.')))
          self.extraPackages = epList

    stepInputData = []
    if 'inputData' in self.step_commons:
      if self.step_commons['inputData']:
        stepInputData = self.step_commons['inputData']
    elif self.InputData:
      stepInputData = copy.deepcopy(self.InputData)
    if stepInputData:
      stepInputData = self._determineStepInputData(stepInputData, )
      self.stepInputData = [sid.strip('LFN:') for sid in stepInputData]

    self.optionsFormat = self.step_commons.get('optionsFormat', self.optionsFormat)

    self.multicoreStep = self.step_commons.get('multiCore', self.multicoreStep)

    self.systemConfig = self.step_commons.get('SystemConfig', self.systemConfig)

    self.mcTCK = self.step_commons.get('mcTCK', self.mcTCK)

    self.DDDBTag = self.step_commons.get('DDDBTag', self.DDDBTag)

    self.condDBTag = self.step_commons.get('CondDBTag', self.condDBTag)

    self.dqTag = self.step_commons.get('DQTag', self.dqTag)

    # This is resolved also in __resolveInputWorkflow but can be specialized per step (e.g. LHCbJob().setApplication())
    self.numberOfEvents = int(self.step_commons.get('numberOfEvents', self.numberOfEvents))

  #############################################################################

  def _determineOutputs(self):
    """ Method that determines the correct outputs.

        For merging jobs the output has normally to be the same as the input, but there might be exceptions
        (like for the productions for the merging of histograms)
        For the others, we use what is in the step definition

        We always remove the 'HIST'(s), when present, from the list of output file types
        as these are treated differently.

        There is  anyway also here the special case of histogram merging productions.
    """

    histoTypes = self.opsH.getValue('Productions/HistogramTypes', ['HIST', 'BRUNELHIST', 'DAVINCIHIST', 'GAUSSHIST'])

    stepOutputs = self.step_commons['listoutput']
    stepOutputsT = [x['outputDataType'] for x in stepOutputs]
    stepOutTypes = []
    for fts in stepOutputsT:
      for ft in fts.split(';'):
        if ft and ft not in stepOutTypes:
          stepOutTypes.append(ft.lower())

    # corrections for Merge productions
    if self.jobType.lower() in ('merge', 'histomerge'):
      if len(stepOutTypes) == 1 and stepOutTypes[0] in [hts.lower() for hts in histoTypes] + ['root']:
        # If it is root/histo/ntuple merging job, we treat it almost as a stripping job
        pass
      else:
        res = self.bkClient.getFileMetadata(self.stepInputData)
        if not res['OK']:
          raise RuntimeError(res['Message'])

        outputTypes = set()
        success = res['Value']['Successful']
        for mdDict in success.values():
          outputTypes.add(mdDict['FileType'])
        if len(success) != len(self.stepInputData):
          self.log.warn("Some inputs are not in BKK, trying to parse the file names")
          for sid in set(self.stepInputData) - set(success):
            # File types in the BK are upper case
            fType = '.'.join(os.path.basename(sid).split('.')[1:]).upper()
            outputTypes.add(fType)

        outputTypes = list(outputTypes)
        if len(outputTypes) > 1:
          raise ValueError("Not all input files have the same type (%s)" % ','.join(outputTypes))
        outputType = outputTypes[0].lower()
        stepOutTypes = [outputType.lower()]
        stepOutputs = [{'outputDataName': self.step_id + '.' + outputType.lower(),
                        'outputDataType': outputType.lower(),
                        'outputBKType': outputType.upper()}]

    histogram = False

    # first here treating the special case of root/histo/ntuple merging jobs
    if self.jobType.lower() in ('merge', 'histomerge') \
            and len(stepOutTypes) == 1 \
            and stepOutTypes[0] in [hts.lower() for hts in histoTypes] + ['root']:
      pass
    else:  # This is not a histogram merging production, so we remove the histograms from the step outputs
      for hist in histoTypes:
        try:
          stepOutTypes.remove(hist)
          histogram = True
        except ValueError:
          pass
        try:
          stepOutTypes.remove(hist.lower())
          histogram = True
        except ValueError:
          pass

    return stepOutputs, stepOutTypes, histogram

  #############################################################################

  def _getJobReporter(self):
    """ just return the job reporter (object, always defined by dirac-jobexec)
    """

    if 'JobReport' in self.workflow_commons:
      return self.workflow_commons['JobReport']
    jobReport = JobReport(self.jobID)
    self.workflow_commons['JobReport'] = jobReport
    return jobReport

  #############################################################################

  def _getFileReporter(self):
    """ just return the file reporter (object)
    """

    if 'FileReport' in self.workflow_commons:
      return self.workflow_commons['FileReport']
    fileReport = FileReport()
    self.workflow_commons['FileReport'] = fileReport
    return fileReport

  #############################################################################

  def _getRequestContainer(self):
    """ just return the Request reporter (object)
    """

    if 'Request' in self.workflow_commons:
      return self.workflow_commons['Request']
    request = Request()
    self.workflow_commons['Request'] = request
    return request

  #############################################################################

  def getCandidateFiles(self, outputList, outputLFNs, fileMask='', stepMask=''):
    """ Returns list of candidate files to upload, check if some outputs are missing.

        :param list outputList: list of outputs with the following structure::
            [{'outputDataType': '', 'outputDataName': ''} , {...}]
        :param list outputLFNs: output LFNs for the job
        :param str fileMask: the output file extensions to restrict the outputs to. Can also be a list of strings
        :param str stepMask: the step ID to restrict the outputs to. Can also be a list of strings.

        :returns: dictionary containing type, SE and LFN for files restricted by mask
    """
    fileInfo = {}

    for outputFile in outputList:
      if 'outputDataType' in outputFile and 'outputDataName' in outputFile:
        fname = outputFile['outputDataName']
        fileType = outputFile['outputDataType']
        fileInfo[fname] = {'type': fileType}
      else:
        self.log.error('Ignoring mal-formed output data specification', str(outputFile))

    for lfn in outputLFNs:
      if os.path.basename(lfn).lower() in list(fi.lower() for fi in fileInfo):
        try:
          fileInfo[os.path.basename(lfn)]['lfn'] = lfn
          self.log.verbose("Found LFN for file",
                           "%s -> %s" % (lfn, os.path.basename(lfn)))
        except KeyError:
          fileInfo[os.path.basename(lfn).lower()]['lfn'] = lfn
          self.log.verbose("Found LFN for file",
                           "%s -> %s" % (lfn, os.path.basename(lfn).lower()))
      elif os.path.basename(lfn).split('_')[-1].lower() in list(fi.lower() for fi in fileInfo):
        try:
          fileInfo[os.path.basename(lfn).split('_')[-1]]['lfn'] = lfn
          self.log.verbose("Found LFN for file",
                           " %s -> %s" % (lfn, os.path.basename(lfn).split('_')[-1]))
        except KeyError:
          fileInfo[os.path.basename(lfn).split('_')[-1].lower()]['lfn'] = lfn
          self.log.verbose("Found LFN for file",
                           " %s -> %s" % (lfn, os.path.basename(lfn).split('_')[-1].lower()))
      else:
        self.log.warn("LFN not recognized", "for LFN %s" % lfn)

    # check local existance
    fileList = self._checkLocalExistance(list(fileInfo.keys()))
    # really horrible stuff for updating the name with what's found on the disk
    # (because maybe the case is not the same as the expected)
    newFileInfo = {}
    for fi in fileInfo.iteritems():
      for li in fileList:
        if fi[0].lower() == li.lower():
          newFileInfo[li] = fi[1]

    # Select which files have to be uploaded: in principle all
    candidateFiles = self._applyMask(newFileInfo, fileMask, stepMask)

    # Sanity check all final candidate metadata keys are present
    self._checkSanity(candidateFiles)

    # Adding the SEs
    for candidateFile in candidateFiles:
      try:
        fType = candidateFiles[candidateFile]['type']
        for fType in [fType, fType.lower(), fType.upper(), fType.capitalize()]:
          try:
            wfSE = self.outputSEs[fType]
            candidateFiles[candidateFile]['workflowSE'] = wfSE
            break
          except KeyError:
            continue
      except AttributeError:
        break

    return candidateFiles

  #############################################################################

  def _checkLocalExistance(self, fileList):
    """ Check that the list of output files are present locally
    """

    notPresentFiles = []

    filesOnDisk = set(os.listdir('.'))
    diff = set(fileList).difference(filesOnDisk)
    if diff:
      self.log.warn("Not all files found",
                    "set of what's on the disk: %s" % filesOnDisk)
      self.log.warn("Looking for files with different case")
      diffCI = set(fx.lower() for fx in fileList).difference(set(fod.lower() for fod in filesOnDisk))
      if diffCI:
        self.log.error("Output data not found",
                       "File list %s does not exist locally" % notPresentFiles)
        raise os.error("Output data not found")

      # now checking what's the actual filename case that's written
      self.log.warn("Found files with filename with different case, returning those")
      filesActuallyOnDisk = []
      for fod in os.listdir('.'):
        if fod.lower() in [fl.lower() for fl in fileList]:
          filesActuallyOnDisk.append(fod)
      return filesActuallyOnDisk

    return fileList

  #############################################################################

  def _applyMask(self, candidateFilesIn, fileMask, stepMask):
    """ Select which files have to be uploaded: in principle all

        :param dict candidateFilesIn: dictionary like
          {'00012345_00012345_4.dst': {'lfn': '/lhcb/MC/2010/DST/123/123_45_4.dst',
                                       type': 'dst'},
           '00012345_00012345_2.digi': {'type': 'digi'}}
        :param str fileMask: the output file extensions to restrict the outputs to. Can also be a list of strings
        :param str stepMask: the step ID to restrict the outputs to. Can also be a list of strings.

        :returns: a dict like the one in candidateFilesIn
    """
    candidateFiles = copy.deepcopy(candidateFilesIn)

    if fileMask and not isinstance(fileMask, list):
      fileMask = [fileMask]
    if isinstance(stepMask, int):
      stepMask = str(stepMask)
    if stepMask and not isinstance(stepMask, list):
      stepMask = [stepMask]

    if fileMask and fileMask != ['']:
      for fileName, metadata in list(candidateFiles.iteritems()):
        if metadata['type'].lower() not in [fm.lower() for fm in fileMask]:
          del candidateFiles[fileName]
          self.log.info('Output file %s was produced but will not be treated (fileMask is %s)' % (fileName,
                                                                                                  ', '.join(fileMask)))
    else:
      self.log.info('No outputDataFileMask provided, the files with all the extensions will be considered')

    if stepMask and stepMask != ['']:
      for fileName, metadata in list(candidateFiles.iteritems()):
        if fileName.lower().replace(metadata['type'].lower(), '').split('_')[-1].split('.')[0] not in stepMask:
          del candidateFiles[fileName]
          self.log.info('Output file %s was produced but will not be treated (stepMask is %s)' % (fileName,
                                                                                                  ', '.join(stepMask)))
    else:
      self.log.info('No outputDataStep provided, the files output of all the steps will be considered')

    return candidateFiles

  #############################################################################

  def _checkSanity(self, candidateFiles):
    """ Sanity check all final candidate metadata keys are present

        :param dict candidateFiles: dictionary like
          {'00012345_00012345_4.dst': {'lfn': '/lhcb/MC/2010/DST/123/123_45_4.dst',
                                       type': 'dst'},
           '00012345_00012345_2.digi': {'type': 'digi'}}

        :returns: None or raises ValueError
    """

    notPresentKeys = []

    mandatoryKeys = ['type', 'lfn']  # filedict is used for requests
    for fileName, metadata in candidateFiles.iteritems():
      for key in mandatoryKeys:
        if key not in metadata:
          notPresentKeys.append((fileName, key))

    if notPresentKeys:
      for fileName_keys in notPresentKeys:
        self.log.error("File %s has missing %s" % (fileName_keys[0], fileName_keys[1]))
      raise ValueError("Missing requested fileName keys")

  #############################################################################

  def getFileMetadata(self, candidateFiles):
    """ Returns the candidate file dictionary with associated metadata.

        The input candidate files dictionary has the structure:
        {'foo_1.txt': {'lfn': '/lhcb/MC/2010/DST/00012345/0001/foo_1.txt',
                       'type': 'txt',
                       'workflowSE': SE1},
        'bar_2.py': {'lfn': '/lhcb/MC/2010/DST/00012345/0001/bar_2.py',
                     'type': 'py',
                     'workflowSE': 'SE2'},
        }

        this also assumes the files are in the current working directory.
    """
    # Retrieve the POOL File GUID(s) for any final output files
    self.log.info('Will search for POOL GUIDs for: %s' % (', '.join(candidateFiles.keys())))
    pfnGUID = getGUID(candidateFiles.keys())
    if not pfnGUID['OK']:
      self.log.error('''PoolXMLFile failed to determine POOL GUID(s) for output file list,
      these will be generated by the DataManager''', pfnGUID['Message'])
      for fileName in candidateFiles.keys():
        candidateFiles[fileName]['guid'] = ''
    elif pfnGUID['generated']:
      self.log.warn('PoolXMLFile generated GUID(s) for the following files ', ', '.join(pfnGUID['generated']))
    else:
      self.log.info('GUIDs found for all specified POOL files: %s' % (', '.join(candidateFiles.keys())))

    for pfn, guid in pfnGUID['Value'].iteritems():
      candidateFiles[pfn]['guid'] = guid

    # Get all additional metadata about the file necessary for requests
    final = {}
    for fileName, metadata in candidateFiles.iteritems():
      fileDict = {}
      fileDict['LFN'] = metadata['lfn']
      fileDict['Size'] = os.path.getsize(fileName)
      fileDict['Checksum'] = fileAdler(fileName)
      fileDict['ChecksumType'] = 'ADLER32'
      fileDict['GUID'] = metadata['guid']
      fileDict['Status'] = 'Waiting'

      final[fileName] = metadata
      final[fileName]['filedict'] = fileDict
      final[fileName]['localpath'] = '%s/%s' % (os.getcwd(), fileName)

    # Sanity check all final candidate metadata keys are present (return S_ERROR if not)
    mandatoryKeys = ['guid', 'filedict']  # filedict is used for requests (this method adds guid and filedict)
    for fileName, metadata in final.iteritems():
      for key in mandatoryKeys:
        if key not in metadata:
          raise RuntimeError("File %s has missing %s" % (fileName, key))

    return final

  #############################################################################

  def _determineStepInputData(self, inputData):
    """ determine the input data for the step
    """
    if inputData == 'previousStep':
      stepIndex = self.gaudiSteps.index(self.stepName)
      previousStep = self.gaudiSteps[stepIndex - 1]

      stepInputData = []
      for outputF in self.workflow_commons['outputList']:
        try:
          if outputF['stepName'] == previousStep and outputF['outputBKType'].lower() == self.inputDataType.lower():
            stepInputData.append(outputF['outputDataName'])
        except KeyError:
          raise RuntimeError("Can't find output of step %s" % previousStep)

      return stepInputData

    return [x.strip('LFN:') for x in inputData.split(';')]

  #############################################################################

  def _manageAppOutput(self, outputs):
    """ Calls self._findOutputs to find what's produced, then creates the LFNs

        outputs, as called here, is created starting from step_commons['listoutput'],
        but enriched with at least the outputDataName.

        example of outputs:
        [{'outputDataType': 'bhadron.dst', 'outputBKType': 'BHADRON.DST',
          'outputDataName': '00012345_00012345_2.BHADRON.DST'},
         {'outputDataType': 'calibration.dst','outputDataType': 'CALIBRATION.DST',
          'outputDataName': '00012345_00012345_2.CALIBRATION.DST'}]

        :params list outputs: list of dicts of step output files descriptions
    """

    if not outputs:
      self.log.warn('Step outputs are not defined (normal user jobs. Not normal in productions and SAM jobs)')
      return
    else:
      finalOutputs, _bkFileTypes = self._findOutputs(outputs)

    self.log.info('Final step outputs are: %s' % (finalOutputs))
    self.step_commons['listoutput'] = finalOutputs

    if 'outputList' in self.workflow_commons:
      for outFile in finalOutputs:
        if outFile not in self.workflow_commons['outputList']:
          self.workflow_commons['outputList'].append(outFile)
    else:
      self.workflow_commons['outputList'] = finalOutputs

    if 'PRODUCTION_ID' and 'JOB_ID' and 'configVersion' and 'configName' in self.workflow_commons:
      self.log.info('Attempting to recreate the production output LFNs...')
      result = constructProductionLFNs(self.workflow_commons, self.bkClient)
      if not result['OK']:
        raise IOError("Could not create production LFNs: %s" % result['Message'])
      self.workflow_commons['BookkeepingLFNs'] = result['Value']['BookkeepingLFNs']
      self.workflow_commons['LogFilePath'] = result['Value']['LogFilePath']
      self.workflow_commons['ProductionOutputData'] = result['Value']['ProductionOutputData']

  #############################################################################

  def _findOutputs(self, stepOutput):
    """ Find which outputs of those in stepOutput (what are expected to be produced) are effectively produced.
        stepOutput, as called here, is created starting from step_commons['listoutput']

        example of stepOutput:
        [{'outputDataType': 'bhadron.dst', 'outputBKType': 'BHADRON.DST',
          'outputDataName': '00012345_00012345_2.BHADRON.DST'},
         {'outputDataType': 'calibration.dst','outputDataType': 'CALIBRATION.DST',
          'outputDataName': '00012345_00012345_2.CALIBRATION.DST'}]

        :params list stepOutput: list of dicts of step output files descriptions
        :returns: list, list
    """

    bkFileTypes = []  # uppercase list of file types
    finalOutputs = []  # list of dicts of what's found on the local disk

    filesFound = []

    for output in stepOutput:

      found = False
      fileOnDisk = None

      for fileOnDisk in os.listdir('.'):
        if output['outputDataName'].lower() == fileOnDisk.lower():
          found = True
          break

      if found and fileOnDisk:
        self.log.info('Found output file (case is not considered)',
                      '%s matching %s ' % (fileOnDisk, output['outputDataName']))
        output['outputDataName'] = fileOnDisk
        filesFound.append(output)
      else:
        self.log.error('Output not found', output['outputDataName'])
        raise IOError("OutputData not found")

    for fileFound in filesFound:
      bkFileTypes.append(fileFound['outputDataType'].upper())
      finalOutputs.append({'outputDataName': fileFound['outputDataName'],
                           'outputDataType': fileFound['outputDataType'].lower(),
                           'outputBKType': fileFound['outputDataType'].upper(),
                           'stepName': self.stepName})

    return (finalOutputs, bkFileTypes)

  #############################################################################

  def _WMSJob(self):
    """ Check if this job is running via WMS
    """
    return True if self.jobID else False

  #############################################################################

  def _enableModule(self):
    """ Enable module if it's running via WMS
    """
    if not self._WMSJob():
      self.log.info('No WMS JobID found, disabling module via control flag')
      return False
    self.log.verbose('Found WMS JobID', self.jobID)
    return True

  #############################################################################

  def _checkWFAndStepStatus(self, noPrint=False):
    """ Check the WF and Step status
    """
    if not self.workflowStatus['OK'] or not self.stepStatus['OK']:
      if not noPrint:
        self.log.info('Skip this module, failure detected in a previous step')
        self.log.info('Workflow status', self.workflowStatus)
        self.log.info('Step Status', self.stepStatus)
      return False
    else:
      return True

  #############################################################################

  def _disableWatchdogCPUCheck(self):
    """ just writes a file to disable the watchdog
    """
    self.log.info("Creating DISABLE_WATCHDOG_CPU_WALLCLOCK_CHECK in order to disable the Watchdog")
    with open('DISABLE_WATCHDOG_CPU_WALLCLOCK_CHECK', 'w') as fopen:
      fopen.write('%s' % time.asctime())

  #############################################################################

  def generateFailoverFile(self):
    """ Retrieve the accumulated reporting request, and produce a JSON file that is consumed by the JobWrapper
    """
    reportRequest = None
    result = self.jobReport.generateForwardDISET()
    if not result['OK']:
      self.log.warn("Could not generate Operation for job report",
                    "with result:\n%s" % (result))
    else:
      reportRequest = result['Value']
    if reportRequest:
      self.log.info("Populating request with job report information")
      self.request.addOperation(reportRequest)

    if len(self.request):
      try:
        optimized = self.request.optimize()
      except AttributeError:
        optimized = {'OK': True}
      if not optimized['OK']:
        self.log.error("Could not optimize", optimized['Message'])
        self.log.error("Not failing the job because of that, keep going")
      isValid = self.requestValidator.validate(self.request)
      if not isValid['OK']:
        raise RuntimeError("Failover request is not valid: %s" % isValid['Message'])
      else:
        requestJSON = self.request.toJSON()
        if requestJSON['OK']:
          self.log.info("Creating failover request for deferred operations",
                        "for job %d" % self.jobID)
          request_string = str(requestJSON['Value'])
          self.log.debug(request_string)
          # Write out the request string
          fname = '%s_%s_request.json' % (self.production_id, self.prod_job_id)
          with open(fname, 'w') as jsonFile:
            jsonFile.write(request_string)
          self.log.info("Created file containing failover request", fname)
          result = self.request.getDigest()
          if result['OK']:
            self.log.info("Digest of the request", result['Value'])
          else:
            self.log.warn("No digest? That's not sooo important",
                          "anyway: %s" % result['Message'])
        else:
          raise RuntimeError(requestJSON['Message'])

    accountingReport = None
    if 'AccountingReport' in self.workflow_commons:
      accountingReport = self.workflow_commons['AccountingReport']
    if accountingReport:
      result = accountingReport.commit()
      if not result['OK']:
        self.log.error("!!! Both accounting and RequestDB are down? !!!")
        self.log.error("accountingReport result: %s" % result['Message'])
        self.log.error("Anyway, the job won't fail for this reason, because this is \"just\" the accounting report")

  #############################################################################

  def setBKRegistrationRequest(self, lfn, error='',
                               metaData={'Checksum': 'justSomething',
                                         'ChecksumType': 'ADLER32',
                                         'GUID': 'aGUID'}):
    """ Set a BK registration request for changing the replica flag.
        Uses the global request object (self.request).
    """
    if error:
      self.log.info('BK registration for %s failed with message: "%s" setting failover request' % (lfn, error))
    else:
      self.log.info('Setting BK registration request for %s' % (lfn))

    regFile = Operation()
    regFile.Type = 'RegisterFile'
    regFile.Catalog = 'BookkeepingDB'

    bkFile = File()
    bkFile.LFN = lfn
    # this should NOT be needed... but RMS complains!
    bkFile.PFN = lfn
    bkFile.GUID = metaData['GUID']
    bkFile.Checksum = metaData['Checksum']
    bkFile.ChecksumType = metaData['ChecksumType']

    regFile.addFile(bkFile)
    res = self.request.addOperation(regFile)
    if not res['OK']:
      raise RuntimeError(res['Message'])

  #############################################################################

  def createProdConfFile(self, stepOutputTypes, histogram, runNumberGauss, firstEventNumberGauss):
    """ Utility that creates a ProdConf file, used mostly as input for gaudirun jobs
    """
    # Creating ProdConf file
    prodConfFileName = 'prodConf_%s_%s_%s_%s.py' % (self.applicationName,
                                                    self.production_id,
                                                    self.prod_job_id,
                                                    self.step_number)
    optionsDict = {}

    optionsDict['Application'] = self.applicationName

    optionsDict['AppVersion'] = self.applicationVersion

    if self.optionsFormat:
      optionsDict['OptionFormat'] = self.optionsFormat

    if self.stepInputData:
      optionsDict['InputFiles'] = ['LFN:' + sid for sid in self.stepInputData]
    else:
      if self.applicationName.lower() != "gauss":
        raise RuntimeError("No MC, but no input data")

    if self.outputFilePrefix:
      optionsDict['OutputFilePrefix'] = self.outputFilePrefix

    optionsDict['OutputFileTypes'] = stepOutputTypes

    optionsDict['XMLSummaryFile'] = self.XMLSummary

    optionsDict['XMLFileCatalog'] = self.poolXMLCatName

    if histogram:
      optionsDict['HistogramFile'] = self.histoName

    if self.DDDBTag:
      if self.DDDBTag.lower() == 'online':
        try:
          optionsDict['DDDBTag'] = self.onlineDDDBTag
          self.log.debug('Set the online DDDB tag')
        except NameError as e:
          self.log.error('Could not find online DDDb Tag', e)
          raise RuntimeError("Could not find online DDDb Tag")
      else:
        optionsDict['DDDBTag'] = self.DDDBTag

    if self.condDBTag:
      if self.condDBTag.lower() == 'online':
        optionsDict['CondDBTag'] = self.onlineCondDBTag
        self.log.debug('Set the online CondDB tag')
      else:
        optionsDict['CondDBTag'] = self.condDBTag

    if self.dqTag:
      optionsDict['DQTag'] = self.dqTag

    if self.applicationName.lower() == 'gauss':
      if self.CPUe and self.maxNumberOfEvents and self.numberOfEvents <= 0:
        # Here we set maxCPUTime to 24 hours, which seems reasonable
        eventsToProduce = getEventsToProduce(self.CPUe,
                                             maxNumberOfEvents=self.maxNumberOfEvents,
                                             jobMaxCPUTime=86400)
      else:
        eventsToProduce = self.numberOfEvents
    else:
      eventsToProduce = self.numberOfEvents
    optionsDict['NOfEvents'] = eventsToProduce

    if runNumberGauss:
      optionsDict['RunNumber'] = runNumberGauss

    if self.runNumber:
      if self.runNumber not in ('Unknown', 'Multiple'):
        optionsDict['RunNumber'] = self.runNumber

    if firstEventNumberGauss:
      optionsDict['FirstEventNumber'] = firstEventNumberGauss

    # TCK: can't have both set!
    if self.TCK and self.mcTCK:
      raise RuntimeError("%s step: TCK set in step, and should't be!" % self.applicationName)
    if self.TCK or self.mcTCK:
      optionsDict['TCK'] = self.TCK if self.TCK else self.mcTCK

    if self.processingPass:
      optionsDict['ProcessingPass'] = self.processingPass

    prodConfFile = ProdConf(prodConfFileName)
    self.log.debug(optionsDict)
    prodConfFile.putOptionsIn(optionsDict)

    return prodConfFileName

  #############################################################################

  # properties
  def set_jobID(self, value):
    if isinstance(value, str):
      if value:
        value = int(value)
      else:
        value = 0
    self._jobID = value

  def get_jobID(self):
    return self._jobID
  jobID = property(get_jobID, set_jobID)
Ejemplo n.º 3
0
    def setInputData(self,
                     lfns,
                     bkClient=None,
                     runNumber=None,
                     persistencyType=None):
        """ Add the input data and the run number, if available
    """

        if not lfns:
            self.log.warn(
                "no lfns passed in setInputData, was that intentional?")
            return S_OK("Nothing to do")

        res = Job.setInputData(self, lfns)
        if not res['OK']:
            return res

        if not runNumber or not persistencyType:
            if not bkClient:
                bkClient = BookkeepingClient()

        if not runNumber:
            res = bkClient.getFileMetadata(lfns)
            if not res['OK']:
                return res

            runNumbers = []
            for fileMeta in res['Value']['Successful'].itervalues():
                try:
                    if fileMeta['RunNumber'] not in runNumbers and fileMeta[
                            'RunNumber'] is not None:
                        runNumbers.append(fileMeta['RunNumber'])
                except KeyError:
                    continue

            if len(runNumbers) > 1:
                runNumber = 'Multiple'
            elif len(runNumbers) == 1:
                runNumber = str(runNumbers[0])
            else:
                runNumber = 'Unknown'

        if not persistencyType:

            res = bkClient.getFileTypeVersion(lfns)
            if not res['OK']:
                return res

            typeVersions = res['Value']

            if not typeVersions:
                self.log.verbose('The requested files do not exist in the BKK')
                typeVersion = ''

            else:
                self.log.verbose('Found file types %s for LFNs: %s' %
                                 (typeVersions.values(), typeVersions.keys()))
                typeVersionsList = list(set(typeVersions.values()))
                if len(typeVersionsList) == 1:
                    typeVersion = typeVersionsList[0]
                else:
                    typeVersion = ''

        self._addParameter(self.workflow, 'runNumber', 'JDL', runNumber,
                           'Input run number')
        self._addParameter(self.workflow, 'persistency', 'String', typeVersion,
                           'Persistency type of the inputs')

        return S_OK()
Ejemplo n.º 4
0
            sep = '=====================================\n'
            if verbose:
                gLogger.always('For %s, SEs: %s' % (site, str(seList)))
            pbFound = False

            res = dm.getReplicas(inputData)
            if not res['OK']:
                gLogger.always(
                    "Error getting replicas for %d files" % len(inputData),
                    res['Message'])
                continue
            replicas = res['Value']['Successful']
            notInFC = res['Value']['Failed']
            if notInFC:
                # Check if files has replica flag in the FC, If not ignore the problem
                res = bk.getFileMetadata(notInFC.keys())
                if not res['OK']:
                    gLogger.always(
                        'Error getting BK metadata for %d files' %
                        len(notInFC), res['Message'])
                    continue
                metadata = res['Value']['Successful']
                notInFC = [
                    lfn for lfn in notInFC
                    if metadata.get(lfn, {}).get('GotReplica') == 'Yes'
                ]
                if notInFC:
                    pbFound = True
                    prettyMsg('not in the FC but in BK', notInFC)
            notFoundReplicas = replicas.keys()
            missingReplicas = []
Ejemplo n.º 5
0
class fakeClient:
    def __init__(self, trans, transID, lfns, asIfProd):
        self.trans = trans
        self.transID = transID
        from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient
        self.transClient = TransformationClient()
        from LHCbDIRAC.BookkeepingSystem.Client.BookkeepingClient import BookkeepingClient
        self.bk = BookkeepingClient()
        from DIRAC.DataManagementSystem.Client.DataManager import DataManager
        self.dm = DataManager()
        self.asIfProd = asIfProd

        (self.transFiles, self.transReplicas) = self.prepareForPlugin(lfns)

    def addFilesToTransformation(self, transID, lfns):
        return S_OK({
            'Failed': {},
            'Successful': dict([(lfn, 'Added') for lfn in lfns])
        })

    def getTransformation(self, transID, extraParams=False):
        if transID == self.transID and self.asIfProd:
            transID = self.asIfProd
        if transID != self.transID:
            return self.transClient.getTransformation(transID)
        res = self.trans.getType()
        return DIRAC.S_OK({'Type': res['Value']})

    def getReplicas(self):
        return self.transReplicas

    def getFiles(self):
        return self.transFiles

    def getCounters(self, table, attrList, condDict):
        if condDict['TransformationID'] == self.transID and self.asIfProd:
            condDict['TransformationID'] = self.asIfProd
        if condDict['TransformationID'] != self.transID:
            return self.transClient.getCounters(table, attrList, condDict)
        possibleTargets = [
            'CERN-RAW', 'CNAF-RAW', 'GRIDKA-RAW', 'IN2P3-RAW', 'SARA-RAW',
            'PIC-RAW', 'RAL-RAW', 'RRCKI-RAW'
        ]
        counters = []
        for se in possibleTargets:
            counters.append(({'UsedSE': se}, 0))
        return DIRAC.S_OK(counters)

    def getBookkeepingQuery(self, transID):
        if transID == self.transID and self.asIfProd:
            return self.transClient.getBookkeepingQuery(asIfProd)
        return self.trans.getBkQuery()

    def insertTransformationRun(self, transID, runID, xx):
        return DIRAC.S_OK()

    def getTransformationRuns(self, condDict):
        if condDict['TransformationID'] == self.transID and self.asIfProd:
            condDict['TransformationID'] = self.asIfProd
        if condDict['TransformationID'] == self.transID:
            transRuns = []
            runs = condDict.get('RunNumber', [])
            if not runs and self.transFiles:
                res = self.bk.getFileMetadata(
                    [fileDict['LFN'] for fileDict in self.transFiles])
                if not res['OK']:
                    return res
                runs = list(
                    set(meta['RunNumber']
                        for meta in res['Value']['Successful'].itervalues()))
            for run in runs:
                transRuns.append({
                    'RunNumber': run,
                    'Status': "Active",
                    "SelectedSite": None
                })
            return DIRAC.S_OK(transRuns)
        else:
            return self.transClient.getTransformationRuns(condDict)

    def getTransformationFiles(self, condDict=None):
        if condDict.get('TransformationID') == self.transID and self.asIfProd:
            condDict['TransformationID'] = self.asIfProd
        if condDict.get('TransformationID') == self.transID:
            transFiles = []
            if 'Status' in condDict and 'Unused' not in condDict['Status']:
                return DIRAC.S_OK(transFiles)
            runs = None
            if 'RunNumber' in condDict:
                runs = condDict['RunNumber']
                if not isinstance(runs, list):
                    runs = [runs]
            for fileDict in self.transFiles:
                if not runs or fileDict['RunNumber'] in runs:
                    transFiles.append({
                        'LFN': fileDict['LFN'],
                        'Status': 'Unused',
                        'RunNumber': fileDict['RunNumber']
                    })
            return DIRAC.S_OK(transFiles)
        else:
            return self.transClient.getTransformationFiles(condDict=condDict)

    def setParameterToTransformationFiles(self, transID, lfnDict):
        """
    Update the transFiles with some parameters
    """
        if transID == self.transID:
            for fileDict in self.transFiles:
                fileDict.update(lfnDict.get(fileDict['LFN'], {}))
            return S_OK()
        else:
            return self.transClient.setParameterToTransformationFiles(
                transID, lfnDict)

    def getTransformationFilesCount(self, transID, field, selection=None):
        if selection is None:
            selection = {}
        if transID == self.transID or selection.get(
                'TransformationID') == self.transID:
            runs = selection.get('RunNumber')
            if runs and not isinstance(runs, list):
                runs = [runs]
            if field == 'Status':
                counters = {'Unused': 0}
                for fileDict in self.transFiles:
                    if not runs or fileDict['RunNumber'] in runs:
                        counters['Unused'] += 1
            elif field == 'RunNumber':
                counters = {}
                for fileDict in self.transFiles:
                    runID = fileDict['RunNumber']
                    if not runs or runID in runs:
                        counters.setdefault(runID, 0)
                        counters[runID] += 1
            else:
                return DIRAC.S_ERROR('Not implemented for field ' + field)
            counters['Total'] = sum(count for count in counters.itervalues())
            return DIRAC.S_OK(counters)
        else:
            return self.transClient.getTransformationFilesCount(
                transID, field, selection=selection)

    def getTransformationRunStats(self, transIDs):
        counters = {}
        for transID in transIDs:
            if transID == self.transID:
                for fileDict in self.transFiles:
                    runID = fileDict['RunNumber']
                    counters[transID][runID]['Unused'] = counters.setdefault(
                        transID, {}).setdefault(runID, {}).setdefault(
                            'Unused', 0) + 1
                for runID in counters[transID]:
                    counters[transID][runID]['Total'] = counters[transID][
                        runID]['Unused']
            else:
                res = self.transClient.getTransformationRunStats(transIDs)
                if res['OK']:
                    counters.update(res['Value'])
                else:
                    return res
        return DIRAC.S_OK(counters)

    def addRunsMetadata(self, runID, val):
        return self.transClient.addRunsMetadata(runID, val)

    def getRunsMetadata(self, runID):
        return self.transClient.getRunsMetadata(runID)

    def setTransformationRunStatus(self, transID, runID, status):
        return DIRAC.S_OK()

    def setTransformationRunsSite(self, transID, runID, site):
        return DIRAC.S_OK()

    def setFileStatusForTransformation(self, transID, status, lfns):
        return DIRAC.S_OK()

    def addTransformationRunFiles(self, transID, run, lfns):
        return DIRAC.S_OK()

    def setDestinationForRun(self, runID, site):
        return DIRAC.S_OK()

    def getDestinationForRun(self, runID):
        return self.transClient.getDestinationForRun(runID)

    def prepareForPlugin(self, lfns):
        import time
        print "Preparing the plugin input data (%d files)" % len(lfns)
        type = self.trans.getType()['Value']
        if not lfns:
            return (None, None)
        res = self.bk.getFileMetadata(lfns)
        if res['OK']:
            files = []
            for lfn, metadata in res['Value']['Successful'].iteritems():
                runID = metadata.get('RunNumber', 0)
                runDict = {"RunNumber": runID, "LFN": lfn}
                files.append(runDict)
        else:
            print "Error getting BK metadata", res['Message']
            return ([], {})
        replicas = {}
        startTime = time.time()
        from DIRAC.Core.Utilities.List import breakListIntoChunks
        for lfnChunk in breakListIntoChunks(lfns, 200):
            # print lfnChunk
            if type.lower() in ("replication", "removal"):
                res = self.dm.getReplicas(lfnChunk, getUrl=False)
            else:
                res = self.dm.getReplicasForJobs(lfnChunk, getUrl=False)
            # print res
            if res['OK']:
                for lfn, ses in res['Value']['Successful'].iteritems():
                    if ses:
                        replicas[lfn] = sorted(ses)
            else:
                print "Error getting replicas of %d files:" % len(
                    lfns), res['Message']
        print "Obtained replicas of %d files in %.3f seconds" % (
            len(lfns), time.time() - startTime)
        return (files, replicas)
                if 'BadPFN' not in problematicFiles[lfn]:
                    problematicFiles[lfn]['BadPFN'] = []
                problematicFiles[lfn]['BadPFN'].append(se)
        elif lfn in res['Value']['Successful']:
            if verbose:
                fp.write("Replica is ok\n")
    fp.flush()

BkkChecks = False
print(
    "-------- Checks LFC -> Bkk ---------------------------------------------------------"
)
fp.write(
    "-------- Checks LFC -> Bkk ---------------------------------------------------------\n"
)
res = bkClient.getFileMetadata(LFNsInLFC)
if res['OK']:
    BkkChecks = True
    metadata = res['Value']['Successful']
    missingLFNs = [
        lfn for lfn in LFNsInLFC
        if metadata.get(lfn, {}).get('GotReplica') == None
    ]
    noFlagLFNs = [
        lfn for lfn in LFNsInLFC
        if metadata.get(lfn, {}).get('GotReplica') == 'No'
    ]
    okLFNs = [
        lfn for lfn in LFNsInLFC
        if metadata.get(lfn, {}).get('GotReplica') == 'Yes'
    ]
Ejemplo n.º 7
0
class DiracLHCb(Dirac):

  #############################################################################
  def __init__(self, withRepo=False, repoLocation='', operationsHelperIn=None):
    """Internal initialization of the DIRAC API.
    """

    super(DiracLHCb, self).__init__(withRepo=withRepo, repoLocation=repoLocation)
    self.tier1s = []

    if not operationsHelperIn:
      self.opsH = Operations()
    else:
      self.opsH = operationsHelperIn

    self._bkQueryTemplate = {'SimulationConditions': 'All',
                             'DataTakingConditions': 'All',
                             'ProcessingPass': '******',
                             'FileType': 'All',
                             'EventType': 'All',
                             'ConfigName': 'All',
                             'ConfigVersion': 'All',
                             'Production': 0,
                             'StartRun': 0,
                             'EndRun': 0,
                             'DataQuality': 'All',
                             'Visible': 'Yes'}
    self._bkClient = BookkeepingClient()  # to expose all BK client methods indirectly

  #############################################################################
  def addRootFile(self, lfn, fullPath, diracSE, printOutput=False):
    """ Add a Root file to Grid storage, an attempt is made to retrieve the
        POOL GUID of the file prior to upload.

       Example Usage:

       >>> print dirac.addFile('/lhcb/user/p/paterson/myRootFile.tar.gz','myFile.tar.gz','CERN-USER')
       {'OK': True, 'Value':{'Failed': {},
        'Successful': {'/lhcb/user/p/paterson/test/myRootFile.tar.gz': {'put': 64.246301889419556,
                                                                    'register': 1.1102778911590576}}}}

       @param lfn: Logical File Name (LFN)
       @type lfn: string
       @param diracSE: DIRAC SE name e.g. CERN-USER
       @type diracSE: strin
       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """
    return super(DiracLHCb, self).addFile(lfn, fullPath, diracSE,
                                          fileGuid=makeGuid(fullPath)[fullPath],
                                          printOutput=printOutput)

  def addFile(self, lfn, fullPath, diracSE, printOutput=False):  # pylint: disable=arguments-differ
    """ Copy of addRootFile
    """
    return super(DiracLHCb, self).addFile(lfn, fullPath, diracSE,
                                          fileGuid=makeGuid(fullPath)[fullPath],
                                          printOutput=printOutput)

  def getBKAncestors(self, lfns, depth=1, replica=True):
    """ This function allows to retrieve ancestor files from the Bookkeeping.

        Example Usage:

        >>> dirac.getBKAncestors('/lhcb/data/2009/DST/00005727/0000/00005727_00000042_1.dst',2)
        {'OK': True, 'Value': ['/lhcb/data/2009/DST/00005727/0000/00005727_00000042_1.dst',
        '/lhcb/data/2009/RAW/FULL/LHCb/COLLISION09/63807/063807_0000000004.raw']}

       @param lfn: Logical File Name (LFN)
       @type lfn: string or list
       @param depth: Ancestor depth
       @type depth: integer
    """

    result = self._bkClient.getFileAncestors(lfns, depth, replica=replica)
    if not result['OK']:
      self.log.error('Could not get ancestors', result['Message'])
      return result
    ancestors = set(x['FileName'] for ancestors in result['Value']['Successful'].itervalues() for x in ancestors)

    return S_OK(lfns + list(ancestors))

  #############################################################################
  def bkQueryRunsByDate(self, bkPath, startDate, endDate, dqFlag='All', selection='Runs'):
    """ This function allows to create and perform a BK query given a supplied
        BK path. The following BK path convention is expected:

        /<ConfigurationName>/<Configuration Version>/<Condition Description><Processing Pass>/<Event Type>/<File Type>

        so an example for 2016 collisions data would be:

        /LHCb/Collision09//LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real Data/Reco16/Stripping26/90000000/EW.DST

        The startDate and endDate must be specified as yyyy-mm-dd.

        Runs can be selected based on their status e.g. the selection parameter
        has the following possible attributes:
         - Runs - data for all runs in the range are queried (default)
         - ProcessedRuns - data is retrieved for runs that are processed
         - NotProcessed - data is retrieved for runs that are not yet processed.

       Example Usage:

       >>> dirac.bkQueryRunsByDate('/LHCb/Collision16//Real Data/90000000/RAW',
                                   '2016-08-20','2016-08-22',dqFlag='OK',selection='Runs')
       {'OK': True, 'Value': [<LFN1>,<LFN2>]}

      dirac.bkQueryRunsByDate('/LHCb/Collision16/Beam6500GeV-VeloClosed-MagDown/Real'
                              'Data/Reco16/Stripping26/90000000/EW.DST',
                              '2016-08-20','2016-08-22',dqFlag='OK',selection='Runs')

       @param bkPath: BK path as described above
       @type bkPath: string
       @param dqFlag: Optional Data Quality flag
       @type dqFlag: string
       @param startDate: Start date  yyyy-mm-dd
       @param startDate: string
       @param endDate: End date  yyyy-mm-dd
       @param endDate: string
       @param selection: Either Runs, ProcessedRuns or NotProcessed
       @param selection: string
       @return: S_OK,S_ERROR
    """
    runSelection = ['Runs', 'ProcessedRuns', 'NotProcessed']
    if selection not in runSelection:
      return S_ERROR('Expected one of %s not "%s" for selection' % (', '.join(runSelection), selection))

    if not isinstance(bkPath, str):
      return S_ERROR('Expected string for bkPath')

    # remove any double slashes, spaces must be preserved
    # remove any empty components from leading and trailing slashes
    bkQuery = BKQuery().buildBKQuery(bkPath)
    if not bkQuery:
      return S_ERROR(
          'Please provide a BK path: '
          '/<ConfigurationName>/<Configuration Version>/<Condition Description>/<Processing Pass>'
          '/<Event Type>/<File Type>')

    if not startDate or not endDate:
      return S_ERROR('Expected both start and end dates to be defined in format: yyyy-mm-dd')

    if not isinstance(startDate, str) or not isinstance(endDate, str):
      return S_ERROR('Expected yyyy-mm-dd string for start and end dates')

    if not len(startDate.split('-')) == 3 or not len(endDate.split('-')) == 3:
      return S_ERROR('Expected yyyy-mm-dd string for start and end dates')

    start = time.time()
    result = self._bkClient.getRunsForAGivenPeriod({'StartDate': startDate, 'EndDate': endDate})
    rtime = time.time() - start
    self.log.info('BK query time: %.2f sec' % rtime)
    if not result['OK']:
      self.log.info('Could not get runs with given dates from BK with result: "%s"' % result)
      return result

    if not result['Value']:
      self.log.info('No runs selected from BK for specified dates')
      return result

    if selection not in result['Value']:
      return S_ERROR('No %s runs for specified dates' % (selection))

    runs = result['Value'][selection]
    self.log.info('Found the following %s runs:\n%s' % (len(runs), ', '.join([str(i) for i in runs])))
    # temporary until we can query for a discrete list of runs
    selectedData = []
    for run in runs:
      query = bkQuery.copy()
      query['StartRun'] = run
      query['EndRun'] = run
      query['CheckRunStatus'] = True if selection in ['ProcessedRuns', 'NotProcessed'] else False
      if dqFlag:
        check = self.__checkDQFlags(dqFlag)
        if not check['OK']:
          return check
        dqFlag = check['Value']
        query['DataQuality'] = dqFlag
      start = time.time()
      result = self._bkClient.getVisibleFilesWithMetadata(query)
      rtime = time.time() - start
      self.log.info('BK query time: %.2f sec' % rtime)
      self.log.verbose(result)
      if not result['OK']:
        return result
      self.log.info('Selected %s files for run %s' % (len(result['Value']), run))
      if result['Value']['LFNs']:
        selectedData += result['Value']['LFNs'].keys()

    self.log.info('Total files selected = %s' % (len(selectedData)))
    return S_OK(selectedData)

  #############################################################################
  def bkQueryRun(self, bkPath, dqFlag='All'):
    """ This function allows to create and perform a BK query given a supplied
        BK path. The following BK path convention is expected:

        /<Run Number>/<Processing Pass>/<Event Type>/<File Type>

        so an example for 2009 collisions data would be:

       /63566/Real Data + RecoToDST-07/90000000/DST

       In addition users can specify a range of runs using the following convention:

       /<Run Number 1> - <Run Number 2>/<Processing Pass>/<Event Type>/<File Type>

       so extending the above example this would look like:

       /63566-63600/Real Data + RecoToDST-07/90000000/DST

       Example Usage:

       >>> dirac.bkQueryRun('/63566/Real Data/RecoToDST-07/90000000/DST')
       {'OK':True,'Value': ['/lhcb/data/2009/DST/00005842/0000/00005842_00000008_1.dst']}

       @param bkPath: BK path as described above
       @type bkPath: string
       @param dqFlag: Optional Data Quality flag
       @type dqFlag: string
       @return: S_OK,S_ERROR
    """
    if not isinstance(bkPath, str):
      return S_ERROR('Expected string for bkPath')

    # remove any double slashes, spaces must be preserved
    # remove any empty components from leading and trailing slashes
    bkPath = translateBKPath(bkPath, procPassID=1)
    if not len(bkPath) == 4:
      return S_ERROR('Expected 4 components to the BK path: /<Run Number>/<Processing Pass>/<Event Type>/<File Type>')

    runNumberString = bkPath[0].replace('--', '-').replace(' ', '')
    startRun = 0
    endRun = 0
    if '-' in runNumberString:
      runs = runNumberString.split('-')
      if len(runs) != 2:
        return S_ERROR('Could not determine run range from "%s", try "<Run 1> - <Run2>"' % (runNumberString))
      try:
        start = int(runs[0])
        end = int(runs[1])
      except Exception:
        return S_ERROR('Invalid run range: %s' % runNumberString)
      startRun = min(start, end)
      endRun = max(start, end)
    else:
      try:
        startRun = int(runNumberString)
        endRun = startRun
      except Exception:
        return S_ERROR('Invalid run number: %s' % runNumberString)

    query = self._bkQueryTemplate.copy()
    query['StartRun'] = startRun
    query['EndRun'] = endRun
    query['ProcessingPass'] = bkPath[1]
    query['EventType'] = bkPath[2]
    query['FileType'] = bkPath[3]

    if dqFlag:
      check = self.__checkDQFlags(dqFlag)
      if not check['OK']:
        return check
      dqFlag = check['Value']
      query['DataQuality'] = dqFlag

    result = self.bkQuery(query)
    self.log.verbose(result)
    return result

  #############################################################################
  def bkQueryProduction(self, bkPath, dqFlag='All'):
    """ This function allows to create and perform a BK query given a supplied
        BK path. The following BK path convention is expected:

        /<ProductionID>/[<Processing Pass>/<Event Type>/]<File Type>

        so an example for 2009 collisions data would be:

       /5842/Real Data + RecoToDST-07/90000000/DST

       Note that neither the processing pass nor the event type should be necessary. So either of them can be omitted

       a data quality flag can also optionally be provided, the full list of these is available
       via the getAllDQFlags() method.

       Example Usage:

       >>> dirac.bkQueryProduction('/5842/Real Data/RecoToDST-07/90000000/DST')
       {'OK': True, 'Value': [<LFN1>,<LFN2>]}

       @param bkPath: BK path as described above
       @type bkPath: string
       @param dqFlag: Optional Data Quality flag
       @type dqFlag: string
       @return: S_OK,S_ERROR
    """
    if not isinstance(bkPath, str):
      return S_ERROR('Expected string for bkPath')

    # remove any double slashes, spaces must be preserved
    # remove any empty components from leading and trailing slashes
    bkPath = translateBKPath(bkPath, procPassID=1)
    if len(bkPath) < 2:
      return S_ERROR('Invalid bkPath: should at least contain /ProductionID/FileType')
    query = self._bkQueryTemplate.copy()
    try:
      query['Production'] = int(bkPath[0])
    except Exception:
      return S_ERROR('Invalid production ID')
    query['FileType'] = bkPath[-1]

    if dqFlag:
      check = self.__checkDQFlags(dqFlag)
      if not check['OK']:
        return check
      dqFlag = check['Value']
      query['DataQuality'] = dqFlag

    for key, val in query.items():
      if isinstance(val, basestring) and val.lower() == 'all':
        query.pop(key)
    result = self.bkQuery(query)
    self.log.verbose(result)
    return result

  #############################################################################
  def bkQueryPath(self, bkPath, dqFlag='All'):
    """ This function allows to create and perform a BK query given a supplied
        BK path. The following BK path convention is expected:

       /<ConfigurationName>/<Configuration Version>/<Sim or Data Taking Condition>
       /<Processing Pass>/<Event Type>/<File Type>

       so an example for 2009 collsions data would be:

       /LHCb/Collision09/Beam450GeV-VeloOpen-MagDown/Real Data + RecoToDST-07/90000000/DST

       or for MC09 simulated data:

       /MC/2010/Beam3500GeV-VeloClosed-MagDown-Nu1/2010-Sim01Reco01-withTruth/27163001/DST

       a data quality flag can also optionally be provided, the full list of these is available
       via the getAllDQFlags() method.

       Example Usage:

       >>> dirac.bkQueryPath('/MC/2010/Beam3500GeV-VeloClosed-MagDown-Nu1/Sim07/Reco06-withTruth/10012004/DST')
       {'OK': True, 'Value': [<LFN1>,<LFN2>]}

       @param bkPath: BK path as described above
       @type bkPath: string
       @param dqFlag: Optional Data Quality flag
       @type dqFlag: string
       @return: S_OK,S_ERROR
    """
    if not isinstance(bkPath, str):
      return S_ERROR('Expected string for bkPath')

    # remove any double slashes, spaces must be preserved
    # remove any empty components from leading and trailing slashes
    bkPath = translateBKPath(bkPath, procPassID=3)
    if not len(bkPath) == 6:
      return S_ERROR('Expected 6 components to the BK path: '
                     '/<ConfigurationName>/<Configuration Version>/<Sim or Data Taking Condition>'
                     '/<Processing Pass>/<Event Type>/<File Type>')

    query = self._bkQueryTemplate.copy()
    query['ConfigName'] = bkPath[0]
    query['ConfigVersion'] = bkPath[1]
    query['ProcessingPass'] = bkPath[3]
    query['EventType'] = bkPath[4]
    query['FileType'] = bkPath[5]

    if dqFlag:
      check = self.__checkDQFlags(dqFlag)
      if not check['OK']:
        return check
      dqFlag = check['Value']
      query['DataQuality'] = dqFlag

    # The problem here is that we don't know if it's a sim or data taking condition,
    # assume that if configName=MC this is simulation
    if bkPath[0].lower() == 'mc':
      query['SimulationConditions'] = bkPath[2]
    else:
      query['DataTakingConditions'] = bkPath[2]

    result = self.bkQuery(query)
    self.log.verbose(result)
    return result

  #############################################################################
  def bookkeepingQuery(self, SimulationConditions='All', DataTakingConditions='All',
                       ProcessingPass='******', FileType='All', EventType='All', ConfigName='All',
                       ConfigVersion='All', ProductionID=0, DataQuality='ALL'):
    """ This function will create and perform a BK query using the supplied arguments
        and return a list of LFNs.

        Example Usage:

        >>> dirac.bookkeepingQuery(ConfigName='LHCb',ConfigVersion='Collision09',
        EventType='90000000',ProcessingPass='******',DataTakingConditions='Beam450GeV-VeloOpen-MagDown')
        {'OK':True,'Value':<files>}

       @param  ConfigName: BK ConfigName
       @type ConfigName: string
       @param  EventType: BK EventType
       @type EventType: string
       @param  FileType: BK FileType
       @type FileType: string
       @param  ProcessingPass: BK ProcessingPass
       @type ProcessingPass: string
       @param  ProductionID: BK ProductionID
       @type ProductionID: integer
       @param  DataQuality: BK DataQuality
       @type DataQuality: string
       @param  ConfigVersion: BK ConfigVersion
       @type ConfigVersion: string
       @param  DataTakingConditions: BK DataTakingConditions
       @type DataTakingConditions: string
       @param  SimulationConditions: BK SimulationConditions
       @type SimulationConditions: string
       @return: S_OK,S_ERROR
    """
    query = self._bkQueryTemplate.copy()
    query['SimulationConditions'] = SimulationConditions
    query['DataTakingConditions'] = DataTakingConditions
    query['ProcessingPass'] = ProcessingPass
    query['FileType'] = FileType
    query['EventType'] = EventType
    query['ConfigName'] = ConfigName
    query['ConfigVersion'] = ConfigVersion
    query['Production'] = ProductionID
    query['DataQuality'] = DataQuality
    return self.bkQuery(query)

  #############################################################################
  def bkQuery(self, bkQueryDict):
    """ Developer function. Perform a query to the LHCb Bookkeeping to return
        a list of LFN(s). This method takes a BK query dictionary.

        Example Usage:

        >>> print dirac.bkQuery(query)
        {'OK':True,'Value':<files>}

       @param bkQueryDict: BK query
       @type bkQueryDict: dictionary (see bookkeepingQuery() for keys)
       @return: S_OK,S_ERROR
    """
    problematicFields = []
    # Remove the Visible flag as anyway the method is for visible files ;-)
    # bkQueryDict.setdefault( 'Visible', 'Yes' )
    for name, value in bkQueryDict.items():
      if name not in self._bkQueryTemplate:
        problematicFields.append(name)

    if problematicFields:
      msg = 'The following fields are not valid for a BK query: %s\nValid fields include: %s' % \
            (', '.join(problematicFields), ', '.join(self._bkQueryTemplate.keys()))
      return S_ERROR(msg)

    for name, value in bkQueryDict.items():
      if name == "Production" or name == "EventType" or name == "StartRun" or name == "EndRun":
        if value == 0:
          del bkQueryDict[name]
        else:
          bkQueryDict[name] = str(value)
      elif name == "FileType":
        if value.lower() == "all":
          bkQueryDict[name] = 'ALL'
      else:
        if str(value).lower() == "all":
          del bkQueryDict[name]

    if 'Production' in bkQueryDict or 'StartRun' in bkQueryDict or 'EndRun' in bkQueryDict:
      self.log.verbose('Found a specific query so loosening some restrictions to prevent BK overloading')
    else:
      if 'SimulationConditions' not in bkQueryDict and 'DataTakingConditions' not in bkQueryDict:
        return S_ERROR('A Simulation or DataTaking Condition must be specified for a BK query.')
      if 'EventType' not in bkQueryDict and 'ConfigName' not in bkQueryDict and 'ConfigVersion' not in bkQueryDict:
        return S_ERROR(
            'The minimal set of BK fields for a query is: EventType, ConfigName and ConfigVersion'
            ' in addition to a Simulation or DataTaking Condition')

    self.log.verbose('Final BK query dictionary is:')
    for item in bkQueryDict.iteritems():
      self.log.verbose('%s : %s' % item)

    start = time.time()
    result = self._bkClient.getVisibleFilesWithMetadata(bkQueryDict)
#    result = bk.getFilesWithGivenDataSets(bkQueryDict)
    rtime = time.time() - start
    self.log.info('BK query time: %.2f sec' % rtime)

    if not result['OK']:
      return S_ERROR('BK query returned an error: "%s"' % (result['Message']))

    if not result['Value']:
      return self._errorReport('No BK files selected')

    returnedFiles = len(result['Value'])
    self.log.verbose('%s files selected from the BK' % (returnedFiles))
    return result

  #############################################################################
  def __checkDQFlags(self, flags):
    """ Internal function.  Checks the provided flags against the list of
        possible DQ flag statuses from the Bookkeeping.
    """
    dqFlags = []
    if isinstance(flags, list):
      dqFlags = flags
    else:
      dqFlags = [flags]

    bkFlags = self.getAllDQFlags()
    if not bkFlags['OK']:
      return bkFlags

    final = []
    for flag in dqFlags:
      if flag.lower() == 'all':
        final.append(flag.upper())
      else:
        flag = flag.upper()
        if flag not in bkFlags['Value']:
          msg = 'Specified DQ flag "%s" is not in allowed list: %s' % (flag, ', '.join(bkFlags['Value']))
          self.log.error(msg)
          return S_ERROR(msg)
        else:
          final.append(flag)

    # when first coding this it was not possible to use a list ;)
    if len(final) == 1:
      final = final[0]

    return S_OK(final)

  #############################################################################
  def getAllDQFlags(self, printOutput=False):
    """ Helper function.  Returns the list of possible DQ flag statuses
        from the Bookkeeping.

        Example Usage:

        >>> print dirac.getAllDQFlags()
        {'OK':True,'Value':<flags>}

       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """
    result = self._bkClient.getAvailableDataQuality()
    if not result['OK']:
      self.log.error('Could not obtain possible DQ flags from BK with result:\n%s' % (result))
      return result

    if printOutput:
      flags = result['Value']
      self.log.info('Possible DQ flags from BK are: %s' % (', '.join(flags)))

    return result

  #############################################################################
  def getDataByRun(self, lfns, printOutput=False):
    """Sort the supplied lfn list by run. An S_OK object will be returned
       containing a dictionary of runs and the corresponding list of LFN(s)
       associated with them.

       Example usage:

       >>> print dirac.getDataByRun(lfns)
       {'OK': True, 'Value': {<RUN>:['<LFN>','<LFN>',...], <RUN>:['<LFN>',..]}}


       @param lfns: Logical File Name(s)
       @type lfns: list
       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """
    if isinstance(lfns, str):
      lfns = [lfns.replace('LFN:', '')]
    elif isinstance(lfns, list):
      try:
        lfns = [str(lfn.replace('LFN:', '')) for lfn in lfns]
      except ValueError as x:
        return self._errorReport(str(x), 'Expected strings for LFNs')
    else:
      return self._errorReport('Expected single string or list of strings for LFN(s)')

    runDict = {}
    start = time.time()
    result = self._bkClient.getFileMetadata(lfns)
    self.log.verbose("Obtained BK file metadata in %.2f seconds" % (time.time() - start))
    if not result['OK']:
      self.log.error('Failed to get bookkeeping metadata with result "%s"' % (result['Message']))
      return result

    for lfn, metadata in result['Value']['Successful'].items():
      if 'RunNumber' in metadata:
        runNumber = metadata['RunNumber']
        runDict.setdefault(runNumber, []).append(lfn)
      else:
        self.log.warn('Could not find run number from BK for %s' % (lfn))

    if printOutput:
      self.log.notice(self.pPrint.pformat(runDict))

    return S_OK(runDict)

  #############################################################################
  def bkMetadata(self, lfns, printOutput=False):
    """Return metadata for the supplied lfn list. An S_OK object will be returned
       containing a dictionary of LFN(s) and the corresponding metadata associated
       with them.

       Example usage:

       >>> print dirac.bkMetadata(lfns)
       {'OK': True, 'Value': {<LFN>:{'<Name>':'<Value>',...},...}}

       @param lfns: Logical File Name(s)
       @type lfns: list
       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """
    if isinstance(lfns, str):
      lfns = [lfns.replace('LFN:', '')]
    elif isinstance(lfns, list):
      try:
        lfns = [str(lfn.replace('LFN:', '')) for lfn in lfns]
      except ValueError as x:
        return self._errorReport(str(x), 'Expected strings for LFNs')
    else:
      return self._errorReport('Expected single string or list of strings for LFN(s)')

    start = time.time()
    result = self._bkClient.getFileMetadata(lfns)
    self.log.verbose("Obtained BK file metadata in %.2f seconds" % (time.time() - start))
    if not result['OK']:
      self.log.error('Failed to get bookkeeping metadata with result "%s"' % (result['Message']))
      return result

    if printOutput:
      self.log.notice(self.pPrint.pformat(result['Value']))

    return result

  #############################################################################

  def lhcbProxyInit(self, *args):  # pylint: disable=no-self-use
    """ just calling the dirac-proxy-init script
    """
    os.system("dirac-proxy-init -o LogLevel=NOTICE -t --rfc %s" % "' '".join(args))

  #############################################################################

  def lhcbProxyInfo(self, *args):  # pylint: disable=no-self-use
    """ just calling the dirac-proxy-info script
    """
    os.system("dirac-proxy-info -o LogLevel=NOTICE %s" % "' '".join(args))
  #############################################################################

  def gridWeather(self, printOutput=False):
    """This method gives a snapshot of the current Grid weather from the perspective
       of the DIRAC site and SE masks.  Tier-1 sites are returned with more detailed
       information.

       Example usage:

       >>> print dirac.gridWeather()
       {'OK': True, 'Value': {{'Sites':<siteInfo>,'SEs':<seInfo>,'Tier-1s':<tierInfo>}}

       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """

    lcgSites = gConfig.getSections('/Resources/Sites/LCG')
    if not lcgSites['OK']:
      return lcgSites

    for lcgSite in lcgSites['Value']:

      tier = gConfig.getValue('/Resources/Sites/LCG/%s/MoUTierLevel' % lcgSite, 2)
      if tier in (0, 1):
        self.tier1s.append(lcgSite)

    siteInfo = self.checkSites()
    if not siteInfo['OK']:
      return siteInfo
    siteInfo = siteInfo['Value']

    seInfo = self.checkSEs()
    if not seInfo['OK']:
      return seInfo
    seInfo = seInfo['Value']

    tierSEs = {}
    for site in self.tier1s:
      tierSEs[site] = getSEsForSite(site)['Value']

    tierInfo = {}
    for site, seList in tierSEs.items():
      tierInfo[site] = {}
      for se in seList:
        if se in seInfo:
          tierSEInfo = seInfo[se]
          tierInfo[site][se] = tierSEInfo
      if site in siteInfo['AllowedSites']:
        tierInfo[site]['MaskStatus'] = 'Allowed'
      else:
        tierInfo[site]['MaskStatus'] = 'Banned'

    if printOutput:
      self.log.notice('========> Tier-1 status in DIRAC site and SE masks')
      for site in sorted(self.tier1s):
        self.log.notice('\n====> %s is %s in site mask\n' % (site, tierInfo[site]['MaskStatus']))
        self.log.notice('%s %s %s' % ('Storage Element'.ljust(25), 'Read Status'.rjust(15), 'Write Status'.rjust(15)))
        for se in sorted(tierSEs[site]):
          if se in tierInfo[site]:
            self.log.notice('%s %s %s' % (se.ljust(25),
                                          tierInfo[site][se]['ReadStatus'].rjust(15),
                                          tierInfo[site][se]['WriteStatus'].rjust(15))
                            )

      self.log.notice('\n========> Tier-2 status in DIRAC site mask\n')
      allowedSites = siteInfo['AllowedSites']
      bannedSites = siteInfo['BannedSites']
      for site in self.tier1s:
        if site in allowedSites:
          allowedSites.remove(site)
        if site in bannedSites:
          bannedSites.remove(site)
      self.log.notice(' %s sites are in the site mask, %s are banned.\n' % (len(allowedSites), len(bannedSites)))

    summary = {'Sites': siteInfo, 'SEs': seInfo, 'Tier-1s': tierInfo}
    return S_OK(summary)

  #############################################################################
  def checkSites(self, printOutput=False):  # pylint: disable=no-self-use
    """Return the list of sites in the DIRAC site mask and those which are banned.

       Example usage:

       >>> print dirac.checkSites()
       {'OK': True, 'Value': {'AllowedSites':['<Site>',...],'BannedSites':[]}

       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """

    res = getSites()
    if not res['OK']:
      self.log.error('Could not get list of sites from CS', res['Message'])
      return res
    totalList = res['Value']

    res = DiracAdmin().getSiteMask()
    if not res['OK']:
      return res

    sites = res['Value']
    bannedSites = []
    for site in totalList:
      if site not in sites:
        bannedSites.append(site)

    if printOutput:
      self.log.notice('\n========> Allowed Sites\n')
      self.log.notice('\n'.join(sites))
      self.log.notice('\n========> Banned Sites\n')
      self.log.notice('\n'.join(bannedSites))
      self.log.notice('\nThere is a total of %s allowed sites and %s banned sites in the system.' % (len(sites),
                                                                                                     len(bannedSites)))

    return S_OK({'AllowedSites': sites, 'BannedSites': bannedSites})

  #############################################################################
  def checkSEs(self, printOutput=False):  # pylint: disable=no-self-use
    """Check the status of read and write operations in the DIRAC SE mask.

       Example usage:

       >>> print dirac.checkSEs()
       {'OK': True, 'Value': {<LFN>:{'<Name>':'<Value>',...},...}}

       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """
    res = gConfig.getSections('/Resources/StorageElements', True)

    if not res['OK']:
      self.log.error('Failed to get storage element information', res['Message'])
      return res

    if printOutput:
      self.log.notice('%s %s %s' % ('Storage Element'.ljust(25), 'Read Status'.rjust(15), 'Write Status'.rjust(15)))

    seList = sorted(res['Value'])
    result = {}
    rss = ResourceStatus()
    for se in seList:
      res = rss.getElementStatus(se, 'StorageElement')
      if not res['OK']:
        self.log.error("Failed to get StorageElement status for %s" % se)
      else:
        readState = res['Value'].get('ReadAccess', 'Active')
        writeState = res['Value'].get('WriteAccess', 'Active')
        result[se] = {'ReadStatus': readState, 'WriteStatus': writeState}
        if printOutput:
          self.log.notice('%s %s %s' % (se.ljust(25), readState.rjust(15), writeState.rjust(15)))

    return S_OK(result)

  def splitInputDataBySize(self, lfns, maxSizePerJob=20, printOutput=False):
    """Split the supplied lfn list by the replicas present at the possible
       destination sites, based on a maximum size.
       An S_OK object will be returned containing a list of
       lists in order to create the jobs.

       Example usage:

       >>> d.splitInputDataBySize(lfns,10)
       {'OK': True, 'Value': [['<LFN>'], ['<LFN>']]}


       @param lfns: Logical File Name(s) to split
       @type lfns: list
       @param maxSizePerJob: Maximum size (in GB) per bunch
       @type maxSizePerJob: integer
       @param printOutput: Optional flag to print result
       @type printOutput: boolean
       @return: S_OK,S_ERROR
    """
    sitesForSE = {}
    if isinstance(lfns, str):
      lfns = [lfns.replace('LFN:', '')]
    elif isinstance(lfns, list):
      try:
        lfns = [str(lfn.replace('LFN:', '')) for lfn in lfns]
      except TypeError as x:
        return self._errorReport(str(x), 'Expected strings for LFNs')
    else:
      return self._errorReport('Expected single string or list of strings for LFN(s)')

    if not isinstance(maxSizePerJob, int):
      try:
        maxSizePerJob = int(maxSizePerJob)
      except ValueError as x:
        return self._errorReport(str(x), 'Expected integer for maxSizePerJob')
    maxSizePerJob *= 1000 * 1000 * 1000

    replicaDict = self.getReplicas(lfns)
    if not replicaDict['OK']:
      return replicaDict
    replicas = replicaDict['Value']['Successful']
    if not replicas:
      return self._errorReport(replicaDict['Value']['Failed'].items()[0],
                               'Failed to get replica information')
    siteLfns = {}
    for lfn, reps in replicas.items():
      possibleSites = set(site
                          for se in reps
                          for site in sitesForSE.setdefault(se, getSitesForSE(se).get('Value', [])))
      siteLfns.setdefault(','.join(sorted(possibleSites)), []).append(lfn)

    if '' in siteLfns:
      # Some files don't have active replicas
      return self._errorReport('No active replica found for', str(siteLfns['']))
    # Get size of files
    metadataDict = self.getLfnMetadata(lfns, printOutput)
    if not metadataDict['OK']:
      return metadataDict
    fileSizes = dict((lfn, metadataDict['Value']['Successful'].get(lfn, {}).get('Size', maxSizePerJob))
                     for lfn in lfns)

    lfnGroups = []
    # maxSize is in GB
    for files in siteLfns.values():
      # Now get bunches of files,
      # Sort in decreasing size
      files.sort(cmp=(lambda f1, f2: fileSizes[f2] - fileSizes[f1]))
      while files:
        # print [( lfn, fileSizes[lfn] ) for lfn in files]
        group = []
        sizeTot = 0
        for lfn in list(files):
          size = fileSizes[lfn]
          if size >= maxSizePerJob:
            lfnGroups.append([lfn])
          elif sizeTot + size < maxSizePerJob:
            sizeTot += size
            group.append(lfn)
            files.remove(lfn)
        if group:
          lfnGroups.append(group)

    if printOutput:
      self.log.notice(self.pPrint.pformat(lfnGroups))
    return S_OK(lfnGroups)

    #############################################################################

  def getAccessURL(self, lfn, storageElement, protocol=None, printOutput=False):
    """Allows to retrieve an access URL for an LFN replica given a valid DIRAC SE
       name.  Contacts the file catalog and contacts the site SRM endpoint behind
       the scenes.

       Example Usage:

       >>> print dirac.getAccessURL('/lhcb/data/CCRC08/DST/00000151/0000/00000151_00004848_2.dst','CERN-RAW')
       {'OK': True, 'Value': {'Successful': {'srm://...': {'SRM2': 'rfio://...'}}, 'Failed': {}}}

       :param lfn: Logical File Name (LFN)
       :type lfn: str or python:list
       :param storageElement: DIRAC SE name e.g. CERN-RAW
       :type storageElement: string
       :param printOutput: Optional flag to print result
       :type printOutput: boolean
       :returns: S_OK,S_ERROR
    """
    ret = self._checkFileArgument(lfn, 'LFN')
    if not ret['OK']:
      return ret
    lfn = ret['Value']
    if isinstance(lfn, basestring):
      lfn = [lfn]
    results = getAccessURL(lfn, storageElement, protocol=protocol)
    if printOutput:
      printDMResult(results, empty="File not at SE", script="dirac-dms-lfn-accessURL")
    return results

  #############################################################################

  def _getLocalInputData(self, parameters):
    """ LHCb extension of DIRAC API's _getLocalInputData. Only used for handling ancestors.
    """
    inputData = parameters.get('InputData')
    if inputData:
      self.log.debug("DiracLHCb._getLocalInputData. InputData: %s" % inputData)
      if isinstance(inputData, basestring):
        inputData = inputData.split(';')
      inputData = [lfn.strip('LFN:') for lfn in inputData]
      ancestorsDepth = int(parameters.get('AncestorDepth', 0))
      if ancestorsDepth:
        self.log.debug("DiracLHCb._getLocalInputData. ancestorsDepth: %d" % ancestorsDepth)
        res = self._bkClient.getFileAncestors(inputData, ancestorsDepth)
        if not res['OK']:
          self.log.error("Can't get ancestors", res['Message'])
          return res
        ancestorsLFNs = []
        for ancestorsLFN in res['Value']['Successful'].itervalues():
          ancestorsLFNs += [i['FileName'] for i in ancestorsLFN]
        self.log.info("DiracLHCb._getLocalInputData: adding %d ancestors" % len(ancestorsLFNs))
        self.log.verbose("%s", ', '.join(ancestorsLFNs))
        inputData += ancestorsLFNs

    return S_OK(inputData)
Ejemplo n.º 8
0
class ProcessingProgress( object ):

  def __init__( self, cacheFile = None ):
    if not cacheFile:
      self.prodStatFile = os.path.join( os.environ['HOME'], ".dirac/work", "dirac-production-stats.pkl" )
    else:
      self.prodStatFile = cacheFile
    self.cacheVersion = '0.0'
    self.clearCache = []
    self.cachedInfo = {}

    # Recuperate the previous cached information
    self.readCache()

    self.bk = BookkeepingClient()
    self.transClient = TransformationClient()

  def setClearCache( self, clearCache ):
    self.clearCache = clearCache

  def __getProdBkDict( self, prod ):
    res = self.transClient.getBookkeepingQuery( prod )
    if not res['OK']:
      gLogger.error( "Couldn't get BK query on production %d" % prod )
      return {}
    prodBKDict = res['Value']
    return prodBKDict

  def getFullStats( self, bkQuery, printResult = False ):
    processingPass = bkQuery.getProcessingPass()
    if printResult:
      gLogger.info( "\nStatistics for processing %s, condition %s\n" % ( processingPass, bkQuery.getConditions() ) )
    prodStats = []
    processingPass = processingPass.split( '/' )
    if len( processingPass ) != 4 or processingPass[1] != "Real Data":
      gLogger.error( "Processing pass should be /Real Data/<Reco>/<Stripping>" )
      return []

    # Get production numbers for the Reco
    recoBKQuery = BKQuery( bkQuery )
    recoBKQuery.setProcessingPass( '/'.join( processingPass[0:3] ) )
    recoList = recoBKQuery.getBKProductions( visible = False )
    recoRunRanges = {}
    recoDQFlags = []
    for prod in recoList:
      prodBKDict = self.__getProdBkDict( prod )
      if prodBKDict:
        recoRunRanges[prod] = [prodBKDict.get( "StartRun", 0 ), prodBKDict.get( "EndRun", sys.maxint )]
        dqFlags = prodBKDict.get( "DataQualityFlag", ['UNCHECKED', 'EXPRESS_OK', 'OK'] )
        if isinstance( dqFlags, basestring ):
          dqFlags = dqFlags.split( ',' )
        recoDQFlags += [fl for fl in dqFlags if fl not in recoDQFlags]
      else:
        recoRunRanges[prod] = [0, 0]
    # Sort productions by runs
    try:
      recoList.sort( cmp = ( lambda p1, p2: int( recoRunRanges[p1][0] - recoRunRanges[p2][1] ) ) )
    except:
      print "Exception in sorting productions:"
      for p in recoList:
        print p, recoRunRanges[p]
    gLogger.verbose( "Reconstruction productions found (%d): %s" % ( len( recoList ), str( sorted( recoList ) ) ) )
    gLogger.verbose( "Reconstruction DQ flags: %s" % str( recoDQFlags ) )

    # Get productions for merging
    mergeList = []
    mergeStripProds = {}
    # Get stripping productions as parents of merging productions
    stripList = []
    for prod in bkQuery.getBKProductions( visible = False ):
      prodBKDict = self.__getProdBkDict( prod )
      gLogger.verbose( "BK query for production %s: %s" % ( prod, str( prodBKDict ) ) )
      mergedTypes = prodBKDict.get( 'FileType' )
      if type( mergedTypes ) != type( [] ):
        mergedTypes = [mergedTypes]
      if [ft for ft in bkQuery.getFileTypeList() if ft in mergedTypes] and 'ProductionID' in prodBKDict:
        mergeList.append( prod )
        prods = prodBKDict['ProductionID']
        if type( prods ) != type( [] ):
          prods = [prods]
        stripList += prods
        mergeStripProds[prod] = [str( p ) for p in prods]
      else:
        _msgTuple = ( str( bkQuery.getFileTypeList() ), prod, str( prodBKDict ) )
        gLogger.verbose( "Could not find production or filetype %s in BKquery of production %d (%s)" % _msgTuple )
    mergeList.sort( cmp = ( lambda p1, p2: int( mergeStripProds[p1][0] ) - int( mergeStripProds[p2][0] ) ) )
    gLogger.verbose( "Merging productions found: %s" % str( mergeList ) )

    # get list of stripping productions (from merging)
    stripRunRanges = {}
    for prod in stripList:
      prodBKDict = self.__getProdBkDict( prod )
      if prodBKDict:
        stripRunRanges[prod] = [prodBKDict.get( "StartRun", 0 ), prodBKDict.get( "EndRun", sys.maxint )]
      else:
        stripRunRanges[prod] = [0, 0]
    # Sort productions by runs
    try:
      stripList.sort( cmp = ( lambda p1, p2: int( stripRunRanges[p1][0] - stripRunRanges[p2][1] ) ) )
    except:
      print "Error when sorting stripping productions:"
      for prodStrip in stripList:
        print prodStrip, stripRunRanges[prodStrip]
    gLogger.verbose( "Stripping productions found (%d): %s" % ( len( stripList ), str( sorted( stripList ) ) ) )

    # Get all runs corresponding to the run range used by the Reco productions
    rawBKQuery = BKQuery( bkQuery )
    rawBKQuery.setProcessingPass( '/Real Data' )
    rawBKQuery.setFileType( "RAW" )
    # get the list of runs (-prodNum)
    fullRunList = rawBKQuery.getBKRuns()
    gLogger.verbose( "Initial list of runs: %s" % str( fullRunList ) )
    recoRunList = []
    openProd = False
    for prod in [p for p in recoList]:
      # Forget fully openProd productions
      # Don't consider productions without a BK query (these are individual files)
      if recoRunRanges[prod][1] == sys.maxint and recoRunRanges[prod][0] != -sys.maxint:
        openProd = True
        # Try and find if that open production overlaps with a closed one, in which case, remove it
        # Do nothing for derived productions
        for p in [p for p in recoList if p != prod and recoRunRanges[prod] != recoRunRanges[p]]:
          if recoRunRanges[prod][0] < recoRunRanges[p][1] and recoRunRanges[p][1] != sys.maxint:
            openProd = False
            gLogger.verbose( "Production %s was removed as redundant..." % str( prod ) )
            recoList.remove( prod )
            break
        if not openProd: continue
      recoRunList += [run for run in fullRunList if run not in recoRunList and run >= recoRunRanges[prod][0] and run <= recoRunRanges[prod][1]]
    gLogger.verbose( "List of runs matching Reco (%d): %s" % ( len( recoRunList ), str( sorted( recoRunList ) ) ) )

    restrictToStripping = True
    if restrictToStripping and not openProd and stripList:
      runList = []
      for prod in stripList:
        runList += [run for run in recoRunList if run not in runList and run >= stripRunRanges[prod][0] and run <= stripRunRanges[prod][1]]
    else:
      runList = recoRunList
    gLogger.verbose( "Final list of runs matching Reco and Stripping (%d): %s" % ( len( runList ), str( sorted( runList ) ) ) )

    # Now get statistics from the runs
    info, runInfo = self._getStatsFromRuns( int( bkQuery.getEventTypeList()[0] ), runList, recoDQFlags )
    rawInfo = StatInfo( processingPass[1], info )
    prodStats.append( rawInfo )
    if printResult:
      gLogger.info( "%s - All runs in Reco productions" % processingPass[1] )
      for fileInfo in runInfo:
        if runInfo[fileInfo]:
          gLogger.info( "%s runs (%d): %s" % ( fileInfo, len( runInfo[fileInfo] ), str( runInfo[fileInfo] ) ) )
      summStr = "%s files, " % rawInfo.getItemAsString( 'Files' )
      summStr += "%s events in " % rawInfo.getItemAsString( 'Events' )
      summStr += "%s runs, luminosity (pb-1):All=%s, Bad=%s, OK=%s" % ( rawInfo.getItemAsString( 'Runs' ),
                                                                        rawInfo.getItemAsString( 'Lumi' ),
                                                                        rawInfo.getItemAsString( 'BadLumi' ),
                                                                        rawInfo.getItemAsString( 'OKLumi' ) )
      gLogger.info( summStr )

    # Create the info for the 3 sets of productions
    prodSets = []
    fileType = bkQuery.getFileTypeList()[0]
    prodSets.append( {'Name': processingPass[2], 'FileType': ['SDST', 'FULL.DST'], 'List':recoList,
                      'RunRange':recoRunRanges, 'MotherProds':None, 'AllReplicas':False } )
    prodSets.append( {'Name': processingPass[3], 'FileType': fileType, 'List':stripList,
                      'RunRange':stripRunRanges, 'MotherProds':None, 'AllReplicas':True, 'StatForOK':False } )
    prodSets.append( {'Name': "Merging (%s)" % fileType.split( '.' )[0], 'FileType': fileType, 'List':mergeList,
                      'RunRange':None, 'MotherProds':mergeStripProds, 'AllReplicas':False } )

    prevInfo = rawInfo
    for prodSet in prodSets:
      info = StatInfo( prodSet['Name'], self._getProdInfo( prodSet, runList, printResult = printResult ) )
      info.setRawInfo( rawInfo )
      info.setPrevInfo( prevInfo )
      prevInfo = info
      prodStats.append( info )

    self.saveCache()
    return prodStats

  @staticmethod
  def __sumProdInfo( info, totInfo ):
    for inf in info:
      for flag in info[inf]:
        if inf == 'Runs':
          totInfo[inf][flag] = totInfo.setdefault( inf, {} ).setdefault( flag, [] ) + info[inf][flag]
        else:
          totInfo[inf][flag] = totInfo.setdefault( inf, {} ).setdefault( flag, 0 ) + info[inf][flag]
    return totInfo

  def _getProdInfo( self, prodSet, runList, printResult = False ):
    totInfo = {}
    if printResult:
      gLogger.info( "" )
    for prod in prodSet['List']:
      info, runInfo = self._getStatsFromBK( prod, prodSet['FileType'], runList, prodSet['AllReplicas'] )
      if info['Files'][''] == 0:
        continue
      if not prodSet.get( 'StatForOK', True ):
        for item in info:
          for fl in info[item]:
            if fl == 'OK':
              info[item][fl] = 0 if not item == 'Runs' else []
      runRange = prodSet['RunRange']
      if runRange and prod in runRange and runRange[prod][0] == 0 and runRange[prod][1] == 0:
        for flag in info['Runs']:
          info['Runs'][flag] = []
      totInfo = self.__sumProdInfo( info, totInfo )
      if printResult:
        summStr = "%s production %d -" % ( prodSet['Name'], prod )
        if runRange and prod in runRange:
          firstRun = runRange[prod][0]
          lastRun = runRange[prod][1]
          if firstRun:
            summStr += " From run %d" % int( firstRun )
          if lastRun and lastRun != sys.maxint:
            summStr += " Up to run %d" % int( lastRun )
          if firstRun == 0 and lastRun == 0:
            summStr += "No run range specified"
        motherProds = prodSet['MotherProds']
        if motherProds and prod in motherProds:
          summStr += " from productions %s" % motherProds[prod]
        gLogger.info( summStr )
        for inf in runInfo:
          if runInfo[inf]:
            gLogger.info( "%s runs (%d): %s" % ( inf, len( runInfo[inf] ), str( runInfo[inf] ) ) )
        summStr = "%d files, " % info['Files']['']
        if info['Events']:
          summStr += "%d events in " % info['Events']['']
        _msgTuple = ( len( info['Runs'][''] ), info['Lumi'][''], info['Lumi']['Bad'], info['Lumi']['OK'] )
        summStr += "%d runs, luminosity (pb-1): All=%.3f, Bad=%.3f, OK=%.3f" % _msgTuple
        gLogger.info( summStr )
    for flag in totInfo.get( 'Runs', [] ):
      totInfo['Runs'][flag] = len( totInfo['Runs'][flag] )
    return totInfo

  @staticmethod
  def outputResults( conditions, processingPass, prodStats ):
    outputString = ""
    _msgTuple = ( conditions, ",", processingPass, "on", time.ctime( time.time() ) )
    outputString += "\nProduction progress for %s %s %s %s %s\n" % _msgTuple
    if len( prodStats ) < 4:
      outputString += "No statistics found for this BK query"
      return outputString
    for i in xrange( 4 ):
      info = prodStats[i]
      if not info:
        continue
      name = info.getName()
      outputString += "\nSummary for %s\n" % name
      outputString += "%d files, " % info.getItem( 'Files' )
      if info.getItem( 'Events' ):
        outputString += "%d events in " % info.getItem( 'Events' )
      _msgTuple = ( info.getItem( 'Runs' ), info.getItem( 'Lumi' ), info.getItem( 'BadLumi' ), info.getItem( 'OKLumi' ) )
      outputString += "%d runs, luminosity (pb-1): All=%.3f, Bad=%.3f, OK=%.3f\n" % _msgTuple
      prevStats = prodStats[:i]
      prevStats.reverse()
      for prevInfo in prevStats:
        name = prevInfo.getName()
        if prevInfo.getItem( 'Runs' ) == 0:
          outputString += "From %s : - No runs...\n" % name
        else:
          outputString += "From %s : %.1f%% files, " % ( name, 100.*info.getItem( 'Files' ) / prevInfo.getItem( 'Files' ) )
          if info.getItem( 'Events' ) and prevInfo.getItem( 'Events' ):
            outputString += "%.1f%% events\n" % ( 100.*info.getItem( 'Events' ) / prevInfo.getItem( 'Events' ) )
          outputString += "%.1f%% runs, %.1f%% luminosity\n" \
                          % ( 100. * info.getItem( 'Runs' ) / prevInfo.getItem( 'Runs' ),
                              100. * info.getItem( 'Lumi' ) / prevInfo.getItem( 'Lumi' ) )
    return outputString

  def __getRunsDQFlag( self, runList, evtType ):
    res = self.bk.getRunFilesDataQuality( runList )
    runFlags = {}
    if res['OK']:
      dqFlags = res['Value']
      for dq in dqFlags:
        if dq[2] == evtType:
          runFlags.setdefault( dq[0], [] ).append( dq[1] )
    runDQFlags = {}
    flags = ( 'BAD', 'OK', 'EXPRESS_OK', 'UNCHECKED' )
    for run in runFlags:
      for fl in flags:
        if fl in runFlags[run]:
          runDQFlags[run] = fl
          break
    return runDQFlags

  def _getStatsFromRuns( self, evtType, runList, recoDQFlags ):
    info = dict.fromkeys( ( 'Events', 'Runs', 'Files', 'Lumi' ), {} )
    for inf in info:
      info[inf] = dict.fromkeys( ( 'Bad', 'OK', '' ), 0 )
    now = datetime.datetime.utcnow()
    # Set to True to renew the cache
    clearCache = 'RAW' in self.clearCache
    newRuns = [ run for run in runList if clearCache
                or run not in self.cachedInfo
                or 'DQFlag' not in self.cachedInfo[run]
                or ( now - self.cachedInfo[run]['Time'] ) < datetime.timedelta( days = 2 )   ]
    if newRuns:
      runFlags = self.__getRunsDQFlag( newRuns, evtType )
    else:
      runFlags = {}
    runsByDQFlag = {}
    runInfo = {}
    for run in runList:
      cached = self.cachedInfo.get( run, {} )
      cachedTime = cached.get( 'Time', None )
      if run not in newRuns:
        cachedFiles = cached.get( 'Files', 0 )
        cachedEvents = cached.get( 'EventStat', 0 )
        cachedLumi = cached.get( 'Luminosity', 0 )
        dqFlag = cached.get( 'DQFlag', None )
      else:
        res = self.bk.getRunInformations( run )
        if res['OK']:
          val = res['Value']
          ind = val['Stream'].index( 90000000 )
          cachedFiles = val['Number of file'][ind]
          cachedEvents = val['Number of events'][ind]
          cachedLumi = val['luminosity'][ind]
          cachedTime = val['RunStart']
        else:
          gLogger.error( "Unable to get run information for run %s" % str( run ) )
          continue
        dqFlag = runFlags[run]
      self.cachedInfo[run] = { 'Time':cachedTime, 'Files':cachedFiles, 'EventStat': cachedEvents,
                              'Luminosity': cachedLumi, 'DQFlag':dqFlag }
      runsByDQFlag[dqFlag] = runsByDQFlag.setdefault( dqFlag, 0 ) + 1
      if dqFlag == "BAD":
        runInfo.setdefault( 'BAD', [] ).append( run )
      elif dqFlag not in recoDQFlags and dqFlag != 'OK' :
        runInfo.setdefault( 'Untagged', [] ).append( run )
      # Now count...
      flags = []
      if dqFlag != 'BAD':
        flags.append( '' )
        # OK in recoDQFlags means we take everything that is not BAD (reprocessing or new convention)
        if dqFlag in recoDQFlags or dqFlag == 'OK':
          flags.append( 'OK' )
      else:
        flags.append( 'Bad' )
      for flag in flags:
        info['Runs'][flag] += 1
        info['Files'][flag] += cachedFiles
        info['Events'][flag] += cachedEvents
        info['Lumi'][flag] += cachedLumi

    # Set lumi in pb-1
    for flag in info['Lumi']:
      info['Lumi'][flag] /= 1000000.
    gLogger.info( "Runs per flag:" )
    for key in runsByDQFlag:
      gLogger.info( "%s : %d" % ( key, runsByDQFlag[key] ) )
    for flag in runInfo:
      runInfo[flag].sort()
    return info, runInfo

  def __getLfnsMetadata( self, lfns ):
    lfnDict = {}
    if len( lfns ):
      gLogger.verbose( "Getting metadata for %d files" % len( lfns ) )
      for lfnChunk in breakListIntoChunks( lfns, 1000 ):
        while True:
          res = self.bk.getFileMetadata( lfnChunk )
          if not res['OK']:
            gLogger.error( "Error getting files metadata, retrying...", res['Message'] )
          else:
            break
        metadata = res['Value']['Successful']
        for lfn in lfnChunk:
          lfnDict[lfn] = {}
          for meta in ( 'EventStat', 'Luminosity', 'DQFlag', 'RunNumber' ):
            lfnDict[lfn][meta] = metadata[lfn][meta]
    return lfnDict

  def _getStatsFromBK( self, prod, fileType, runList, allReplicas ):
    bkQueryDict = { "ProductionID": prod, "FileType": fileType }
    bkStr = str( bkQueryDict )
    bkQuery = BKQuery( bkQueryDict, visible = not allReplicas )
    if allReplicas:
      bkQuery.setOption( 'ReplicaFlag', "All" )
    cached = self.cachedInfo.get( bkStr, {} )
    cachedTime = cached.get( 'Time', None )
    cachedLfns = cached.get( 'Lfns', {} )
    if isinstance( fileType, basestring ):
      fileType = [fileType]
    if set( fileType ).intersection( set( self.clearCache ) ):
      cachedTime = datetime.datetime.utcnow() - datetime.timedelta( days = 8 )
      cachedTime = None
      cachedLfns = {}
      gLogger.verbose( "Cleared cache for production %s, file type %s" % ( str( prod ), fileType ) )
    # Update if needed the cached information on LFNs
    if cachedLfns:
      lfns = [lfn for lfn in cachedLfns if cachedLfns[lfn].get( 'DQFlag' ) not in ( 'OK', 'BAD' )]
      for lfnChunk in breakListIntoChunks( lfns, 1000 ):
        #  get the DQFlag of files that are not yet OK
        while True:
          res = self.bk.getFileMetadata( lfnChunk )
          if not res['OK']:
            gLogger.error( "Error getting files metadata for cached files, bkQuery %s: %s" % ( bkStr, res['Message'] ) )
          else:
            metadata = res['Value']['Successful']
            for lfn in lfnChunk:
              cachedLfns[lfn]['DQFlag'] = metadata[lfn]['DQFlag']
            break

    # Now get the new files since last time...
    if cachedTime:
      bkQuery.setOption( 'StartDate', cachedTime.strftime( '%Y-%m-%d %H:%M:%S' ) )
    gLogger.verbose( "Getting files for BKQuery %s" % str( bkQuery ) )
    cachedTime = datetime.datetime.utcnow()
    lfns = [lfn for lfn in bkQuery.getLFNs( printOutput = False ) if lfn not in cachedLfns]
    gLogger.verbose( "Returned %d files" % len( lfns ) )
    cachedLfns.update( self.__getLfnsMetadata( lfns ) )

    self.cachedInfo[bkStr] = { 'Time':cachedTime, 'Lfns':cachedLfns }

    # Now sum up all information for the files
    info = dict.fromkeys( ( 'Events', 'Runs', 'Files', 'Lumi' ), {} )
    for inf in info:
      if inf == 'Runs':
        for flag in ( 'Bad', 'OK', '' ):
          info[inf][flag] = []
      else:
        info[inf] = dict.fromkeys( ( 'Bad', 'OK', '' ), 0 )

    for lfn in cachedLfns:
      lfnInfo = cachedLfns[lfn]
      run = lfnInfo['RunNumber']
      if run in runList and run in self.cachedInfo and self.cachedInfo[run]['DQFlag'] != 'BAD':
        dqFlag = cachedLfns[lfn]['DQFlag']
        flags = []
        if dqFlag != 'BAD':
          flags.append( '' )
        else:
          flags.append( 'Bad' )
        if dqFlag == 'OK':
          flags.append( 'OK' )
        for flag in flags:
          if run not in info['Runs'][flag]:
            info['Runs'][flag].append( run )
          info['Files'][flag] += 1
          info['Events'][flag] += lfnInfo['EventStat']
          info['Lumi'][flag] += lfnInfo['Luminosity']

    runInfo = {}
    if 'BAD' in info['Runs']:
      runInfo['BAD'] = info['Runs']['BAD']
      runInfo['BAD'].sort()
    else:
      runInfo['BAD'] = []
    if '' in info['Runs'] and 'OK' in info['Runs']:
      runInfo['Untagged'] = [run for run in info['Runs'][''] if run not in info['Runs']['OK']]
      runInfo['Untagged'].sort()
    else:
      runInfo['Untagged'] = []
    # for f in info['Runs']:
    #  info['Runs'][f] = len( info['Runs'][f] )
    for flag in info['Lumi']:
      info['Lumi'][flag] /= 1000000.
    return info, runInfo

  def getPreviousStats( self, processingPass ):
    prevStats = self.cachedInfo.get( 'ProdStats', {} ).get( processingPass )
    if prevStats:
      try:
        _name = prevStats['ProdStats'][0][0].getName()
      except:
        prevStats = None
    return prevStats

  def setPreviousStats( self, processingPass, prevStats ):
    self.cachedInfo.setdefault( 'ProdStats', {} )[processingPass] = prevStats
    self.saveCache()

  def readCache( self ):
    if not os.path.exists( self.prodStatFile ):
      gLogger.info( "Created cached file %s" % self.prodStatFile )
      self.cachedInfo = {}
      self.saveCache()
      return
    fileRead = False
    while not fileRead:
      try:
        with FileLock( self.prodStatFile ):
          lFile = open( self.prodStatFile, 'r' )
          cachedVersion = pickle.load( lFile )
          startTime = time.time()
          if cachedVersion == self.cacheVersion:
            self.cachedInfo = pickle.load( lFile )
            _msgTuple = ( self.prodStatFile, time.time() - startTime )
            gLogger.info( "Loaded cached information from %s in %.3f seconds" % _msgTuple )
          else:
            _msgTuple = ( cachedVersion, self.cacheVersion )
            gLogger.info( "Incompatible versions of cache, reset information (%s, expect %s)" % _msgTuple )
            self.cachedInfo = {}
          lFile.close()
          fileRead = True
      except FileLockException, error:
        gLogger.error( "Lock exception: %s while reading pickle file %s" % ( error, self.prodStatFile ) )
      except: