def updateCS(changeSet):

    global vo, dry, ceBdiiDict

    changeList = sorted(changeSet)
    if dry:
        gLogger.notice('The following needed changes are detected:\n')
    else:
        gLogger.notice('We are about to make the following changes to CS:\n')
    for entry in changeList:
        gLogger.notice("%s/%s %s -> %s" % entry)

    if not dry:
        csAPI = CSAPI()
        csAPI.initialize()
        result = csAPI.downloadCSData()
        if not result['OK']:
            gLogger.error('Failed to initialize CSAPI object',
                          result['Message'])
            DIRACExit(-1)
        for section, option, value, new_value in changeSet:
            if value == 'Unknown' or not value:
                csAPI.setOption(cfgPath(section, option), new_value)
            else:
                csAPI.modifyValue(cfgPath(section, option), new_value)

        yn = six.moves.input(
            'Do you want to commit changes to CS ? [default yes] [yes|no]: ')
        if yn == '' or yn.lower().startswith('y'):
            result = csAPI.commit()
            if not result['OK']:
                gLogger.error("Error while commit to CS", result['Message'])
            else:
                gLogger.notice("Successfully committed %d changes to CS" %
                               len(changeSet))
def updateCS( changeSet ):
  
  global vo, dry, ceBdiiDict
  
  changeList = list( changeSet )
  changeList.sort()
  if dry:
    gLogger.notice( 'The following needed changes are detected:\n' )
  else:  
    gLogger.notice( 'We are about to make the following changes to CS:\n' )
  for entry in changeList:
    gLogger.notice( "%s/%s %s -> %s" % entry )

  if not dry:
    csAPI = CSAPI()
    csAPI.initialize()
    result = csAPI.downloadCSData()
    if not result['OK']:
      gLogger.error( 'Failed to initialize CSAPI object', result['Message'] )
      DIRACExit( -1 )
    for section, option, value, new_value in changeSet:
      if value == 'Unknown' or not value:
        csAPI.setOption( cfgPath( section, option ), new_value )
      else:
        csAPI.modifyValue( cfgPath( section, option ), new_value )
        
    yn = raw_input( 'Do you want to commit changes to CS ? [default yes] [yes|no]: ' )
    if yn == '' or yn.lower().startswith( 'y' ):    
      result = csAPI.commit()
      if not result['OK']:
        gLogger.error( "Error while commit to CS", result['Message'] )
      else:
        gLogger.notice( "Successfully committed %d changes to CS" % len( changeSet ) )  
Example #3
0
def getMQParamsFromCS(mqURI):
    """Function gets parameters of a MQ destination (queue/topic) from the CS.

    Args:
      mqURI(str):Pseudo URI identifing the MQ service. It has the following format:
                mqConnection::DestinationType::DestinationName
                e.g. blabla.cern.ch::Queues::MyQueue1
      mType(str): 'consumer' or 'producer'
    Returns:
      S_OK(param_dicts) or S_ERROR
    """
    # API initialization is required to get an up-to-date configuration from the CS
    csAPI = CSAPI()
    csAPI.initialize()

    try:
        mqService, mqType, mqName = mqURI.split("::")
    except ValueError:
        return S_ERROR("Bad format of mqURI address:%s" % (mqURI))

    result = gConfig.getConfigurationTree("/Resources/MQServices", mqService,
                                          mqType, mqName)
    if not result["OK"] or not result["Value"]:
        return S_ERROR(
            "Requested destination not found in the CS: %s::%s::%s" %
            (mqService, mqType, mqName))
    mqDestinationPath = None
    for path, value in result["Value"].items():
        if not value and path.endswith(mqName):
            mqDestinationPath = path

    # set-up internal parameter depending on the destination type
    tmp = mqDestinationPath.split("Queues")[0].split("Topics")
    servicePath = tmp[0]
    serviceDict = {}
    if len(tmp) > 1:
        serviceDict["Topic"] = mqName
    else:
        serviceDict["Queue"] = mqName

    result = gConfig.getOptionsDict(servicePath)
    if not result["OK"]:
        return result
    serviceDict.update(result["Value"])

    result = gConfig.getOptionsDict(mqDestinationPath)
    if not result["OK"]:
        return result
    serviceDict.update(result["Value"])
    return S_OK(serviceDict)
Example #4
0
def getMQParamsFromCS( mqURI ):
  """ Function gets parameters of a MQ destination (queue/topic) from the CS.
  Args:
    mqURI(str):Pseudo URI identifing the MQ service. It has the following format:
              mqConnection::DestinationType::DestinationName
              e.g. blabla.cern.ch::Queue::MyQueue1
    mType(str): 'consumer' or 'producer'
  Returns:
    S_OK(param_dicts)/S_ERROR:
  """
  # API initialization is required to get an up-to-date configuration from the CS
  csAPI = CSAPI()
  csAPI.initialize()

  try :
    mqService, mqType, mqName = mqURI.split( "::" )
  except ValueError:
    return S_ERROR( 'Bad format of mqURI address:%s' % ( mqURI ) )

  result = gConfig.getConfigurationTree( '/Resources/MQServices', mqService, mqType, mqName )
  if not result['OK'] or len( result['Value'] ) == 0:
    return S_ERROR( 'Requested destination not found in the CS: %s::%s::%s' % ( mqService, mqType, mqName ) )
  mqDestinationPath = None
  for path, value in result['Value'].iteritems():
    if not value and path.endswith( mqName ):
      mqDestinationPath = path

  # set-up internal parameter depending on the destination type
  tmp = mqDestinationPath.split( 'Queue' )[0].split( 'Topic' )
  servicePath = tmp[0]
  serviceDict = {}
  if len( tmp ) > 1:
    serviceDict['Topic'] = mqName
  else:
    serviceDict['Queue'] = mqName

  result = gConfig.getOptionsDict( servicePath )
  if not result['OK']:
    return result
  serviceDict.update( result['Value'] )

  result = gConfig.getOptionsDict( mqDestinationPath )
  if not result['OK']:
    return result
  serviceDict.update( result['Value'] )
  return S_OK( serviceDict )
Example #5
0
class Bdii2CSAgent(AgentModule):
    def __init__(self, *args, **kwargs):
        """ Defines default parameters
    """

        super(Bdii2CSAgent, self).__init__(*args, **kwargs)

        self.addressTo = ''
        self.addressFrom = ''
        self.voName = []
        self.subject = "Bdii2CSAgent"
        self.alternativeBDIIs = []
        self.voBdiiCEDict = {}
        self.voBdiiSEDict = {}

        self.csAPI = None

        # What to get
        self.processCEs = True
        self.processSEs = False
        # Update the CS or not?
        self.dryRun = False

    def initialize(self):
        """ Gets run paramaters from the configuration
    """

        self.addressTo = self.am_getOption('MailTo', self.addressTo)
        self.addressFrom = self.am_getOption('MailFrom', self.addressFrom)
        # Create a list of alternative bdii urls
        self.alternativeBDIIs = self.am_getOption('AlternativeBDIIs',
                                                  self.alternativeBDIIs)
        # Check if the bdii url is appended by a port number, if not append the default 2170
        for index, url in enumerate(self.alternativeBDIIs):
            if not url.split(':')[-1].isdigit():
                self.alternativeBDIIs[index] += ':2170'
        if self.addressTo and self.addressFrom:
            self.log.info("MailTo", self.addressTo)
            self.log.info("MailFrom", self.addressFrom)
        if self.alternativeBDIIs:
            self.log.info("AlternativeBDII URLs:", self.alternativeBDIIs)

        self.processCEs = self.am_getOption('ProcessCEs', self.processCEs)
        self.processSEs = self.am_getOption('ProcessSEs', self.processSEs)
        self.dryRun = self.am_getOption('DryRun', self.dryRun)

        self.voName = self.am_getOption('VirtualOrganization', self.voName)
        if not self.voName:
            self.voName = self.am_getOption('VO', [])
        if not self.voName or (len(self.voName) == 1
                               and self.voName[0].lower() == 'all'):
            # Get all VOs defined in the configuration
            self.voName = []
            result = getVOs()
            if result['OK']:
                vos = result['Value']
                for vo in vos:
                    vomsVO = getVOOption(vo, "VOMSName")
                    if vomsVO:
                        self.voName.append(vomsVO)

        if self.voName:
            self.log.info("Agent will manage VO(s) %s" % self.voName)
        else:
            self.log.fatal("VirtualOrganization option not defined for agent")
            return S_ERROR()

        self.csAPI = CSAPI()
        return self.csAPI.initialize()

    def execute(self):
        """ General agent execution method
    """
        self.voBdiiCEDict = {}
        self.voBdiiSEDict = {}

        # Get a "fresh" copy of the CS data
        result = self.csAPI.downloadCSData()
        if not result['OK']:
            self.log.warn("Could not download a fresh copy of the CS data",
                          result['Message'])

        # Refresh the configuration from the master server
        gConfig.forceRefresh(fromMaster=True)

        if self.processCEs:
            self.__lookForNewCEs()
            self.__updateCEs()
        if self.processSEs:
            self.__lookForNewSEs()
            self.__updateSEs()
        return S_OK()

    def __lookForNewCEs(self):
        """ Look up BDII for CEs not yet present in the DIRAC CS
    """

        bannedCEs = self.am_getOption('BannedCEs', [])
        result = getCEsFromCS()
        if not result['OK']:
            return result
        knownCEs = set(result['Value'])
        knownCEs = knownCEs.union(set(bannedCEs))

        for vo in self.voName:
            result = self.__getBdiiCEInfo(vo)
            if not result['OK']:
                continue
            bdiiInfo = result['Value']
            result = getGridCEs(vo, bdiiInfo=bdiiInfo, ceBlackList=knownCEs)
            if not result['OK']:
                self.log.error('Failed to get unused CEs', result['Message'])
            siteDict = result['Value']
            body = ''
            for site in siteDict:
                newCEs = set(siteDict[site].keys())  # pylint: disable=no-member
                if not newCEs:
                    continue

                ceString = ''
                for ce in newCEs:
                    queueString = ''
                    ceInfo = bdiiInfo[site]['CEs'][ce]
                    newCEString = "CE: %s, GOCDB Site Name: %s" % (ce, site)
                    systemTuple = siteDict[site][ce]['System']
                    osString = "%s_%s_%s" % (systemTuple)
                    newCEString = "\n%s\n%s\n" % (newCEString, osString)
                    for queue in ceInfo['Queues']:
                        queueStatus = ceInfo['Queues'][queue].get(
                            'GlueCEStateStatus', 'UnknownStatus')
                        if 'production' in queueStatus.lower():
                            ceType = ceInfo['Queues'][queue].get(
                                'GlueCEImplementationName', '')
                            queueString += "   %s %s %s\n" % (
                                queue, queueStatus, ceType)
                    if queueString:
                        ceString += newCEString
                        ceString += "Queues:\n"
                        ceString += queueString

                if ceString:
                    body += ceString

            if body:
                body = "\nWe are glad to inform You about new CE(s) possibly suitable for %s:\n" % vo + body
                body += "\n\nTo suppress information about CE add its name to BannedCEs list.\n"
                body += "Add new Sites/CEs for vo %s with the command:\n" % vo
                body += "dirac-admin-add-resources --vo %s --ce\n" % vo
                self.log.info(body)
                if self.addressTo and self.addressFrom:
                    notification = NotificationClient()
                    result = notification.sendMail(self.addressTo,
                                                   self.subject,
                                                   body,
                                                   self.addressFrom,
                                                   localAttempt=False)
                    if not result['OK']:
                        self.log.error(
                            'Can not send new site notification mail',
                            result['Message'])

        return S_OK()

    def __getBdiiCEInfo(self, vo):

        if vo in self.voBdiiCEDict:
            return S_OK(self.voBdiiCEDict[vo])
        self.log.info("Check for available CEs for VO", vo)
        totalResult = S_OK({})
        message = ''

        mainResult = getBdiiCEInfo(vo)
        if not mainResult['OK']:
            self.log.error("Failed getting information from default bdii",
                           mainResult['Message'])
            message = mainResult['Message']

        for bdii in reversed(self.alternativeBDIIs):
            resultAlt = getBdiiCEInfo(vo, host=bdii)
            if resultAlt['OK']:
                totalResult['Value'].update(resultAlt['Value'])
            else:
                self.log.error("Failed getting information from %s " % bdii,
                               resultAlt['Message'])
                message = (message + "\n" + resultAlt['Message']).strip()

        if mainResult['OK']:
            totalResult['Value'].update(mainResult['Value'])

        if not totalResult[
                'Value'] and message:  ## Dict is empty and we have an error message
            self.log.error("Error during BDII request", message)
            totalResult = S_ERROR(message)
        else:
            self.voBdiiCEDict[vo] = totalResult['Value']
        return totalResult

    def __getBdiiSEInfo(self, vo):

        if vo in self.voBdiiSEDict:
            return S_OK(self.voBdiiSEDict[vo])
        self.log.info("Check for available SEs for VO", vo)
        result = getBdiiSEInfo(vo)
        message = ''
        if not result['OK']:
            message = result['Message']
            for bdii in self.alternativeBDIIs:
                result = getBdiiSEInfo(vo, host=bdii)
                if result['OK']:
                    break
        if not result['OK']:
            if message:
                self.log.error("Error during BDII request", message)
            else:
                self.log.error("Error during BDII request", result['Message'])
        else:
            self.voBdiiSEDict[vo] = result['Value']
        return result

    def __updateCEs(self):
        """ Update the Site/CE/queue settings in the CS if they were changed in the BDII
    """

        bdiiChangeSet = set()

        for vo in self.voName:
            result = self.__getBdiiCEInfo(vo)
            if not result['OK']:
                continue
            ceBdiiDict = result['Value']
            result = getSiteUpdates(vo, bdiiInfo=ceBdiiDict, log=self.log)
            if not result['OK']:
                continue
            bdiiChangeSet = bdiiChangeSet.union(result['Value'])

        # We have collected all the changes, consolidate VO settings
        result = self.__updateCS(bdiiChangeSet)
        return result

    def __updateCS(self, bdiiChangeSet):

        queueVODict = {}
        changeSet = set()
        for entry in bdiiChangeSet:
            section, option, _value, new_value = entry
            if option == "VO":
                queueVODict.setdefault(section, set())
                queueVODict[section] = queueVODict[section].union(
                    set(new_value.split(',')))
            else:
                changeSet.add(entry)
        for section, VOs in queueVODict.items():
            changeSet.add((section, 'VO', '', ','.join(VOs)))

        if changeSet:
            changeList = list(changeSet)
            changeList.sort()
            body = '\n'.join(
                ["%s/%s %s -> %s" % entry for entry in changeList])
            if body and self.addressTo and self.addressFrom:
                notification = NotificationClient()
                result = notification.sendMail(self.addressTo,
                                               self.subject,
                                               body,
                                               self.addressFrom,
                                               localAttempt=False)

            if body:
                self.log.info(
                    'The following configuration changes were detected:')
                self.log.info(body)

            for section, option, value, new_value in changeSet:
                if value == 'Unknown' or not value:
                    self.csAPI.setOption(cfgPath(section, option), new_value)
                else:
                    self.csAPI.modifyValue(cfgPath(section, option), new_value)

            if self.dryRun:
                self.log.info("Dry Run: CS won't be updated")
                self.csAPI.showDiff()
            else:
                result = self.csAPI.commit()
                if not result['OK']:
                    self.log.error("Error while committing to CS",
                                   result['Message'])
                else:
                    self.log.info("Successfully committed %d changes to CS" %
                                  len(changeList))
                return result
        else:
            self.log.info("No changes found")
            return S_OK()

    def __lookForNewSEs(self):
        """ Look up BDII for SEs not yet present in the DIRAC CS
    """

        bannedSEs = self.am_getOption('BannedSEs', [])
        result = getSEsFromCS()
        if not result['OK']:
            return result
        knownSEs = set(result['Value'])
        knownSEs = knownSEs.union(set(bannedSEs))

        for vo in self.voName:
            result = self.__getBdiiSEInfo(vo)
            if not result['OK']:
                continue
            bdiiInfo = result['Value']
            result = getGridSRMs(vo, bdiiInfo=bdiiInfo, srmBlackList=knownSEs)
            if not result['OK']:
                continue
            siteDict = result['Value']
            body = ''
            for site in siteDict:
                newSEs = set(siteDict[site].keys())  # pylint: disable=no-member
                if not newSEs:
                    continue
                for se in newSEs:
                    body += '\n New SE %s available at site %s:\n' % (se, site)
                    backend = siteDict[site][se]['SE'].get(
                        'GlueSEImplementationName', 'Unknown')
                    size = siteDict[site][se]['SE'].get(
                        'GlueSESizeTotal', 'Unknown')
                    body += '  Backend %s, Size %s' % (backend, size)

            if body:
                body = "\nWe are glad to inform You about new SE(s) possibly suitable for %s:\n" % vo + body
                body += "\n\nTo suppress information about an SE add its name to BannedSEs list.\n"
                body += "Add new SEs for vo %s with the command:\n" % vo
                body += "dirac-admin-add-resources --vo %s --se\n" % vo
                self.log.info(body)
                if self.addressTo and self.addressFrom:
                    notification = NotificationClient()
                    result = notification.sendMail(self.addressTo,
                                                   self.subject,
                                                   body,
                                                   self.addressFrom,
                                                   localAttempt=False)
                    if not result['OK']:
                        self.log.error(
                            'Can not send new site notification mail',
                            result['Message'])

        return S_OK()

    def __updateSEs(self):
        """ Update the Storage Element settings in the CS if they were changed in the BDII
    """

        bdiiChangeSet = set()

        for vo in self.voName:
            result = self.__getBdiiSEInfo(vo)
            if not result['OK']:
                continue
            seBdiiDict = result['Value']
            result = getSRMUpdates(vo, bdiiInfo=seBdiiDict)
            if not result['OK']:
                continue
            bdiiChangeSet = bdiiChangeSet.union(result['Value'])

        # We have collected all the changes, consolidate VO settings
        result = self.__updateCS(bdiiChangeSet)
        return result
Example #6
0
class Bdii2CSAgent( AgentModule ):

  addressTo = ''
  addressFrom = ''
  voName = ''
  subject = "CE2CSAgent"
  alternativeBDIIs = []

  def initialize( self ):

    self.addressTo = self.am_getOption( 'MailTo', self.addressTo )
    self.addressFrom = self.am_getOption( 'MailFrom', self.addressFrom )
    # Create a list of alternative bdii urls
    self.alternativeBDIIs = self.am_getOption( 'AlternativeBDIIs', [] )
    # Check if the bdii url is appended by a port number, if not append the default 2170
    for index, url in enumerate( self.alternativeBDIIs ):
      if not url.split( ':' )[-1].isdigit():
        self.alternativeBDIIs[index] += ':2170'
    if self.addressTo and self.addressFrom:
      self.log.info( "MailTo", self.addressTo )
      self.log.info( "MailFrom", self.addressFrom )
    if self.alternativeBDIIs :
      self.log.info( "AlternativeBDII URLs:", self.alternativeBDIIs )
    self.subject = "CE2CSAgent"
    
    self.processCEs = self.am_getOption( 'ProcessCEs', True )
    self.processSEs = self.am_getOption( 'ProcessSEs', False )

    self.voName = self.am_getOption( 'VirtualOrganization', [] )
    if not self.voName:
      self.voName = self.am_getOption( 'VO', [] )
    if not self.voName or ( len( self.voName ) == 1 and self.voName[0].lower() == 'all' ):
      # Get all VOs defined in the configuration
      self.voName = []
      result = getVOs()
      if result['OK']:
        vos = result['Value']
        for vo in vos:
          vomsVO = getVOOption( vo, "VOMSName" )
          if vomsVO:
            self.voName.append( vomsVO )

    if self.voName:
      self.log.info( "Agent will manage VO(s) %s" % self.voName )
    else:
      self.log.fatal( "VirtualOrganization option not defined for agent" )
      return S_ERROR()
    self.voBdiiCEDict = {}
    self.voBdiiSEDict = {}

    self.csAPI = CSAPI()
    return self.csAPI.initialize()

  def execute( self ):
    """ General agent execution method
    """
   
    # Get a "fresh" copy of the CS data
    result = self.csAPI.downloadCSData()
    if not result['OK']:
      self.log.warn( "Could not download a fresh copy of the CS data", result[ 'Message' ] )

    if self.processCEs:
      self.__lookForNewCEs()
      self.__updateCEs()
    if self.processSEs:  
      self.__lookForNewSEs()
      self.__updateSEs()
    return S_OK()

  def __lookForNewCEs( self ):
    """ Look up BDII for CEs not yet present in the DIRAC CS
    """

    bannedCEs = self.am_getOption( 'BannedCEs', [] )
    result = getCEsFromCS()
    if not result['OK']:
      return result
    knownCEs = set( result['Value'] )
    knownCEs = knownCEs.union( set( bannedCEs ) )

    for vo in self.voName:
      result = self.__getBdiiCEInfo( vo )
      if not result['OK']:
        continue
      bdiiInfo = result['Value']
      result = getGridCEs( vo, bdiiInfo = bdiiInfo, ceBlackList = knownCEs )
      if not result['OK']:
        self.log.error( 'Failed to get unused CEs', result['Message'] )
      siteDict = result['Value']  
      body = ''
      for site in siteDict:
        newCEs = set( siteDict[site].keys() )
        if not newCEs:
          continue
        
        ceString = ''
        for ce in newCEs:
          queueString = ''
          ceInfo = bdiiInfo[site]['CEs'][ce]
          ceString = "CE: %s, GOCDB Site Name: %s" % ( ce, site )
          systemTuple = siteDict[site][ce]['System']
          osString = "%s_%s_%s" % ( systemTuple )
          newCEString = "\n%s\n%s\n" % ( ceString, osString )
          for queue in ceInfo['Queues']:
            queueStatus = ceInfo['Queues'][queue].get( 'GlueCEStateStatus', 'UnknownStatus' )
            if 'production' in queueStatus.lower():
              ceType = ceInfo['Queues'][queue].get( 'GlueCEImplementationName', '' )
              queueString += "   %s %s %s\n" % ( queue, queueStatus, ceType )
          if queueString:
            ceString = newCEString
            ceString += "Queues:\n"
            ceString += queueString

        if ceString:
          body += ceString

      if body:
        body = "\nWe are glad to inform You about new CE(s) possibly suitable for %s:\n" % vo + body
        body += "\n\nTo suppress information about CE add its name to BannedCEs list.\n"
        body += "Add new Sites/CEs for vo %s with the command:\n" % vo
        body += "dirac-admin-add-resources --vo %s --ce\n" % vo
        self.log.info( body )
        if self.addressTo and self.addressFrom:
          notification = NotificationClient()
          result = notification.sendMail( self.addressTo, self.subject, body, self.addressFrom, localAttempt = False )
          if not result['OK']:
            self.log.error( 'Can not send new site notification mail', result['Message'] )

    return S_OK()

  def __getBdiiCEInfo( self, vo ):

    if vo in self.voBdiiCEDict:
      return S_OK( self.voBdiiCEDict[vo] )
    self.log.info( "Check for available CEs for VO", vo )
    result = getBdiiCEInfo( vo )
    message = ''
    if not result['OK']:
      message = result['Message']
      for bdii in self.alternativeBDIIs :
        result = getBdiiCEInfo( vo, host = bdii )
        if result['OK']:
          break
    if not result['OK']:
      if message:
        self.log.error( "Error during BDII request", message )
      else:
        self.log.error( "Error during BDII request", result['Message'] )
    else:
      self.voBdiiCEDict[vo] = result['Value']
    return result
  
  def __getBdiiSEInfo( self, vo ):

    if vo in self.voBdiiSEDict:
      return S_OK( self.voBdiiSEDict[vo] )
    self.log.info( "Check for available SEs for VO", vo )
    result = getBdiiSEInfo( vo )
    message = ''
    if not result['OK']:
      message = result['Message']
      for bdii in self.alternativeBDIIs :
        result = getBdiiSEInfo( vo, host = bdii )
        if result['OK']:
          break
    if not result['OK']:
      if message:
        self.log.error( "Error during BDII request", message )
      else:
        self.log.error( "Error during BDII request", result['Message'] )
    else:
      self.voBdiiSEDict[vo] = result['Value']
    return result

  def __updateCEs( self ):
    """ Update the Site/CE/queue settings in the CS if they were changed in the BDII
    """

    bdiiChangeSet = set()

    for vo in self.voName:
      result = self.__getBdiiCEInfo( vo )
      if not result['OK']:
        continue
      ceBdiiDict = result['Value']
      result = getSiteUpdates( vo, bdiiInfo = ceBdiiDict, log = self.log )
      if not result['OK']:
        continue
      bdiiChangeSet = bdiiChangeSet.union( result['Value'] )
      
    # We have collected all the changes, consolidate VO settings
    result = self.__updateCS( bdiiChangeSet )
    return result

  def __updateCS( self, bdiiChangeSet ):
    
    queueVODict = {}
    changeSet = set()
    for entry in bdiiChangeSet:
      section, option , _value, new_value = entry
      if option == "VO":
        queueVODict.setdefault( section, set() )
        queueVODict[section] = queueVODict[section].union( set( new_value.split( ',' ) ) )  
      else:
        changeSet.add( entry )  
    for section, VOs in queueVODict.items():
      changeSet.add( ( section, 'VO', '', ','.join( VOs ) ) )    

    if changeSet:
      changeList = list( changeSet )
      changeList.sort()
      body = '\n'.join( [ "%s/%s %s -> %s" % entry for entry in changeList ] )
      if body and self.addressTo and self.addressFrom:
        notification = NotificationClient()
        result = notification.sendMail( self.addressTo, self.subject, body, self.addressFrom, localAttempt = False )
        
      if body:  
        self.log.info( 'The following configuration changes were detected:' )  
        self.log.info( body )

      for section, option, value, new_value in changeSet:
        if value == 'Unknown' or not value:
          self.csAPI.setOption( cfgPath( section, option ), new_value )
        else:
          self.csAPI.modifyValue( cfgPath( section, option ), new_value )

      result = self.csAPI.commit()
      if not result['OK']:
        self.log.error( "Error while committing to CS", result['Message'] )
      else:
        self.log.info( "Successfully committed %d changes to CS" % len( changeList ) )
      return result
    else:
      self.log.info( "No changes found" )
      return S_OK()

  def __lookForNewSEs( self ):
    """ Look up BDII for SEs not yet present in the DIRAC CS
    """
    
    bannedSEs = self.am_getOption( 'BannedSEs', [] )
    result = getSEsFromCS()
    if not result['OK']:
      return result
    knownSEs = set( result['Value'] )
    knownSEs = knownSEs.union( set( bannedSEs ) )

    for vo in self.voName:
      result = self.__getBdiiSEInfo( vo )
      if not result['OK']:
        continue
      bdiiInfo = result['Value']
      result = getGridSRMs( vo, bdiiInfo = bdiiInfo, srmBlackList = knownSEs )
      if not result['OK']:
        continue
      siteDict = result['Value']  
      body = ''
      for site in siteDict:
        newSEs = set( siteDict[site].keys() )
        if not newSEs:
          continue
        for se in newSEs:
          body += '\n New SE %s available at site %s:\n' % ( se, site )
          backend = siteDict[site][se]['SE'].get( 'GlueSEImplementationName', 'Unknown' )
          size = siteDict[site][se]['SE'].get( 'GlueSESizeTotal', 'Unknown' )
          body += '  Backend %s, Size %s' % ( backend, size )
          
      if body:
        body = "\nWe are glad to inform You about new SE(s) possibly suitable for %s:\n" % vo + body
        body += "\n\nTo suppress information about an SE add its name to BannedSEs list.\n"
        body += "Add new SEs for vo %s with the command:\n" % vo
        body += "dirac-admin-add-resources --vo %s --se\n" % vo
        self.log.info( body )
        if self.addressTo and self.addressFrom:
          notification = NotificationClient()
          result = notification.sendMail( self.addressTo, self.subject, body, self.addressFrom, localAttempt = False )
          if not result['OK']:
            self.log.error( 'Can not send new site notification mail', result['Message'] )

    return S_OK()   
  
  def __updateSEs( self ):
    """ Update the Storage Element settings in the CS if they were changed in the BDII
    """
    
    bdiiChangeSet = set()

    for vo in self.voName:
      result = self.__getBdiiSEInfo( vo )
      if not result['OK']:
        continue
      seBdiiDict = result['Value']
      result = getSRMUpdates( vo, bdiiInfo = seBdiiDict )
      if not result['OK']:
        continue
      bdiiChangeSet = bdiiChangeSet.union( result['Value'] )
      
    # We have collected all the changes, consolidate VO settings
    result = self.__updateCS( bdiiChangeSet )
    return result
Example #7
0
class CE2CSAgent( AgentModule ):

  addressTo = ''
  addressFrom = ''
  voName = ''
  subject = "CE2CSAgent"
  alternativeBDIIs = []

  def initialize( self ):

    # TODO: Have no default and if no mail is found then use the diracAdmin group
    # and resolve all associated mail addresses.
    self.addressTo = self.am_getOption( 'MailTo', self.addressTo )
    self.addressFrom = self.am_getOption( 'MailFrom', self.addressFrom )
    # create a list of alternative bdii urls
    self.alternativeBDIIs = self.am_getOption( 'AlternativeBDIIs', [] )
    # check if the bdii url is appended by a port number, if not append the default 2170
    for index, url in enumerate( self.alternativeBDIIs ):
      if not url.split( ':' )[-1].isdigit():
        self.alternativeBDIIs[index] += ':2170'
    if self.addressTo and self.addressFrom:
      self.log.info( "MailTo", self.addressTo )
      self.log.info( "MailFrom", self.addressFrom )
    if self.alternativeBDIIs :
      self.log.info( "AlternativeBDII URLs:", self.alternativeBDIIs )
    self.subject = "CE2CSAgent"

    # This sets the Default Proxy to used as that defined under
    # /Operations/Shifter/TestManager
    # the shifterProxy option in the Configuration can be used to change this default.
    self.am_setOption( 'shifterProxy', 'TestManager' )

    self.voName = self.am_getOption( 'VirtualOrganization', self.voName )
    if not self.voName:
      self.voName = getVO()

    if not self.voName:
      self.log.fatal( "VO option not defined for agent" )
      return S_ERROR()

    self.csAPI = CSAPI()
    return self.csAPI.initialize()

  def execute( self ):

    self.log.info( "Start Execution" )
    result = getProxyInfo()
    if not result[ 'OK' ]:
      return result
    infoDict = result[ 'Value' ]
    self.log.info( formatProxyInfoAsString( infoDict ) )

    #Get a "fresh" copy of the CS data
    result = self.csAPI.downloadCSData()
    if not result[ 'OK' ]:
      self.log.warn( "Could not download a fresh copy of the CS data", result[ 'Message' ] )

    self.__lookForCE()
    self.__infoFromCE()
    self.log.info( "End Execution" )
    return S_OK()

  def __checkAlternativeBDIISite( self, fun, *args ):
    if self.alternativeBDIIs:
      self.log.warn( "Trying to use alternative bdii sites" )
      for site in self.alternativeBDIIs :
        self.log.info( "Trying to contact alternative bdii ", site )
        if len( args ) == 1 :
          result = fun( args[0], host = site )
        elif len( args ) == 2 :
          result = fun( args[0], vo = args[1], host = site )
        if not result['OK'] :
          self.log.error ( "Problem contacting alternative bddii", result['Message'] )
        elif result['OK'] :
          return result
      self.log.warn( "Also checking alternative BDII sites failed" )
      return result

  def __lookForCE( self ):

    knownces = self.am_getOption( 'BannedCEs', [] )

    result = gConfig.getSections( '/Resources/Sites' )
    if not result['OK']:
      return
    grids = result['Value']

    for grid in grids:

      result = gConfig.getSections( '/Resources/Sites/%s' % grid )
      if not result['OK']:
        return
      sites = result['Value']

      for site in sites:
        opt = gConfig.getOptionsDict( '/Resources/Sites/%s/%s' % ( grid, site ) )['Value']
        ces = List.fromChar( opt.get( 'CE', '' ) )
        knownces += ces

    response = ldapCEState( '', vo = self.voName )
    if not response['OK']:
      self.log.error( "Error during BDII request", response['Message'] )
      response = self.__checkAlternativeBDIISite( ldapCEState, '', self.voName )
      return response

    newces = {}
    for queue in response['Value']:
      try:
        queuename = queue['GlueCEUniqueID']
      except:
        continue

      cename = queuename.split( ":" )[0]
      if not cename in knownces:
        newces[cename] = None
        self.log.debug( "newce", cename )

    body = ""
    possibleNewSites = []
    for ce in newces.iterkeys():
      response = ldapCluster( ce )
      if not response['OK']:
        self.log.warn( "Error during BDII request", response['Message'] )
        response = self.__checkAlternativeBDIISite( ldapCluster, ce )
        continue
      clusters = response['Value']
      if len( clusters ) != 1:
        self.log.warn( "Error in cluster length", " CE %s Length %d" % ( ce, len( clusters ) ) )
      if len( clusters ) == 0:
        continue
      cluster = clusters[0]
      fkey = cluster.get( 'GlueForeignKey', [] )
      if type( fkey ) == type( '' ):
        fkey = [fkey]
      nameBDII = None
      for entry in fkey:
        if entry.count( 'GlueSiteUniqueID' ):
          nameBDII = entry.split( '=' )[1]
          break
      if not nameBDII:
        continue

      cestring = "CE: %s, GOCDB Name: %s" % ( ce, nameBDII )
      self.log.info( cestring )

      response = ldapCE( ce )
      if not response['OK']:
        self.log.warn( "Error during BDII request", response['Message'] )
        response = self.__checkAlternativeBDIISite( ldapCE, ce )
        continue

      ceinfos = response['Value']
      if len( ceinfos ):
        ceinfo = ceinfos[0]
        systemName = ceinfo.get( 'GlueHostOperatingSystemName', 'Unknown' )
        systemVersion = ceinfo.get( 'GlueHostOperatingSystemVersion', 'Unknown' )
        systemRelease = ceinfo.get( 'GlueHostOperatingSystemRelease', 'Unknown' )
      else:
        systemName = "Unknown"
        systemVersion = "Unknown"
        systemRelease = "Unknown"

      osstring = "SystemName: %s, SystemVersion: %s, SystemRelease: %s" % ( systemName, systemVersion, systemRelease )
      self.log.info( osstring )

      response = ldapCEState( ce, vo = self.voName )
      if not response['OK']:
        self.log.warn( "Error during BDII request", response['Message'] )
        response = self.__checkAlternativeBDIISite( ldapCEState, ce, self.voName )
        continue

      newcestring = "\n\n%s\n%s" % ( cestring, osstring )
      usefull = False
      cestates = response['Value']
      for cestate in cestates:
        queuename = cestate.get( 'GlueCEUniqueID', 'UnknownName' )
        queuestatus = cestate.get( 'GlueCEStateStatus', 'UnknownStatus' )

        queuestring = "%s %s" % ( queuename, queuestatus )
        self.log.info( queuestring )
        newcestring += "\n%s" % queuestring
        if queuestatus.count( 'Production' ):
          usefull = True
      if usefull:
        body += newcestring
        possibleNewSites.append( 'dirac-admin-add-site DIRACSiteName %s %s' % ( nameBDII, ce ) )
    if body:
      body = "We are glad to inform You about new CE(s) possibly suitable for %s:\n" % self.voName + body
      body += "\n\nTo suppress information about CE add its name to BannedCEs list."
      for  possibleNewSite in  possibleNewSites:
        body = "%s\n%s" % ( body, possibleNewSite )
      self.log.info( body )
      if self.addressTo and self.addressFrom:
        notification = NotificationClient()
        result = notification.sendMail( self.addressTo, self.subject, body, self.addressFrom, localAttempt = False )

    return S_OK()

  def __infoFromCE( self ):

    sitesSection = cfgPath( 'Resources', 'Sites' )
    result = gConfig.getSections( sitesSection )
    if not result['OK']:
      return
    grids = result['Value']

    changed = False
    body = ""

    for grid in grids:

      gridSection = cfgPath( sitesSection, grid )
      result = gConfig.getSections( gridSection )
      if not result['OK']:
        return
      sites = result['Value']

      for site in sites:
        siteSection = cfgPath( gridSection, site )
        opt = gConfig.getOptionsDict( siteSection )['Value']
        name = opt.get( 'Name', '' )
        if name:
          coor = opt.get( 'Coordinates', 'Unknown' )
          mail = opt.get( 'Mail', 'Unknown' )

          result = ldapSite( name )
          if not result['OK']:
            self.log.warn( "BDII site %s: %s" % ( name, result['Message'] ) )
            result = self.__checkAlternativeBDIISite( ldapSite, name )

          if result['OK']:
            bdiisites = result['Value']
            if len( bdiisites ) == 0:
              self.log.warn( name, "Error in bdii: leng = 0" )
            else:
              if not len( bdiisites ) == 1:
                self.log.warn( name, "Warning in bdii: leng = %d" % len( bdiisites ) )

              bdiisite = bdiisites[0]

              try:
                longitude = bdiisite['GlueSiteLongitude']
                latitude = bdiisite['GlueSiteLatitude']
                newcoor = "%s:%s" % ( longitude, latitude )
              except:
                self.log.warn( "Error in bdii coor" )
                newcoor = "Unknown"

              try:
                newmail = bdiisite['GlueSiteSysAdminContact'].split( ":" )[-1].strip()
              except:
                self.log.warn( "Error in bdii mail" )
                newmail = "Unknown"

              self.log.debug( "%s %s %s" % ( name, newcoor, newmail ) )

              if newcoor != coor:
                self.log.info( "%s" % ( name ), "%s -> %s" % ( coor, newcoor ) )
                if coor == 'Unknown':
                  self.csAPI.setOption( cfgPath( siteSection, 'Coordinates' ), newcoor )
                else:
                  self.csAPI.modifyValue( cfgPath( siteSection, 'Coordinates' ), newcoor )
                changed = True

              if newmail != mail:
                self.log.info( "%s" % ( name ), "%s -> %s" % ( mail, newmail ) )
                if mail == 'Unknown':
                  self.csAPI.setOption( cfgPath( siteSection, 'Mail' ), newmail )
                else:
                  self.csAPI.modifyValue( cfgPath( siteSection, 'Mail' ), newmail )
                changed = True

        celist = List.fromChar( opt.get( 'CE', '' ) )

        if not celist:
          self.log.warn( site, 'Empty site list' )
          continue

  #      result = gConfig.getSections( cfgPath( siteSection,'CEs' )
  #      if not result['OK']:
  #        self.log.debug( "Section CEs:", result['Message'] )

        for ce in celist:
          ceSection = cfgPath( siteSection, 'CEs', ce )
          result = gConfig.getOptionsDict( ceSection )
          if not result['OK']:
            self.log.debug( "Section CE", result['Message'] )
            wnTmpDir = 'Unknown'
            arch = 'Unknown'
            os = 'Unknown'
            si00 = 'Unknown'
            pilot = 'Unknown'
            cetype = 'Unknown'
          else:
            ceopt = result['Value']
            wnTmpDir = ceopt.get( 'wnTmpDir', 'Unknown' )
            arch = ceopt.get( 'architecture', 'Unknown' )
            os = ceopt.get( 'OS', 'Unknown' )
            si00 = ceopt.get( 'SI00', 'Unknown' )
            pilot = ceopt.get( 'Pilot', 'Unknown' )
            cetype = ceopt.get( 'CEType', 'Unknown' )

          result = ldapCE( ce )
          if not result['OK']:
            self.log.warn( 'Error in bdii for %s' % ce, result['Message'] )
            result = self.__checkAlternativeBDIISite( ldapCE, ce )
            continue
          try:
            bdiice = result['Value'][0]
          except:
            self.log.warn( 'Error in bdii for %s' % ce, result )
            bdiice = None
          if bdiice:
            try:
              newwnTmpDir = bdiice['GlueSubClusterWNTmpDir']
            except:
              newwnTmpDir = 'Unknown'
            if wnTmpDir != newwnTmpDir and newwnTmpDir != 'Unknown':
              section = cfgPath( ceSection, 'wnTmpDir' )
              self.log.info( section, " -> ".join( ( wnTmpDir, newwnTmpDir ) ) )
              if wnTmpDir == 'Unknown':
                self.csAPI.setOption( section, newwnTmpDir )
              else:
                self.csAPI.modifyValue( section, newwnTmpDir )
              changed = True

            try:
              newarch = bdiice['GlueHostArchitecturePlatformType']
            except:
              newarch = 'Unknown'
            if arch != newarch and newarch != 'Unknown':
              section = cfgPath( ceSection, 'architecture' )
              self.log.info( section, " -> ".join( ( arch, newarch ) ) )
              if arch == 'Unknown':
                self.csAPI.setOption( section, newarch )
              else:
                self.csAPI.modifyValue( section, newarch )
              changed = True

            try:
              newos = '_'.join( ( bdiice['GlueHostOperatingSystemName'],
                                  bdiice['GlueHostOperatingSystemVersion'],
                                  bdiice['GlueHostOperatingSystemRelease'] ) )
            except:
              newos = 'Unknown'
            if os != newos and newos != 'Unknown':
              section = cfgPath( ceSection, 'OS' )
              self.log.info( section, " -> ".join( ( os, newos ) ) )
              if os == 'Unknown':
                self.csAPI.setOption( section, newos )
              else:
                self.csAPI.modifyValue( section, newos )
              changed = True
              body = body + "OS was changed %s -> %s for %s at %s\n" % ( os, newos, ce, site )

            try:
              newsi00 = bdiice['GlueHostBenchmarkSI00']
            except:
              newsi00 = 'Unknown'
            if si00 != newsi00 and newsi00 != 'Unknown':
              section = cfgPath( ceSection, 'SI00' )
              self.log.info( section, " -> ".join( ( si00, newsi00 ) ) )
              if si00 == 'Unknown':
                self.csAPI.setOption( section, newsi00 )
              else:
                self.csAPI.modifyValue( section, newsi00 )
              changed = True

            try:
              rte = bdiice['GlueHostApplicationSoftwareRunTimeEnvironment']
              if self.voName.lower() == 'lhcb':
                if 'VO-lhcb-pilot' in rte:
                  newpilot = 'True'
                else:
                  newpilot = 'False'
              else:
                newpilot = 'Unknown'
            except:
              newpilot = 'Unknown'
            if pilot != newpilot and newpilot != 'Unknown':
              section = cfgPath( ceSection, 'Pilot' )
              self.log.info( section, " -> ".join( ( pilot, newpilot ) ) )
              if pilot == 'Unknown':
                self.csAPI.setOption( section, newpilot )
              else:
                self.csAPI.modifyValue( section, newpilot )
              changed = True

          result = ldapCEState( ce, vo = self.voName )        #getBDIICEVOView
          if not result['OK']:
            self.log.warn( 'Error in bdii for queue %s' % ce, result['Message'] )
            result = self.__checkAlternativeBDIISite( ldapCEState, ce, self.voName )
            continue
          try:
            queues = result['Value']
          except:
            self.log.warn( 'Error in bdii for queue %s' % ce, result['Massage'] )
            continue

          newcetype = 'Unknown'
          for queue in queues:
            try:
              queuetype = queue['GlueCEImplementationName']
            except:
              queuetype = 'Unknown'
            if newcetype == 'Unknown':
              newcetype = queuetype
            else:
              if queuetype != newcetype:
                self.log.warn( 'Error in bdii for ce %s ' % ce, 'different cetypes %s %s' % ( newcetype, queuetype ) )

          if newcetype=='ARC-CE':
            newcetype = 'ARC'

          if cetype != newcetype and newcetype != 'Unknown':
            section = cfgPath( ceSection, 'CEType' )
            self.log.info( section, " -> ".join( ( cetype, newcetype ) ) )
            if cetype == 'Unknown':
              self.csAPI.setOption( section, newcetype )
            else:
              self.csAPI.modifyValue( section, newcetype )
            changed = True

          for queue in queues:
            try:
              queueName = queue['GlueCEUniqueID'].split( '/' )[-1]
            except:
              self.log.warn( 'error in queuename ', queue )
              continue

            try:
              newmaxCPUTime = queue['GlueCEPolicyMaxCPUTime']
            except:
              newmaxCPUTime = None

            newsi00 = None
            try:
              caps = queue['GlueCECapability']
              if type( caps ) == type( '' ):
                caps = [caps]
              for cap in caps:
                if cap.count( 'CPUScalingReferenceSI00' ):
                  newsi00 = cap.split( '=' )[-1]
            except:
              newsi00 = None

            queueSection = cfgPath( ceSection, 'Queues', queueName )
            result = gConfig.getOptionsDict( queueSection )
            if not result['OK']:
              self.log.warn( "Section Queues", result['Message'] )
              maxCPUTime = 'Unknown'
              si00 = 'Unknown'
            else:
              queueopt = result['Value']
              maxCPUTime = queueopt.get( 'maxCPUTime', 'Unknown' )
              si00 = queueopt.get( 'SI00', 'Unknown' )

            if newmaxCPUTime and ( maxCPUTime != newmaxCPUTime ):
              section = cfgPath( queueSection, 'maxCPUTime' )
              self.log.info( section, " -> ".join( ( maxCPUTime, newmaxCPUTime ) ) )
              if maxCPUTime == 'Unknown':
                self.csAPI.setOption( section, newmaxCPUTime )
              else:
                self.csAPI.modifyValue( section, newmaxCPUTime )
              changed = True

            if newsi00 and ( si00 != newsi00 ):
              section = cfgPath( queueSection, 'SI00' )
              self.log.info( section, " -> ".join( ( si00, newsi00 ) ) )
              if si00 == 'Unknown':
                self.csAPI.setOption( section, newsi00 )
              else:
                self.csAPI.modifyValue( section, newsi00 )
              changed = True

    if changed:
      self.log.info( body )
      if body and self.addressTo and self.addressFrom:
        notification = NotificationClient()
        result = notification.sendMail( self.addressTo, self.subject, body, self.addressFrom, localAttempt = False )

      return self.csAPI.commit()
    else:
      self.log.info( "No changes found" )
      return S_OK()
Example #8
0
class CE2CSAgent(AgentModule):
    """ !!!Out-dated!!! Moved to Bdii2CSAgent
  """

    addressTo = ''
    addressFrom = ''
    voName = ''
    subject = "CE2CSAgent"
    alternativeBDIIs = []

    csAPI = None

    def initialize(self):

        # TODO: Have no default and if no mail is found then use the diracAdmin group
        # and resolve all associated mail addresses.
        self.addressTo = self.am_getOption('MailTo', self.addressTo)
        self.addressFrom = self.am_getOption('MailFrom', self.addressFrom)
        # Create a list of alternative bdii urls
        self.alternativeBDIIs = self.am_getOption('AlternativeBDIIs', [])
        # Check if the bdii url is appended by a port number, if not append the default 2170
        for index, url in enumerate(self.alternativeBDIIs):
            if not url.split(':')[-1].isdigit():
                self.alternativeBDIIs[index] += ':2170'
        if self.addressTo and self.addressFrom:
            self.log.info("MailTo", self.addressTo)
            self.log.info("MailFrom", self.addressFrom)
        if self.alternativeBDIIs:
            self.log.info("AlternativeBDII URLs:", self.alternativeBDIIs)
        self.subject = "CE2CSAgent"

        # This sets the Default Proxy to used as that defined under
        # /Operations/Shifter/TestManager
        # the shifterProxy option in the Configuration can be used to change this default.
        self.am_setOption('shifterProxy', 'TestManager')

        self.voName = self.am_getOption('VirtualOrganization', [])
        if not self.voName:
            vo = getVO()
            if vo:
                self.voName = [vo]

        if self.voName:
            self.log.info("Agent will manage VO(s) %s" % self.voName)
        else:
            self.log.fatal("VirtualOrganization option not defined for agent")
            return S_ERROR()

        self.csAPI = CSAPI()
        return self.csAPI.initialize()

    def execute(self):

        self.log.info("Start Execution")
        result = getProxyInfo()
        if not result['OK']:
            return result
        infoDict = result['Value']
        self.log.info(formatProxyInfoAsString(infoDict))

        # Get a "fresh" copy of the CS data
        result = self.csAPI.downloadCSData()
        if not result['OK']:
            self.log.warn("Could not download a fresh copy of the CS data",
                          result['Message'])

        self.__lookForCE()
        self.__infoFromCE()
        self.log.info("End Execution")
        return S_OK()

    def __checkAlternativeBDIISite(self, fun, *args):
        if self.alternativeBDIIs:
            self.log.warn("Trying to use alternative BDII sites")
            for site in self.alternativeBDIIs:
                self.log.info("Trying to contact alternative BDII", site)
                if len(args) == 1:
                    result = fun(args[0], host=site)
                elif len(args) == 2:
                    result = fun(args[0], vo=args[1], host=site)
                if not result['OK']:
                    self.log.error("Problem contacting alternative BDII",
                                   result['Message'])
                elif result['OK']:
                    return result
            self.log.warn("Also checking alternative BDII sites failed")
            return result

    def __lookForCE(self):

        knownCEs = self.am_getOption('BannedCEs', [])

        result = gConfig.getSections('/Resources/Sites')
        if not result['OK']:
            return
        grids = result['Value']

        for grid in grids:
            result = gConfig.getSections('/Resources/Sites/%s' % grid)
            if not result['OK']:
                return
            sites = result['Value']

            for site in sites:
                opt = gConfig.getOptionsDict('/Resources/Sites/%s/%s' %
                                             (grid, site))['Value']
                ces = List.fromChar(opt.get('CE', ''))
                knownCEs += ces

        response = ''
        for vo in self.voName:
            self.log.info("Check for available CEs for VO", vo)
            response = ldapCEState('', vo)
            if not response['OK']:
                self.log.error("Error during BDII request",
                               response['Message'])
                response = self.__checkAlternativeBDIISite(ldapCEState, '', vo)
                return response

            newCEs = {}
            for queue in response['Value']:
                try:
                    queueName = queue['GlueCEUniqueID']
                except:
                    continue

                ceName = queueName.split(":")[0]
                if not ceName in knownCEs:
                    newCEs[ceName] = None
                    self.log.debug("New CE", ceName)

            body = ""
            possibleNewSites = []
            for ce in newCEs.iterkeys():
                response = ldapCluster(ce)
                if not response['OK']:
                    self.log.warn("Error during BDII request",
                                  response['Message'])
                    response = self.__checkAlternativeBDIISite(ldapCluster, ce)
                    continue
                clusters = {}
                clusters = response['Value']
                if len(clusters) != 1:
                    self.log.warn("Error in cluster length",
                                  " CE %s Length %d" % (ce, len(clusters)))
                if len(clusters) == 0:
                    continue
                cluster = clusters[0]
                fkey = cluster.get('GlueForeignKey', [])
                if type(fkey) == type(''):
                    fkey = [fkey]
                nameBDII = None
                for entry in fkey:
                    if entry.count('GlueSiteUniqueID'):
                        nameBDII = entry.split('=')[1]
                        break
                if not nameBDII:
                    continue

                ceString = "CE: %s, GOCDB Name: %s" % (ce, nameBDII)
                self.log.info(ceString)

                response = ldapCE(ce)
                if not response['OK']:
                    self.log.warn("Error during BDII request",
                                  response['Message'])
                    response = self.__checkAlternativeBDIISite(ldapCE, ce)
                    continue

                ceInfos = response['Value']
                if len(ceInfos):
                    ceInfo = ceInfos[0]
                    systemName = ceInfo.get('GlueHostOperatingSystemName',
                                            'Unknown')
                    systemVersion = ceInfo.get(
                        'GlueHostOperatingSystemVersion', 'Unknown')
                    systemRelease = ceInfo.get(
                        'GlueHostOperatingSystemRelease', 'Unknown')
                else:
                    systemName = "Unknown"
                    systemVersion = "Unknown"
                    systemRelease = "Unknown"

                osString = "SystemName: %s, SystemVersion: %s, SystemRelease: %s" % (
                    systemName, systemVersion, systemRelease)
                self.log.info(osString)

                response = ldapCEState(ce, vo)
                if not response['OK']:
                    self.log.warn("Error during BDII request",
                                  response['Message'])
                    response = self.__checkAlternativeBDIISite(
                        ldapCEState, ce, vo)
                    continue

                newCEString = "\n\n%s\n%s" % (ceString, osString)
                usefull = False
                ceStates = response['Value']
                for ceState in ceStates:
                    queueName = ceState.get('GlueCEUniqueID', 'UnknownName')
                    queueStatus = ceState.get('GlueCEStateStatus',
                                              'UnknownStatus')

                    queueString = "%s %s" % (queueName, queueStatus)
                    self.log.info(queueString)
                    newCEString += "\n%s" % queueString
                    if queueStatus.count('Production'):
                        usefull = True
                if usefull:
                    body += newCEString
                    possibleNewSites.append(
                        'dirac-admin-add-site DIRACSiteName %s %s' %
                        (nameBDII, ce))
            if body:
                body = "We are glad to inform You about new CE(s) possibly suitable for %s:\n" % vo + body
                body += "\n\nTo suppress information about CE add its name to BannedCEs list."
                for possibleNewSite in possibleNewSites:
                    body = "%s\n%s" % (body, possibleNewSite)
                self.log.info(body)
                if self.addressTo and self.addressFrom:
                    notification = NotificationClient()
                    result = notification.sendMail(self.addressTo,
                                                   self.subject,
                                                   body,
                                                   self.addressFrom,
                                                   localAttempt=False)

        return S_OK()

    def __infoFromCE(self):

        sitesSection = cfgPath('Resources', 'Sites')
        result = gConfig.getSections(sitesSection)
        if not result['OK']:
            return
        grids = result['Value']

        changed = False
        body = ""

        for grid in grids:
            gridSection = cfgPath(sitesSection, grid)
            result = gConfig.getSections(gridSection)
            if not result['OK']:
                return
            sites = result['Value']

            for site in sites:
                siteSection = cfgPath(gridSection, site)
                opt = gConfig.getOptionsDict(siteSection)['Value']
                name = opt.get('Name', '')
                if name:
                    coor = opt.get('Coordinates', 'Unknown')
                    mail = opt.get('Mail', 'Unknown')

                    result = ldapSite(name)
                    if not result['OK']:
                        self.log.warn("BDII site %s: %s" %
                                      (name, result['Message']))
                        result = self.__checkAlternativeBDIISite(
                            ldapSite, name)

                    if result['OK']:
                        bdiiSites = result['Value']
                        if len(bdiiSites) == 0:
                            self.log.warn(name, "Error in BDII: leng = 0")
                        else:
                            if not len(bdiiSites) == 1:
                                self.log.warn(
                                    name, "Warning in BDII: leng = %d" %
                                    len(bdiiSites))

                            bdiiSite = bdiiSites[0]

                            try:
                                longitude = bdiiSite['GlueSiteLongitude']
                                latitude = bdiiSite['GlueSiteLatitude']
                                newcoor = "%s:%s" % (longitude, latitude)
                            except:
                                self.log.warn("Error in BDII coordinates")
                                newcoor = "Unknown"

                            try:
                                newmail = bdiiSite[
                                    'GlueSiteSysAdminContact'].split(
                                        ":")[-1].strip()
                            except:
                                self.log.warn("Error in BDII mail")
                                newmail = "Unknown"

                            self.log.debug("%s %s %s" %
                                           (name, newcoor, newmail))

                            if newcoor != coor:
                                self.log.info("%s" % (name),
                                              "%s -> %s" % (coor, newcoor))
                                if coor == 'Unknown':
                                    self.csAPI.setOption(
                                        cfgPath(siteSection, 'Coordinates'),
                                        newcoor)
                                else:
                                    self.csAPI.modifyValue(
                                        cfgPath(siteSection, 'Coordinates'),
                                        newcoor)
                                changed = True

                            if newmail != mail:
                                self.log.info("%s" % (name),
                                              "%s -> %s" % (mail, newmail))
                                if mail == 'Unknown':
                                    self.csAPI.setOption(
                                        cfgPath(siteSection, 'Mail'), newmail)
                                else:
                                    self.csAPI.modifyValue(
                                        cfgPath(siteSection, 'Mail'), newmail)
                                changed = True

                ceList = List.fromChar(opt.get('CE', ''))

                if not ceList:
                    self.log.warn(site, 'Empty site list')
                    continue

    #      result = gConfig.getSections( cfgPath( siteSection,'CEs' )
    #      if not result['OK']:
    #        self.log.debug( "Section CEs:", result['Message'] )

                for ce in ceList:
                    ceSection = cfgPath(siteSection, 'CEs', ce)
                    result = gConfig.getOptionsDict(ceSection)
                    if not result['OK']:
                        self.log.debug("Section CE", result['Message'])
                        wnTmpDir = 'Unknown'
                        arch = 'Unknown'
                        os = 'Unknown'
                        si00 = 'Unknown'
                        pilot = 'Unknown'
                        ceType = 'Unknown'
                    else:
                        ceopt = result['Value']
                        wnTmpDir = ceopt.get('wnTmpDir', 'Unknown')
                        arch = ceopt.get('architecture', 'Unknown')
                        os = ceopt.get('OS', 'Unknown')
                        si00 = ceopt.get('SI00', 'Unknown')
                        pilot = ceopt.get('Pilot', 'Unknown')
                        ceType = ceopt.get('CEType', 'Unknown')

                    result = ldapCE(ce)
                    if not result['OK']:
                        self.log.warn('Error in BDII for %s' % ce,
                                      result['Message'])
                        result = self.__checkAlternativeBDIISite(ldapCE, ce)
                        continue
                    try:
                        bdiiCE = result['Value'][0]
                    except:
                        self.log.warn('Error in BDII for %s' % ce, result)
                        bdiiCE = None
                    if bdiiCE:
                        try:
                            newWNTmpDir = bdiiCE['GlueSubClusterWNTmpDir']
                        except:
                            newWNTmpDir = 'Unknown'
                        if wnTmpDir != newWNTmpDir and newWNTmpDir != 'Unknown':
                            section = cfgPath(ceSection, 'wnTmpDir')
                            self.log.info(section, " -> ".join(
                                (wnTmpDir, newWNTmpDir)))
                            if wnTmpDir == 'Unknown':
                                self.csAPI.setOption(section, newWNTmpDir)
                            else:
                                self.csAPI.modifyValue(section, newWNTmpDir)
                            changed = True

                        try:
                            newArch = bdiiCE[
                                'GlueHostArchitecturePlatformType']
                        except:
                            newArch = 'Unknown'
                        if arch != newArch and newArch != 'Unknown':
                            section = cfgPath(ceSection, 'architecture')
                            self.log.info(section, " -> ".join(
                                (arch, newArch)))
                            if arch == 'Unknown':
                                self.csAPI.setOption(section, newArch)
                            else:
                                self.csAPI.modifyValue(section, newArch)
                            changed = True

                        try:
                            newOS = '_'.join(
                                (bdiiCE['GlueHostOperatingSystemName'],
                                 bdiiCE['GlueHostOperatingSystemVersion'],
                                 bdiiCE['GlueHostOperatingSystemRelease']))
                        except:
                            newOS = 'Unknown'
                        if os != newOS and newOS != 'Unknown':
                            section = cfgPath(ceSection, 'OS')
                            self.log.info(section, " -> ".join((os, newOS)))
                            if os == 'Unknown':
                                self.csAPI.setOption(section, newOS)
                            else:
                                self.csAPI.modifyValue(section, newOS)
                            changed = True
                            body = body + "OS was changed %s -> %s for %s at %s\n" % (
                                os, newOS, ce, site)

                        try:
                            newSI00 = bdiiCE['GlueHostBenchmarkSI00']
                        except:
                            newSI00 = 'Unknown'
                        if si00 != newSI00 and newSI00 != 'Unknown':
                            section = cfgPath(ceSection, 'SI00')
                            self.log.info(section, " -> ".join(
                                (si00, newSI00)))
                            if si00 == 'Unknown':
                                self.csAPI.setOption(section, newSI00)
                            else:
                                self.csAPI.modifyValue(section, newSI00)
                            changed = True

                        try:
                            rte = bdiiCE[
                                'GlueHostApplicationSoftwareRunTimeEnvironment']
                            for vo in self.voName:
                                if vo.lower() == 'lhcb':
                                    if 'VO-lhcb-pilot' in rte:
                                        newPilot = 'True'
                                    else:
                                        newPilot = 'False'
                                else:
                                    newPilot = 'Unknown'
                        except:
                            newPilot = 'Unknown'
                        if pilot != newPilot and newPilot != 'Unknown':
                            section = cfgPath(ceSection, 'Pilot')
                            self.log.info(section, " -> ".join(
                                (pilot, newPilot)))
                            if pilot == 'Unknown':
                                self.csAPI.setOption(section, newPilot)
                            else:
                                self.csAPI.modifyValue(section, newPilot)
                            changed = True

                    newVO = ''
                    for vo in self.voName:
                        result = ldapCEState(ce, vo)  #getBDIICEVOView
                        if not result['OK']:
                            self.log.warn('Error in BDII for queue %s' % ce,
                                          result['Message'])
                            result = self.__checkAlternativeBDIISite(
                                ldapCEState, ce, vo)
                            continue
                        try:
                            queues = result['Value']
                        except:
                            self.log.warn('Error in BDII for queue %s' % ce,
                                          result['Massage'])
                            continue

                        newCEType = 'Unknown'
                        for queue in queues:
                            try:
                                queueType = queue['GlueCEImplementationName']
                            except:
                                queueType = 'Unknown'
                            if newCEType == 'Unknown':
                                newCEType = queueType
                            else:
                                if queueType != newCEType:
                                    self.log.warn(
                                        'Error in BDII for CE %s ' % ce,
                                        'different CE types %s %s' %
                                        (newCEType, queueType))

                        if newCEType == 'ARC-CE':
                            newCEType = 'ARC'

                        if ceType != newCEType and newCEType != 'Unknown':
                            section = cfgPath(ceSection, 'CEType')
                            self.log.info(section, " -> ".join(
                                (ceType, newCEType)))
                            if ceType == 'Unknown':
                                self.csAPI.setOption(section, newCEType)
                            else:
                                self.csAPI.modifyValue(section, newCEType)
                            changed = True

                        for queue in queues:
                            try:
                                queueName = queue['GlueCEUniqueID'].split(
                                    '/')[-1]
                            except:
                                self.log.warn('Error in queueName ', queue)
                                continue

                            try:
                                newMaxCPUTime = queue['GlueCEPolicyMaxCPUTime']
                            except:
                                newMaxCPUTime = None

                            newSI00 = None
                            try:
                                caps = queue['GlueCECapability']
                                if type(caps) == type(''):
                                    caps = [caps]
                                for cap in caps:
                                    if cap.count('CPUScalingReferenceSI00'):
                                        newSI00 = cap.split('=')[-1]
                            except:
                                newSI00 = None

                            queueSection = cfgPath(ceSection, 'Queues',
                                                   queueName)
                            result = gConfig.getOptionsDict(queueSection)
                            if not result['OK']:
                                self.log.warn("Section Queues",
                                              result['Message'])
                                maxCPUTime = 'Unknown'
                                si00 = 'Unknown'
                                allowedVOs = ['']
                            else:
                                queueOpt = result['Value']
                                maxCPUTime = queueOpt.get(
                                    'maxCPUTime', 'Unknown')
                                si00 = queueOpt.get('SI00', 'Unknown')
                                if newVO == '':  # Remember previous iteration, if none - read from conf
                                    allowedVOs = queueOpt.get('VO',
                                                              '').split(",")
                                else:  # Else use newVO, as it can contain changes, which aren't in conf yet
                                    allowedVOs = newVO.split(",")
                            if newMaxCPUTime and (maxCPUTime != newMaxCPUTime):
                                section = cfgPath(queueSection, 'maxCPUTime')
                                self.log.info(
                                    section, " -> ".join(
                                        (maxCPUTime, newMaxCPUTime)))
                                if maxCPUTime == 'Unknown':
                                    self.csAPI.setOption(
                                        section, newMaxCPUTime)
                                else:
                                    self.csAPI.modifyValue(
                                        section, newMaxCPUTime)
                                changed = True

                            if newSI00 and (si00 != newSI00):
                                section = cfgPath(queueSection, 'SI00')
                                self.log.info(section, " -> ".join(
                                    (si00, newSI00)))
                                if si00 == 'Unknown':
                                    self.csAPI.setOption(section, newSI00)
                                else:
                                    self.csAPI.modifyValue(section, newSI00)
                                changed = True

                            modifyVO = True  # Flag saying if we need VO option to change
                            newVO = ''
                            if allowedVOs != ['']:
                                for allowedVO in allowedVOs:
                                    allowedVO = allowedVO.strip(
                                    )  # Get rid of spaces
                                    newVO += allowedVO
                                    if allowedVO == vo:  # Current VO has been already in list
                                        newVO = ''
                                        modifyVO = False  # Don't change anything
                                        break  # Skip next 'if', proceed to next VO
                                    newVO += ', '

                            if modifyVO:
                                section = cfgPath(queueSection, 'VO')
                                newVO += vo
                                self.log.info(
                                    section, " -> ".join(
                                        ('%s' % allowedVOs, newVO)))
                                if allowedVOs == ['']:
                                    self.csAPI.setOption(section, newVO)
                                else:
                                    self.csAPI.modifyValue(section, newVO)
                                changed = True

        if changed:
            self.log.info(body)
            if body and self.addressTo and self.addressFrom:
                notification = NotificationClient()
                result = notification.sendMail(self.addressTo,
                                               self.subject,
                                               body,
                                               self.addressFrom,
                                               localAttempt=False)

            return self.csAPI.commit()
        else:
            self.log.info("No changes found")
            return S_OK()
Example #9
0
class NetworkAgent(AgentModule):
    """
  AccountingSystem agent to processes messages containing perfSONAR network metrics.
  Results are stored in the accounting database.
  """

    BUFFER_TIMEOUT = 1800  # number of seconds after which network accounting objects

    # are removed form the the temporary buffer

    def initialize(self):

        self.log = gLogger.getSubLogger('NetworkAgent')

        # API initialization is required to get an up-to-date configuration from the CS
        self.csAPI = CSAPI()
        self.csAPI.initialize()

        # temporary buffer for network accounting objects + some parameters
        self.buffer = {
        }  # { {addTime: datetime.now(), object: Network() }, ... }
        self.bufferTiemout = self.am_getOption('BufferTimeout',
                                               NetworkAgent.BUFFER_TIMEOUT)

        # internal list of message queue consumers
        self.consumers = []

        # host-to-dirac name dictionary
        self.nameDictionary = {}

        # statistics
        self.messagesCount = 0  # number of received messages
        self.messagesCountOld = 0  # previous number of received messages (used to check connection status)

        self.skippedMessagesCount = 0  # number of skipped messages (errors, unsupported metrics, etc.)
        self.PLRMetricCount = 0  # number of received packet-loss-rate metrics
        self.OWDMetricCount = 0  # number of received one-way-delay metrics
        self.skippedMetricCount = 0  # number of skipped metrics (errors, invalid data, etc.)
        self.insertedCount = 0  # number of properly inserted accounting objects
        self.removedCount = 0  # number of removed accounting objects (missing data)

        return S_OK()

    def finalize(self):
        '''
    Gracefully close all consumer connections and commit last records to the DB.
    '''

        for consumer in self.consumers:
            consumer.close()

        self.commitData()

        return S_OK()

    def execute(self):
        '''
    During each cycle update the internal host-to-dirac name dictionary,
    check the consumers status (restart them if necessary),
    commit data stored in the buffer and show statistics.
    '''

        self.updateNameDictionary()
        self.checkConsumers()
        self.commitData()
        self.showStatistics()

        return S_OK()

    def updateNameDictionary(self):
        '''
    Update the internal host-to-dirac name dictionary.
    '''

        result = gConfig.getConfigurationTree('/Resources/Sites', 'Network/',
                                              '/Enabled')
        if not result['OK']:
            self.log.error("getConfigurationTree() failed with message: %s" %
                           result['Message'])
            return S_ERROR('Unable to fetch perfSONAR endpoints from CS.')

        tmpDict = {}
        for path, value in result['Value'].iteritems():
            if value == 'True':
                elements = path.split('/')
                diracName = elements[4]
                hostName = elements[6]
                tmpDict[hostName] = diracName

        self.nameDictionary = tmpDict

    def checkConsumers(self):
        '''
    Check whether consumers exist and work properly.
    (Re)create consumers if needed.
    '''

        # recreate consumers if there are any problems
        if not self.consumers or self.messagesCount == self.messagesCountOld:

            for consumer in self.consumers:
                consumer.close()

            for uri in self.am_getOption('MessageQueueURI',
                                         '').replace(" ", "").split(','):
                result = createConsumer(uri, self.processMessage)
                if not result['OK']:
                    self.log.error('Failed to create a consumer from URI: %s' %
                                   uri)
                    continue
                else:
                    self.log.info(
                        'Successfully created a consumer from URI: %s' % uri)

                self.consumers.append(result['Value'])

            if len(self.consumers) > 0:
                return S_OK('Successfully created at least one consumer')
            else:
                return S_ERROR('Failed to create at least one consumer')

        # if everything is OK just update the counter
        else:
            self.messagesCountOld = self.messagesCount

    def processMessage(self, headers, body):
        '''
    Process a message containing perfSONAR data and store the result in the Accounting DB.
    Supports packet-loss-rate and one-way-delay metrics send in 5min summaries.

    Function is designed to be an MQConsumer callback function.
    '''

        self.messagesCount += 1
        metadata = {
            'SourceIP': body['meta']['source'],
            'SourceHostName': body['meta']['input_source'],
            'DestinationIP': body['meta']['destination'],
            'DestinationHostName': body['meta']['input_destination'],
        }

        try:
            metadata['Source'] = self.nameDictionary[body['meta']
                                                     ['input_source']]
            metadata['Destination'] = self.nameDictionary[
                body['meta']['input_destination']]
        except KeyError as error:
            # messages with unsupported source or destination host name can be safely skipped
            self.skippedMessagesCount += 1
            self.log.debug(
                'Host "%s" does not exist in the host-to-dirac name dictionary (message skipped)'
                % error)
            return S_OK()

        metadataKey = ''
        for value in metadata.values():
            metadataKey += value

        if body.has_key('summaries'):
            for summary in body['summaries']:

                # we are interested in 5 minutes summaries only
                if summary['summary_window'] == '300':
                    for data in summary['summary_data']:
                        try:

                            # set the time and create a hash
                            startTime = datetime.utcfromtimestamp(
                                float(data[0]))
                            endTime = datetime.utcfromtimestamp(
                                float(data[0]) + 300)

                            key = metadataKey
                            key += str(startTime)
                            key += str(endTime)

                            # use existing or create a new temporary accounting object to store the data in DB
                            if self.buffer.has_key(key):
                                net = self.buffer[key]['object']
                            else:
                                net = Network()
                                net.setStartTime(startTime)
                                net.setEndTime(endTime)
                                net.setValuesFromDict(metadata)

                            # look for supported event types
                            if summary['event_type'] == 'packet-loss-rate':
                                self.PLRMetricCount += 1
                                net.setValueByKey('PacketLossRate',
                                                  data[1] * 100)
                            elif summary[
                                    'event_type'] == 'histogram-owdelay' and summary[
                                        'summary_type'] == 'statistics':
                                self.OWDMetricCount += 1

                                # skip metrics with invalid data
                                if data[1]['mean'] < 0 or data[1][
                                        'minimum'] < 0 or data[1][
                                            'maximum'] < 0:
                                    raise Exception(
                                        'Invalid OWD metric (%s, %s, %s)' %
                                        (data[1]['mean'], data[1]['minimum'],
                                         data[1]['maximum']))
                                else:
                                    # approximate jitter value as OWDMax - OWDMin
                                    net.setValueByKey(
                                        'Jitter', data[1]['maximum'] -
                                        data[1]['minimum'])
                                    net.setValueByKey('OneWayDelay',
                                                      data[1]['mean'])
                            else:
                                self.skippedMetricCount += 1
                                continue

                            self.buffer[key] = {
                                'addTime': datetime.now(),
                                'object': net
                            }

                        # suppress all exceptions to protect the listener thread
                        except Exception as e:
                            self.skippedMetricCount += 1
                            self.log.warn(
                                'Metric skipped because of an exception: %s' %
                                e)

                else:
                    self.skippedMessagesCount += 1

        return S_OK()

    def commitData(self):
        '''
    Iterates through all object in the temporary buffer and commit objects to DB
    if both packet-loss-rate and one-way-delay values are set.

    Objects in the buffer older than self.bufferTiemout seconds which still have
    missing data are removed permanently (a warning is issued).
    '''

        now = datetime.now()
        removed = False

        for key, value in self.buffer.items():
            result = value['object'].checkValues()
            if not result['OK']:
                if (now -
                        value['addTime']).total_seconds() > self.bufferTiemout:
                    del self.buffer[key]
                    self.removedCount += 1
                    removed = True
            else:
                value['object'].delayedCommit()
                del self.buffer[key]
                self.insertedCount += 1

        if removed:
            self.log.warn(
                'Network accounting object(s) has been removed because of missing data'
            )

        return S_OK()

    def showStatistics(self):
        ''' Display different statistics as info messages in the log file.
    '''

        self.log.info("\tReceived messages:           %s" % self.messagesCount)
        self.log.info("\tSkipped messages:            %s" %
                      self.skippedMessagesCount)
        self.log.info("\tPacket-Loss-Rate metrics:    %s" %
                      self.PLRMetricCount)
        self.log.info("\tOne-Way-Delay metrics:       %s" %
                      self.OWDMetricCount)
        self.log.info("\tSkipped metrics:             %s" %
                      self.skippedMetricCount)
        self.log.info("")
        self.log.info("\tObjects in the buffer:       %s" % len(self.buffer))
        self.log.info("\tObjects inserted to DB:      %s" % self.insertedCount)
        self.log.info("\tPermanently removed objects: %s" % self.removedCount)

        return S_OK()
class StorageMonitorAgent( AgentModule ):

  addressTo = ''
  addressFrom = ''
  voName = ''
  bdii = 'lcg-bdii.cern.ch:2170'
  productionSEs = []
  subject = "StorageMonitorAgent"
 
  def initialize( self ):

    self.addressTo = self.am_getOption( 'MailTo', self.addressTo )
    self.addressFrom = self.am_getOption( 'MailFrom', self.addressFrom )

    if self.addressTo and self.addressFrom:
      self.log.info( "MailTo", self.addressTo )
      self.log.info( "MailFrom", self.addressFrom )

    self.productionSEs = self.am_getOption( 'ProductionSEs', self.productionSEs)
    if self.productionSEs:
      self.log.info( "ProductionSEs", self.productionSEs )
    else:
      self.log.fatal( "ProductionSEs option not defined for agent" )
      return S_ERROR()

    self.voName = self.am_getOption( 'VirtualOrganization', self.voName )
    if self.voName:
      self.log.info( "Agent will manage VO", self.voName )
    else:
      self.log.fatal( "VirtualOrganization option not defined for agent" )
      return S_ERROR()

    self.bdii = self.am_getOption( 'Bdii', self.bdii )
    if self.bdii:
      self.log.info( "Bdii", self.bdii )

    self.csAPI = CSAPI()
    return self.csAPI.initialize()

  def execute( self ):
    """ General agent execution method
    """
    # Get a "fresh" copy of the CS data
    result = self.csAPI.downloadCSData()
    if not result['OK']:
      self.log.warn( "Could not download a fresh copy of the CS data", result[ 'Message' ] )

    # Execute command to retrieve storage usage information
    cmdTuple = ['lcg-infosites', '--vo', self.voName , 'se']
    self.log.info( "Executing %s" % cmdTuple )
    ret = systemCall( 0, cmdTuple, env = {'LCG_GFAL_INFOSYS':self.bdii} )

    if not ret['OK']:
      return ret
    elif not ret['Value'][1] != '':
      self.log.error( "Error while executing %s" % cmdTuple )
      return S_ERROR()

    # initialize sedict
    sedict = {}
    for SE in self.productionSEs:
      sedict[SE] = []

    fields = ['SE', 'Available(TB)', 'Used(TB)', 'Total(TB)', 'Used(%)' ]
    records = []
    fullSEList = []

    for se in ret['Value'][1].split( '\n' ):
      if len( se.split() ) == 4:
        spacedict = {}
        SE = se.split()[3]
        if SE in self.productionSEs and se.split()[0] != 'n.a' and se.split()[1] != 'n.a':
          # ## convert into TB
          available = float( se.split()[0] ) / 1e9
          used = float( se.split()[1] ) / 1e9
          spacedict['Available'] = available
          spacedict['Used'] = used
          spacedict['Total'] = available + used
          sedict[SE].append(spacedict)

    for SE in self.productionSEs:
      for spacedict in sedict[SE]:
        available = '%.1f' % spacedict['Available']
        used = '%.1f' % spacedict['Used']
        total = '%.1f' % spacedict['Total']
        fraction_used = spacedict['Used'] / spacedict['Total'] * 100
        if fraction_used > 90.:
          fullSEList.append(SE)
          self.log.warn( "%s full at %.1f%%" % (SE, fraction_used) )
        fraction_used = '%.1f' % fraction_used
        records.append( [SE, available, used, total, fraction_used] )

    body = printTable( fields, records, printOut = False )

    if len(fullSEList) > 0:
      self.subject = 'CRITICAL storage usage beyond 90%%: %s' % ( ', '.join( fullSEList ) )

    if self.addressTo and self.addressFrom:
      notification = NotificationClient()
      result = notification.sendMail( self.addressTo, self.subject, body, self.addressFrom, localAttempt = False )
      if not result['OK']:
        self.log.error( 'Can not send  notification mail', result['Message'] )

    return S_OK()
Example #11
0
class Bdii2CSAgent(AgentModule):

  def __init__(self, *args, **kwargs):
    """ Defines default parameters
    """

    super(Bdii2CSAgent, self).__init__(*args, **kwargs)

    self.addressTo = ''
    self.addressFrom = ''
    self.voName = []
    self.subject = self.am_getModuleParam('fullName')
    self.alternativeBDIIs = []
    self.voBdiiCEDict = {}
    self.voBdiiSEDict = {}
    self.host = 'lcg-bdii.cern.ch:2170'
    self.glue2URLs = []
    self.glue2Only = True

    self.csAPI = None

    # What to get
    self.processCEs = True
    self.selectedSites = []

    # Update the CS or not?
    self.dryRun = False

  def initialize(self):
    """ Gets run paramaters from the configuration
    """

    self.addressTo = self.am_getOption('MailTo', self.addressTo)
    self.addressFrom = self.am_getOption('MailFrom', self.addressFrom)
    # Create a list of alternative bdii urls
    self.alternativeBDIIs = self.am_getOption('AlternativeBDIIs', self.alternativeBDIIs)
    self.host = self.am_getOption('Host', self.host)
    self.glue2URLs = self.am_getOption('GLUE2URLs', self.glue2URLs)
    self.glue2Only = self.am_getOption('GLUE2Only', self.glue2Only)

    # Check if the bdii url is appended by a port number, if not append the default 2170
    for index, url in enumerate(self.alternativeBDIIs):
      if not url.split(':')[-1].isdigit():
        self.alternativeBDIIs[index] += ':2170'
    if self.addressTo and self.addressFrom:
      self.log.info("MailTo", self.addressTo)
      self.log.info("MailFrom", self.addressFrom)
    if self.alternativeBDIIs:
      self.log.info("AlternativeBDII URLs:", self.alternativeBDIIs)

    self.processCEs = self.am_getOption('ProcessCEs', self.processCEs)
    self.selectedSites = self.am_getOption('SelectedSites', [])
    self.dryRun = self.am_getOption('DryRun', self.dryRun)

    self.voName = self.am_getOption('VirtualOrganization', self.voName)
    if not self.voName:
      self.voName = self.am_getOption('VO', [])
    if not self.voName or (len(self.voName) == 1 and self.voName[0].lower() == 'all'):
      # Get all VOs defined in the configuration
      self.voName = []
      result = getVOs()
      if result['OK']:
        vos = result['Value']
        for vo in vos:
          vomsVO = getVOOption(vo, "VOMSName")
          if vomsVO:
            self.voName.append(vomsVO)

    if self.voName:
      self.log.info("Agent will manage VO(s) %s" % self.voName)
    else:
      self.log.fatal("VirtualOrganization option not defined for agent")
      return S_ERROR()

    self.csAPI = CSAPI()
    return self.csAPI.initialize()

  def execute(self):
    """ General agent execution method
    """
    self.voBdiiCEDict = {}

    # Get a "fresh" copy of the CS data
    result = self.csAPI.downloadCSData()
    if not result['OK']:
      self.log.warn("Could not download a fresh copy of the CS data", result['Message'])

    # Refresh the configuration from the master server
    gConfig.forceRefresh(fromMaster=True)

    if self.processCEs:
      self.__lookForNewCEs()
      self.__updateCEs()
    return S_OK()

  def __lookForNewCEs(self):
    """ Look up BDII for CEs not yet present in the DIRAC CS
    """

    bannedCEs = self.am_getOption('BannedCEs', [])

    for vo in self.voName:
      # get the known CEs for a given VO, so we can know the unknowns, or no longer supported,
      # for a VO
      res = getQueues(community=vo)
      if not res['OK']:
        return res

      knownCEs = set()
      for _site, ces in res['Value'].items():
        knownCEs.update(ces)
      knownCEs.update(bannedCEs)

      result = self.__getBdiiCEInfo(vo)
      if not result['OK']:
        continue
      bdiiInfo = result['Value']
      result = getGridCEs(vo, bdiiInfo=bdiiInfo, ceBlackList=knownCEs)
      if not result['OK']:
        self.log.error('Failed to get unused CEs', result['Message'])
        continue  # next VO
      siteDict = result['Value']
      unknownCEs = set(result['UnknownCEs']) - set(bannedCEs)

      body = ''
      for site in siteDict:
        newCEs = set(siteDict[site])  # pylint: disable=no-member
        if not newCEs:
          continue

        ceString = ''
        for ce in newCEs:
          queueString = ''
          ceInfo = bdiiInfo[site]['CEs'][ce]
          newCEString = "CE: %s, GOCDB Site Name: %s" % (ce, site)
          systemTuple = siteDict[site][ce]['System']
          osString = "%s_%s_%s" % (systemTuple)
          newCEString = "\n%s\n%s\n" % (newCEString, osString)
          for queue in ceInfo['Queues']:
            queueStatus = ceInfo['Queues'][queue].get('GlueCEStateStatus', 'UnknownStatus')
            if 'production' in queueStatus.lower():
              ceType = ceInfo['Queues'][queue].get('GlueCEImplementationName', '')
              queueString += "   %s %s %s\n" % (queue, queueStatus, ceType)
          if queueString:
            ceString += newCEString
            ceString += "Queues:\n"
            ceString += queueString

        if ceString:
          body += ceString

      if siteDict:
        body = "\nWe are glad to inform You about new CE(s) possibly suitable for %s:\n" % vo + body
        body += "\n\nTo suppress information about CE add its name to BannedCEs list.\n"
        body += "Add new Sites/CEs for vo %s with the command:\n" % vo
        body += "dirac-admin-add-resources --vo %s --ce\n" % vo

      if unknownCEs:
        body += '\n\n'
        body += 'There is no (longer) information about the following CEs for the %s VO.\n' % vo
        body += '\n'.join(sorted(unknownCEs))
        body += '\n\n'

      if body:
        self.log.info(body)
        if self.addressTo and self.addressFrom:
          notification = NotificationClient()
          result = notification.sendMail(self.addressTo, self.subject, body, self.addressFrom,
                                         localAttempt=False, avoidSpam=True)
          if not result['OK']:
            self.log.error('Can not send new site notification mail', result['Message'])

    return S_OK()

  def __getBdiiCEInfo(self, vo):

    if vo in self.voBdiiCEDict:
      return S_OK(self.voBdiiCEDict[vo])
    self.log.info("Check for available CEs for VO", vo)
    totalResult = S_OK({})
    message = ''

    mainResult = getBdiiCEInfo(vo, host=self.host, glue2=self.glue2Only)
    if not mainResult['OK']:
      self.log.error("Failed getting information from default bdii", mainResult['Message'])
      message = mainResult['Message']

    for bdii in reversed(self.alternativeBDIIs):
      resultAlt = getBdiiCEInfo(vo, host=bdii, glue2=self.glue2Only)
      if resultAlt['OK']:
        totalResult['Value'].update(resultAlt['Value'])
      else:
        self.log.error("Failed getting information from %s " % bdii, resultAlt['Message'])
        message = (message + "\n" + resultAlt['Message']).strip()

    for glue2URL in self.glue2URLs:
      if self.glue2Only:
        break
      resultGlue2 = getBdiiCEInfo(vo, host=glue2URL, glue2=True)
      if resultGlue2['OK']:
        totalResult['Value'].update(resultGlue2['Value'])
      else:
        self.log.error("Failed getting GLUE2 information for", "%s, %s: %s" %
                       (glue2URL, vo, resultGlue2['Message']))
        message = (message + "\n" + resultGlue2['Message']).strip()

    if mainResult['OK']:
      totalResult['Value'].update(mainResult['Value'])

    if not totalResult['Value'] and message:  # Dict is empty and we have an error message
      self.log.error("Error during BDII request", message)
      totalResult = S_ERROR(message)
    else:
      self.voBdiiCEDict[vo] = totalResult['Value']
      self.__purgeSites(totalResult['Value'])

    return totalResult

  def __updateCEs(self):
    """ Update the Site/CE/queue settings in the CS if they were changed in the BDII
    """

    bdiiChangeSet = set()

    for vo in self.voName:
      result = self.__getBdiiCEInfo(vo)
      if not result['OK']:
        continue
      ceBdiiDict = result['Value']
      result = getSiteUpdates(vo, bdiiInfo=ceBdiiDict, log=self.log)
      if not result['OK']:
        continue
      bdiiChangeSet = bdiiChangeSet.union(result['Value'])

    # We have collected all the changes, consolidate VO settings
    result = self.__updateCS(bdiiChangeSet)
    return result

  def __purgeSites(self, ceBdiiDict):
    """Remove all sites that are not in self.selectedSites.

    Modifies the ceBdiiDict!
    """
    if not self.selectedSites:
      return
    for site in list(ceBdiiDict):
      ces = list(ceBdiiDict[site]['CEs'])
      if not ces:
        self.log.error("No CE information for site:", site)
        continue
      siteInCS = 'Not_In_CS'
      for ce in ces:
        res = getCESiteMapping(ce)
        if not res['OK']:
          self.log.error("Failed to get DIRAC site name for ce", "%s: %s" % (ce, res['Message']))
          continue
        # if the ce is not in the CS the returned value will be empty
        if ce in res['Value']:
          siteInCS = res['Value'][ce]
          break
      self.log.debug("Checking site %s (%s), aka %s" % (site, ces, siteInCS))
      if siteInCS in self.selectedSites:
        continue
      self.log.info("Dropping site %s, aka %s" % (site, siteInCS))
      ceBdiiDict.pop(site)
    return

  def __updateCS(self, bdiiChangeSet):

    queueVODict = {}
    changeSet = set()
    for entry in bdiiChangeSet:
      section, option, _value, new_value = entry
      if option == "VO":
        queueVODict.setdefault(section, set())
        queueVODict[section] = queueVODict[section].union(set(new_value.split(',')))
      else:
        changeSet.add(entry)
    for section, VOs in queueVODict.items():  # can be an iterator
      changeSet.add((section, 'VO', '', ','.join(VOs)))

    if changeSet:
      changeList = sorted(changeSet)
      body = '\n'.join(["%s/%s %s -> %s" % entry for entry in changeList])
      if body and self.addressTo and self.addressFrom:
        notification = NotificationClient()
        result = notification.sendMail(self.addressTo, self.subject, body, self.addressFrom, localAttempt=False,
                                       avoidSpam=True)

      if body:
        self.log.info('The following configuration changes were detected:')
        self.log.info(body)

      for section, option, value, new_value in changeSet:
        if value == 'Unknown' or not value:
          self.csAPI.setOption(cfgPath(section, option), new_value)
        else:
          self.csAPI.modifyValue(cfgPath(section, option), new_value)

      if self.dryRun:
        self.log.info("Dry Run: CS won't be updated")
        self.csAPI.showDiff()
      else:
        result = self.csAPI.commit()
        if not result['OK']:
          self.log.error("Error while committing to CS", result['Message'])
        else:
          self.log.info("Successfully committed %d changes to CS" % len(changeList))
        return result
    else:
      self.log.info("No changes found")
      return S_OK()
Example #12
0
class CE2CSAgent(AgentModule):

    addressTo = ""
    addressFrom = ""
    voName = ""
    subject = "CE2CSAgent"
    alternativeBDIIs = []

    def initialize(self):

        # TODO: Have no default and if no mail is found then use the diracAdmin group
        # and resolve all associated mail addresses.
        self.addressTo = self.am_getOption("MailTo", self.addressTo)
        self.addressFrom = self.am_getOption("MailFrom", self.addressFrom)
        # Create a list of alternative bdii urls
        self.alternativeBDIIs = self.am_getOption("AlternativeBDIIs", [])
        # Check if the bdii url is appended by a port number, if not append the default 2170
        for index, url in enumerate(self.alternativeBDIIs):
            if not url.split(":")[-1].isdigit():
                self.alternativeBDIIs[index] += ":2170"
        if self.addressTo and self.addressFrom:
            self.log.info("MailTo", self.addressTo)
            self.log.info("MailFrom", self.addressFrom)
        if self.alternativeBDIIs:
            self.log.info("AlternativeBDII URLs:", self.alternativeBDIIs)
        self.subject = "CE2CSAgent"

        # This sets the Default Proxy to used as that defined under
        # /Operations/Shifter/TestManager
        # the shifterProxy option in the Configuration can be used to change this default.
        self.am_setOption("shifterProxy", "TestManager")

        self.voName = self.am_getOption("VirtualOrganization", [])
        if not self.voName:
            vo = getVO()
            if vo:
                self.voName = [vo]

        if self.voName:
            self.log.info("Agent will manage VO(s) %s" % self.voName)
        else:
            self.log.fatal("VirtualOrganization option not defined for agent")
            return S_ERROR()

        self.csAPI = CSAPI()
        return self.csAPI.initialize()

    def execute(self):

        self.log.info("Start Execution")
        result = getProxyInfo()
        if not result["OK"]:
            return result
        infoDict = result["Value"]
        self.log.info(formatProxyInfoAsString(infoDict))

        # Get a "fresh" copy of the CS data
        result = self.csAPI.downloadCSData()
        if not result["OK"]:
            self.log.warn("Could not download a fresh copy of the CS data", result["Message"])

        self.__lookForCE()
        self.__infoFromCE()
        self.log.info("End Execution")
        return S_OK()

    def __checkAlternativeBDIISite(self, fun, *args):
        if self.alternativeBDIIs:
            self.log.warn("Trying to use alternative BDII sites")
            for site in self.alternativeBDIIs:
                self.log.info("Trying to contact alternative BDII", site)
                if len(args) == 1:
                    result = fun(args[0], host=site)
                elif len(args) == 2:
                    result = fun(args[0], vo=args[1], host=site)
                if not result["OK"]:
                    self.log.error("Problem contacting alternative BDII", result["Message"])
                elif result["OK"]:
                    return result
            self.log.warn("Also checking alternative BDII sites failed")
            return result

    def __lookForCE(self):

        knownCEs = self.am_getOption("BannedCEs", [])

        result = gConfig.getSections("/Resources/Sites")
        if not result["OK"]:
            return
        grids = result["Value"]

        for grid in grids:
            result = gConfig.getSections("/Resources/Sites/%s" % grid)
            if not result["OK"]:
                return
            sites = result["Value"]

            for site in sites:
                opt = gConfig.getOptionsDict("/Resources/Sites/%s/%s" % (grid, site))["Value"]
                ces = List.fromChar(opt.get("CE", ""))
                knownCEs += ces

        response = ""
        for vo in self.voName:
            self.log.info("Check for available CEs for VO", vo)
            response = ldapCEState("", vo)
            if not response["OK"]:
                self.log.error("Error during BDII request", response["Message"])
                response = self.__checkAlternativeBDIISite(ldapCEState, "", vo)
                return response

            newCEs = {}
            for queue in response["Value"]:
                try:
                    queueName = queue["GlueCEUniqueID"]
                except:
                    continue

                ceName = queueName.split(":")[0]
                if not ceName in knownCEs:
                    newCEs[ceName] = None
                    self.log.debug("New CE", ceName)

            body = ""
            possibleNewSites = []
            for ce in newCEs.iterkeys():
                response = ldapCluster(ce)
                if not response["OK"]:
                    self.log.warn("Error during BDII request", response["Message"])
                    response = self.__checkAlternativeBDIISite(ldapCluster, ce)
                    continue
                clusters = response["Value"]
                if len(clusters) != 1:
                    self.log.warn("Error in cluster length", " CE %s Length %d" % (ce, len(clusters)))
                if len(clusters) == 0:
                    continue
                cluster = clusters[0]
                fkey = cluster.get("GlueForeignKey", [])
                if type(fkey) == type(""):
                    fkey = [fkey]
                nameBDII = None
                for entry in fkey:
                    if entry.count("GlueSiteUniqueID"):
                        nameBDII = entry.split("=")[1]
                        break
                if not nameBDII:
                    continue

                ceString = "CE: %s, GOCDB Name: %s" % (ce, nameBDII)
                self.log.info(ceString)

                response = ldapCE(ce)
                if not response["OK"]:
                    self.log.warn("Error during BDII request", response["Message"])
                    response = self.__checkAlternativeBDIISite(ldapCE, ce)
                    continue

                ceInfos = response["Value"]
                if len(ceInfos):
                    ceInfo = ceInfos[0]
                    systemName = ceInfo.get("GlueHostOperatingSystemName", "Unknown")
                    systemVersion = ceInfo.get("GlueHostOperatingSystemVersion", "Unknown")
                    systemRelease = ceInfo.get("GlueHostOperatingSystemRelease", "Unknown")
                else:
                    systemName = "Unknown"
                    systemVersion = "Unknown"
                    systemRelease = "Unknown"

                osString = "SystemName: %s, SystemVersion: %s, SystemRelease: %s" % (
                    systemName,
                    systemVersion,
                    systemRelease,
                )
                self.log.info(osString)

                response = ldapCEState(ce, vo)
                if not response["OK"]:
                    self.log.warn("Error during BDII request", response["Message"])
                    response = self.__checkAlternativeBDIISite(ldapCEState, ce, vo)
                    continue

                newCEString = "\n\n%s\n%s" % (ceString, osString)
                usefull = False
                ceStates = response["Value"]
                for ceState in ceStates:
                    queueName = ceState.get("GlueCEUniqueID", "UnknownName")
                    queueStatus = ceState.get("GlueCEStateStatus", "UnknownStatus")

                    queueString = "%s %s" % (queueName, queueStatus)
                    self.log.info(queueString)
                    newCEString += "\n%s" % queueString
                    if queueStatus.count("Production"):
                        usefull = True
                if usefull:
                    body += newCEString
                    possibleNewSites.append("dirac-admin-add-site DIRACSiteName %s %s" % (nameBDII, ce))
            if body:
                body = "We are glad to inform You about new CE(s) possibly suitable for %s:\n" % vo + body
                body += "\n\nTo suppress information about CE add its name to BannedCEs list."
                for possibleNewSite in possibleNewSites:
                    body = "%s\n%s" % (body, possibleNewSite)
                self.log.info(body)
                if self.addressTo and self.addressFrom:
                    notification = NotificationClient()
                    result = notification.sendMail(
                        self.addressTo, self.subject, body, self.addressFrom, localAttempt=False
                    )

        return S_OK()

    def __infoFromCE(self):

        sitesSection = cfgPath("Resources", "Sites")
        result = gConfig.getSections(sitesSection)
        if not result["OK"]:
            return
        grids = result["Value"]

        changed = False
        body = ""

        for grid in grids:
            gridSection = cfgPath(sitesSection, grid)
            result = gConfig.getSections(gridSection)
            if not result["OK"]:
                return
            sites = result["Value"]

            for site in sites:
                siteSection = cfgPath(gridSection, site)
                opt = gConfig.getOptionsDict(siteSection)["Value"]
                name = opt.get("Name", "")
                if name:
                    coor = opt.get("Coordinates", "Unknown")
                    mail = opt.get("Mail", "Unknown")

                    result = ldapSite(name)
                    if not result["OK"]:
                        self.log.warn("BDII site %s: %s" % (name, result["Message"]))
                        result = self.__checkAlternativeBDIISite(ldapSite, name)

                    if result["OK"]:
                        bdiiSites = result["Value"]
                        if len(bdiiSites) == 0:
                            self.log.warn(name, "Error in BDII: leng = 0")
                        else:
                            if not len(bdiiSites) == 1:
                                self.log.warn(name, "Warning in BDII: leng = %d" % len(bdiiSites))

                            bdiiSite = bdiiSites[0]

                            try:
                                longitude = bdiiSite["GlueSiteLongitude"]
                                latitude = bdiiSite["GlueSiteLatitude"]
                                newcoor = "%s:%s" % (longitude, latitude)
                            except:
                                self.log.warn("Error in BDII coordinates")
                                newcoor = "Unknown"

                            try:
                                newmail = bdiiSite["GlueSiteSysAdminContact"].split(":")[-1].strip()
                            except:
                                self.log.warn("Error in BDII mail")
                                newmail = "Unknown"

                            self.log.debug("%s %s %s" % (name, newcoor, newmail))

                            if newcoor != coor:
                                self.log.info("%s" % (name), "%s -> %s" % (coor, newcoor))
                                if coor == "Unknown":
                                    self.csAPI.setOption(cfgPath(siteSection, "Coordinates"), newcoor)
                                else:
                                    self.csAPI.modifyValue(cfgPath(siteSection, "Coordinates"), newcoor)
                                changed = True

                            if newmail != mail:
                                self.log.info("%s" % (name), "%s -> %s" % (mail, newmail))
                                if mail == "Unknown":
                                    self.csAPI.setOption(cfgPath(siteSection, "Mail"), newmail)
                                else:
                                    self.csAPI.modifyValue(cfgPath(siteSection, "Mail"), newmail)
                                changed = True

                ceList = List.fromChar(opt.get("CE", ""))

                if not ceList:
                    self.log.warn(site, "Empty site list")
                    continue

                #      result = gConfig.getSections( cfgPath( siteSection,'CEs' )
                #      if not result['OK']:
                #        self.log.debug( "Section CEs:", result['Message'] )

                for ce in ceList:
                    ceSection = cfgPath(siteSection, "CEs", ce)
                    result = gConfig.getOptionsDict(ceSection)
                    if not result["OK"]:
                        self.log.debug("Section CE", result["Message"])
                        wnTmpDir = "Unknown"
                        arch = "Unknown"
                        os = "Unknown"
                        si00 = "Unknown"
                        pilot = "Unknown"
                        ceType = "Unknown"
                    else:
                        ceopt = result["Value"]
                        wnTmpDir = ceopt.get("wnTmpDir", "Unknown")
                        arch = ceopt.get("architecture", "Unknown")
                        os = ceopt.get("OS", "Unknown")
                        si00 = ceopt.get("SI00", "Unknown")
                        pilot = ceopt.get("Pilot", "Unknown")
                        ceType = ceopt.get("CEType", "Unknown")

                    result = ldapCE(ce)
                    if not result["OK"]:
                        self.log.warn("Error in BDII for %s" % ce, result["Message"])
                        result = self.__checkAlternativeBDIISite(ldapCE, ce)
                        continue
                    try:
                        bdiiCE = result["Value"][0]
                    except:
                        self.log.warn("Error in BDII for %s" % ce, result)
                        bdiiCE = None
                    if bdiiCE:
                        try:
                            newWNTmpDir = bdiiCE["GlueSubClusterWNTmpDir"]
                        except:
                            newWNTmpDir = "Unknown"
                        if wnTmpDir != newWNTmpDir and newWNTmpDir != "Unknown":
                            section = cfgPath(ceSection, "wnTmpDir")
                            self.log.info(section, " -> ".join((wnTmpDir, newWNTmpDir)))
                            if wnTmpDir == "Unknown":
                                self.csAPI.setOption(section, newWNTmpDir)
                            else:
                                self.csAPI.modifyValue(section, newWNTmpDir)
                            changed = True

                        try:
                            newArch = bdiiCE["GlueHostArchitecturePlatformType"]
                        except:
                            newArch = "Unknown"
                        if arch != newArch and newArch != "Unknown":
                            section = cfgPath(ceSection, "architecture")
                            self.log.info(section, " -> ".join((arch, newArch)))
                            if arch == "Unknown":
                                self.csAPI.setOption(section, newArch)
                            else:
                                self.csAPI.modifyValue(section, newArch)
                            changed = True

                        try:
                            newOS = "_".join(
                                (
                                    bdiiCE["GlueHostOperatingSystemName"],
                                    bdiiCE["GlueHostOperatingSystemVersion"],
                                    bdiiCE["GlueHostOperatingSystemRelease"],
                                )
                            )
                        except:
                            newOS = "Unknown"
                        if os != newOS and newOS != "Unknown":
                            section = cfgPath(ceSection, "OS")
                            self.log.info(section, " -> ".join((os, newOS)))
                            if os == "Unknown":
                                self.csAPI.setOption(section, newOS)
                            else:
                                self.csAPI.modifyValue(section, newOS)
                            changed = True
                            body = body + "OS was changed %s -> %s for %s at %s\n" % (os, newOS, ce, site)

                        try:
                            newSI00 = bdiiCE["GlueHostBenchmarkSI00"]
                        except:
                            newSI00 = "Unknown"
                        if si00 != newSI00 and newSI00 != "Unknown":
                            section = cfgPath(ceSection, "SI00")
                            self.log.info(section, " -> ".join((si00, newSI00)))
                            if si00 == "Unknown":
                                self.csAPI.setOption(section, newSI00)
                            else:
                                self.csAPI.modifyValue(section, newSI00)
                            changed = True

                        try:
                            rte = bdiiCE["GlueHostApplicationSoftwareRunTimeEnvironment"]
                            for vo in self.voName:
                                if vo.lower() == "lhcb":
                                    if "VO-lhcb-pilot" in rte:
                                        newPilot = "True"
                                    else:
                                        newPilot = "False"
                                else:
                                    newPilot = "Unknown"
                        except:
                            newPilot = "Unknown"
                        if pilot != newPilot and newPilot != "Unknown":
                            section = cfgPath(ceSection, "Pilot")
                            self.log.info(section, " -> ".join((pilot, newPilot)))
                            if pilot == "Unknown":
                                self.csAPI.setOption(section, newPilot)
                            else:
                                self.csAPI.modifyValue(section, newPilot)
                            changed = True

                    newVO = ""
                    for vo in self.voName:
                        result = ldapCEState(ce, vo)  # getBDIICEVOView
                        if not result["OK"]:
                            self.log.warn("Error in BDII for queue %s" % ce, result["Message"])
                            result = self.__checkAlternativeBDIISite(ldapCEState, ce, vo)
                            continue
                        try:
                            queues = result["Value"]
                        except:
                            self.log.warn("Error in BDII for queue %s" % ce, result["Massage"])
                            continue

                        newCEType = "Unknown"
                        for queue in queues:
                            try:
                                queueType = queue["GlueCEImplementationName"]
                            except:
                                queueType = "Unknown"
                            if newCEType == "Unknown":
                                newCEType = queueType
                            else:
                                if queueType != newCEType:
                                    self.log.warn(
                                        "Error in BDII for CE %s " % ce,
                                        "different CE types %s %s" % (newCEType, queueType),
                                    )

                        if newCEType == "ARC-CE":
                            newCEType = "ARC"

                        if ceType != newCEType and newCEType != "Unknown":
                            section = cfgPath(ceSection, "CEType")
                            self.log.info(section, " -> ".join((ceType, newCEType)))
                            if ceType == "Unknown":
                                self.csAPI.setOption(section, newCEType)
                            else:
                                self.csAPI.modifyValue(section, newCEType)
                            changed = True

                        for queue in queues:
                            try:
                                queueName = queue["GlueCEUniqueID"].split("/")[-1]
                            except:
                                self.log.warn("Error in queueName ", queue)
                                continue

                            try:
                                newMaxCPUTime = queue["GlueCEPolicyMaxCPUTime"]
                            except:
                                newMaxCPUTime = None

                            newSI00 = None
                            try:
                                caps = queue["GlueCECapability"]
                                if type(caps) == type(""):
                                    caps = [caps]
                                for cap in caps:
                                    if cap.count("CPUScalingReferenceSI00"):
                                        newSI00 = cap.split("=")[-1]
                            except:
                                newSI00 = None

                            queueSection = cfgPath(ceSection, "Queues", queueName)
                            result = gConfig.getOptionsDict(queueSection)
                            if not result["OK"]:
                                self.log.warn("Section Queues", result["Message"])
                                maxCPUTime = "Unknown"
                                si00 = "Unknown"
                                allowedVOs = [""]
                            else:
                                queueOpt = result["Value"]
                                maxCPUTime = queueOpt.get("maxCPUTime", "Unknown")
                                si00 = queueOpt.get("SI00", "Unknown")
                                if newVO == "":  # Remember previous iteration, if none - read from conf
                                    allowedVOs = queueOpt.get("VO", "").split(",")
                                else:  # Else use newVO, as it can contain changes, which aren't in conf yet
                                    allowedVOs = newVO.split(",")
                            if newMaxCPUTime and (maxCPUTime != newMaxCPUTime):
                                section = cfgPath(queueSection, "maxCPUTime")
                                self.log.info(section, " -> ".join((maxCPUTime, newMaxCPUTime)))
                                if maxCPUTime == "Unknown":
                                    self.csAPI.setOption(section, newMaxCPUTime)
                                else:
                                    self.csAPI.modifyValue(section, newMaxCPUTime)
                                changed = True

                            if newSI00 and (si00 != newSI00):
                                section = cfgPath(queueSection, "SI00")
                                self.log.info(section, " -> ".join((si00, newSI00)))
                                if si00 == "Unknown":
                                    self.csAPI.setOption(section, newSI00)
                                else:
                                    self.csAPI.modifyValue(section, newSI00)
                                changed = True

                            modifyVO = True  # Flag saying if we need VO option to change
                            newVO = ""
                            if allowedVOs != [""]:
                                for allowedVO in allowedVOs:
                                    allowedVO = allowedVO.strip()  # Get rid of spaces
                                    newVO += allowedVO
                                    if allowedVO == vo:  # Current VO has been already in list
                                        newVO = ""
                                        modifyVO = False  # Don't change anything
                                        break  # Skip next 'if', proceed to next VO
                                    newVO += ", "

                            if modifyVO:
                                section = cfgPath(queueSection, "VO")
                                newVO += vo
                                self.log.info(section, " -> ".join(("%s" % allowedVOs, newVO)))
                                if allowedVOs == [""]:
                                    self.csAPI.setOption(section, newVO)
                                else:
                                    self.csAPI.modifyValue(section, newVO)
                                changed = True

        if changed:
            self.log.info(body)
            if body and self.addressTo and self.addressFrom:
                notification = NotificationClient()
                result = notification.sendMail(self.addressTo, self.subject, body, self.addressFrom, localAttempt=False)

            return self.csAPI.commit()
        else:
            self.log.info("No changes found")
            return S_OK()
Example #13
0
class Bdii2CSAgent(AgentModule):

  def __init__(self, *args, **kwargs):
    """ Defines default parameters
    """

    super(Bdii2CSAgent, self).__init__(*args, **kwargs)

    self.addressTo = ''
    self.addressFrom = ''
    self.voName = []
    self.subject = "Bdii2CSAgent"
    self.alternativeBDIIs = []
    self.voBdiiCEDict = {}
    self.voBdiiSEDict = {}
    self.host = 'lcg-bdii.cern.ch:2170'
    self.glue2URLs = []
    self.glue2Only = False

    self.csAPI = None

    # What to get
    self.processCEs = True
    self.processSEs = False
    self.selectedSites = []

    # Update the CS or not?
    self.dryRun = False

  def initialize(self):
    """ Gets run paramaters from the configuration
    """

    self.addressTo = self.am_getOption('MailTo', self.addressTo)
    self.addressFrom = self.am_getOption('MailFrom', self.addressFrom)
    # Create a list of alternative bdii urls
    self.alternativeBDIIs = self.am_getOption('AlternativeBDIIs', self.alternativeBDIIs)
    self.host = self.am_getOption('Host', self.host)
    self.glue2URLs = self.am_getOption('GLUE2URLs', self.glue2URLs)
    self.glue2Only = self.am_getOption('GLUE2Only', self.glue2Only)

    # Check if the bdii url is appended by a port number, if not append the default 2170
    for index, url in enumerate(self.alternativeBDIIs):
      if not url.split(':')[-1].isdigit():
        self.alternativeBDIIs[index] += ':2170'
    if self.addressTo and self.addressFrom:
      self.log.info("MailTo", self.addressTo)
      self.log.info("MailFrom", self.addressFrom)
    if self.alternativeBDIIs:
      self.log.info("AlternativeBDII URLs:", self.alternativeBDIIs)

    self.processCEs = self.am_getOption('ProcessCEs', self.processCEs)
    self.processSEs = self.am_getOption('ProcessSEs', self.processSEs)
    self.selectedSites = self.am_getOption('SelectedSites', [])
    self.dryRun = self.am_getOption('DryRun', self.dryRun)

    self.voName = self.am_getOption('VirtualOrganization', self.voName)
    if not self.voName:
      self.voName = self.am_getOption('VO', [])
    if not self.voName or (len(self.voName) == 1 and self.voName[0].lower() == 'all'):
      # Get all VOs defined in the configuration
      self.voName = []
      result = getVOs()
      if result['OK']:
        vos = result['Value']
        for vo in vos:
          vomsVO = getVOOption(vo, "VOMSName")
          if vomsVO:
            self.voName.append(vomsVO)

    if self.voName:
      self.log.info("Agent will manage VO(s) %s" % self.voName)
    else:
      self.log.fatal("VirtualOrganization option not defined for agent")
      return S_ERROR()

    self.csAPI = CSAPI()
    return self.csAPI.initialize()

  def execute(self):
    """ General agent execution method
    """
    self.voBdiiCEDict = {}
    self.voBdiiSEDict = {}

    # Get a "fresh" copy of the CS data
    result = self.csAPI.downloadCSData()
    if not result['OK']:
      self.log.warn("Could not download a fresh copy of the CS data", result['Message'])

    # Refresh the configuration from the master server
    gConfig.forceRefresh(fromMaster=True)

    if self.processCEs:
      self.__lookForNewCEs()
      self.__updateCEs()
    if self.processSEs:
      self.__lookForNewSEs()
      self.__updateSEs()
    return S_OK()

  def __lookForNewCEs(self):
    """ Look up BDII for CEs not yet present in the DIRAC CS
    """

    bannedCEs = self.am_getOption('BannedCEs', [])
    result = getCEsFromCS()
    if not result['OK']:
      return result
    knownCEs = set(result['Value'])
    knownCEs = knownCEs.union(set(bannedCEs))

    for vo in self.voName:
      result = self.__getBdiiCEInfo(vo)
      if not result['OK']:
        continue
      bdiiInfo = result['Value']
      result = getGridCEs(vo, bdiiInfo=bdiiInfo, ceBlackList=knownCEs)
      if not result['OK']:
        self.log.error('Failed to get unused CEs', result['Message'])
      siteDict = result['Value']
      body = ''
      for site in siteDict:
        newCEs = set(siteDict[site].keys())  # pylint: disable=no-member
        if not newCEs:
          continue

        ceString = ''
        for ce in newCEs:
          queueString = ''
          ceInfo = bdiiInfo[site]['CEs'][ce]
          newCEString = "CE: %s, GOCDB Site Name: %s" % (ce, site)
          systemTuple = siteDict[site][ce]['System']
          osString = "%s_%s_%s" % (systemTuple)
          newCEString = "\n%s\n%s\n" % (newCEString, osString)
          for queue in ceInfo['Queues']:
            queueStatus = ceInfo['Queues'][queue].get('GlueCEStateStatus', 'UnknownStatus')
            if 'production' in queueStatus.lower():
              ceType = ceInfo['Queues'][queue].get('GlueCEImplementationName', '')
              queueString += "   %s %s %s\n" % (queue, queueStatus, ceType)
          if queueString:
            ceString += newCEString
            ceString += "Queues:\n"
            ceString += queueString

        if ceString:
          body += ceString

      if body:
        body = "\nWe are glad to inform You about new CE(s) possibly suitable for %s:\n" % vo + body
        body += "\n\nTo suppress information about CE add its name to BannedCEs list.\n"
        body += "Add new Sites/CEs for vo %s with the command:\n" % vo
        body += "dirac-admin-add-resources --vo %s --ce\n" % vo
        self.log.info(body)
        if self.addressTo and self.addressFrom:
          notification = NotificationClient()
          result = notification.sendMail(self.addressTo, self.subject, body, self.addressFrom,
                                         localAttempt=False, avoidSpam=True)
          if not result['OK']:
            self.log.error('Can not send new site notification mail', result['Message'])

    return S_OK()

  def __getBdiiCEInfo(self, vo):

    if vo in self.voBdiiCEDict:
      return S_OK(self.voBdiiCEDict[vo])
    self.log.info("Check for available CEs for VO", vo)
    totalResult = S_OK({})
    message = ''

    mainResult = getBdiiCEInfo(vo, host=self.host, glue2=self.glue2Only)
    if not mainResult['OK']:
      self.log.error("Failed getting information from default bdii", mainResult['Message'])
      message = mainResult['Message']

    for bdii in reversed(self.alternativeBDIIs):
      resultAlt = getBdiiCEInfo(vo, host=bdii, glue2=self.glue2Only)
      if resultAlt['OK']:
        totalResult['Value'].update(resultAlt['Value'])
      else:
        self.log.error("Failed getting information from %s " % bdii, resultAlt['Message'])
        message = (message + "\n" + resultAlt['Message']).strip()

    for glue2URL in self.glue2URLs:
      if self.glue2Only:
        break
      resultGlue2 = getBdiiCEInfo(vo, host=glue2URL, glue2=True)
      if resultGlue2['OK']:
        totalResult['Value'].update(resultGlue2['Value'])
      else:
        self.log.error("Failed getting GLUE2 information for", "%s, %s: %s" %
                       (glue2URL, vo, resultGlue2['Message']))
        message = (message + "\n" + resultGlue2['Message']).strip()

    if mainResult['OK']:
      totalResult['Value'].update(mainResult['Value'])

    if not totalResult['Value'] and message:  # Dict is empty and we have an error message
      self.log.error("Error during BDII request", message)
      totalResult = S_ERROR(message)
    else:
      self.voBdiiCEDict[vo] = totalResult['Value']
    return totalResult

  def __getBdiiSEInfo(self, vo):

    if vo in self.voBdiiSEDict:
      return S_OK(self.voBdiiSEDict[vo])
    self.log.info("Check for available SEs for VO", vo)
    result = getBdiiSEInfo(vo)
    message = ''
    if not result['OK']:
      message = result['Message']
      for bdii in self.alternativeBDIIs:
        result = getBdiiSEInfo(vo, host=bdii)
        if result['OK']:
          break
    if not result['OK']:
      if message:
        self.log.error("Error during BDII request", message)
      else:
        self.log.error("Error during BDII request", result['Message'])
    else:
      self.voBdiiSEDict[vo] = result['Value']
    return result

  def __updateCEs(self):
    """ Update the Site/CE/queue settings in the CS if they were changed in the BDII
    """

    bdiiChangeSet = set()

    for vo in self.voName:
      result = self.__getBdiiCEInfo(vo)
      if not result['OK']:
        continue
      ceBdiiDict = result['Value']
      self.__purgeSites(ceBdiiDict)
      result = getSiteUpdates(vo, bdiiInfo=ceBdiiDict, log=self.log)
      if not result['OK']:
        continue
      bdiiChangeSet = bdiiChangeSet.union(result['Value'])

    # We have collected all the changes, consolidate VO settings
    result = self.__updateCS(bdiiChangeSet)
    return result

  def __purgeSites(self, ceBdiiDict):
    """Remove all sites that are not in self.selectedSites.

    Modifies the ceBdiiDict!
    """
    if not self.selectedSites:
      return
    for site in list(ceBdiiDict):
      ces = list(ceBdiiDict[site]['CEs'])
      if not ces:
        self.log.error("No CE information for site:", site)
        continue
      diracSiteName = getSiteForCE(ces[0])
      if not diracSiteName['OK']:
        self.log.error("Failed to get DIRAC site name for ce", "%s: %s" % (ces[0], diracSiteName['Message']))
        continue
      self.log.debug("Checking site %s (%s), aka %s" % (site, ces, diracSiteName['Value']))
      if diracSiteName['Value'] in self.selectedSites:
        continue
      self.log.info("Dropping site %s, aka %s" % (site, diracSiteName))
      ceBdiiDict.pop(site)
    return

  def __updateCS(self, bdiiChangeSet):

    queueVODict = {}
    changeSet = set()
    for entry in bdiiChangeSet:
      section, option, _value, new_value = entry
      if option == "VO":
        queueVODict.setdefault(section, set())
        queueVODict[section] = queueVODict[section].union(set(new_value.split(',')))
      else:
        changeSet.add(entry)
    for section, VOs in queueVODict.items():
      changeSet.add((section, 'VO', '', ','.join(VOs)))

    if changeSet:
      changeList = sorted(changeSet)
      body = '\n'.join(["%s/%s %s -> %s" % entry for entry in changeList])
      if body and self.addressTo and self.addressFrom:
        notification = NotificationClient()
        result = notification.sendMail(self.addressTo, self.subject, body, self.addressFrom, localAttempt=False)

      if body:
        self.log.info('The following configuration changes were detected:')
        self.log.info(body)

      for section, option, value, new_value in changeSet:
        if value == 'Unknown' or not value:
          self.csAPI.setOption(cfgPath(section, option), new_value)
        else:
          self.csAPI.modifyValue(cfgPath(section, option), new_value)

      if self.dryRun:
        self.log.info("Dry Run: CS won't be updated")
        self.csAPI.showDiff()
      else:
        result = self.csAPI.commit()
        if not result['OK']:
          self.log.error("Error while committing to CS", result['Message'])
        else:
          self.log.info("Successfully committed %d changes to CS" % len(changeList))
        return result
    else:
      self.log.info("No changes found")
      return S_OK()

  def __lookForNewSEs(self):
    """ Look up BDII for SEs not yet present in the DIRAC CS
    """

    bannedSEs = self.am_getOption('BannedSEs', [])
    result = getSEsFromCS()
    if not result['OK']:
      return result
    knownSEs = set(result['Value'])
    knownSEs = knownSEs.union(set(bannedSEs))

    for vo in self.voName:
      result = self.__getBdiiSEInfo(vo)
      if not result['OK']:
        continue
      bdiiInfo = result['Value']
      result = getGridSRMs(vo, bdiiInfo=bdiiInfo, srmBlackList=knownSEs)
      if not result['OK']:
        continue
      siteDict = result['Value']
      body = ''
      for site in siteDict:
        newSEs = set(siteDict[site].keys())  # pylint: disable=no-member
        if not newSEs:
          continue
        for se in newSEs:
          body += '\n New SE %s available at site %s:\n' % (se, site)
          backend = siteDict[site][se]['SE'].get('GlueSEImplementationName', 'Unknown')
          size = siteDict[site][se]['SE'].get('GlueSESizeTotal', 'Unknown')
          body += '  Backend %s, Size %s' % (backend, size)

      if body:
        body = "\nWe are glad to inform You about new SE(s) possibly suitable for %s:\n" % vo + body
        body += "\n\nTo suppress information about an SE add its name to BannedSEs list.\n"
        body += "Add new SEs for vo %s with the command:\n" % vo
        body += "dirac-admin-add-resources --vo %s --se\n" % vo
        self.log.info(body)
        if self.addressTo and self.addressFrom:
          notification = NotificationClient()
          result = notification.sendMail(self.addressTo, self.subject, body, self.addressFrom, localAttempt=False)
          if not result['OK']:
            self.log.error('Can not send new site notification mail', result['Message'])

    return S_OK()

  def __updateSEs(self):
    """ Update the Storage Element settings in the CS if they were changed in the BDII
    """

    bdiiChangeSet = set()

    for vo in self.voName:
      result = self.__getBdiiSEInfo(vo)
      if not result['OK']:
        continue
      seBdiiDict = result['Value']
      result = getSRMUpdates(vo, bdiiInfo=seBdiiDict)
      if not result['OK']:
        continue
      bdiiChangeSet = bdiiChangeSet.union(result['Value'])

    # We have collected all the changes, consolidate VO settings
    result = self.__updateCS(bdiiChangeSet)
    return result
Example #14
0
def checkUnusedSEs():

  global vo, dry

  result = getGridSRMs(vo, unUsed=True)
  if not result['OK']:
    gLogger.error('Failed to look up SRMs in BDII', result['Message'])
  siteSRMDict = result['Value']

  # Evaluate VOs
  result = getVOs()
  if result['OK']:
    csVOs = set(result['Value'])
  else:
    csVOs = {vo}

  changeSetFull = set()

  for site in siteSRMDict:
    for gridSE in siteSRMDict[site]:
      changeSet = set()
      seDict = siteSRMDict[site][gridSE]['SE']
      srmDict = siteSRMDict[site][gridSE]['SRM']
      # Check the SRM version
      version = srmDict.get('GlueServiceVersion', '')
      if not (version and version.startswith('2')):
        gLogger.debug('Skipping SRM service with version %s' % version)
        continue
      result = getDIRACSiteName(site)
      if not result['OK']:
        gLogger.notice('Unused se %s is detected at unused site %s' % (gridSE, site))
        gLogger.notice('Consider adding site %s to the DIRAC CS' % site)
        continue
      diracSites = result['Value']
      yn = raw_input(
          '\nDo you want to add new SRM SE %s at site(s) %s ? default yes [yes|no]: ' %
          (gridSE, str(diracSites)))
      if not yn or yn.lower().startswith('y'):
        if len(diracSites) > 1:
          prompt = 'Which DIRAC site the new SE should be attached to ?'
          for i, s in enumerate(diracSites):
            prompt += '\n[%d] %s' % (i, s)
          prompt += '\nEnter your choice number: '
          inp = raw_input(prompt)
          try:
            ind = int(inp)
          except BaseException:
            gLogger.notice('Can not interpret your choice: %s, try again later' % inp)
            continue
          diracSite = diracSites[ind]
        else:
          diracSite = diracSites[0]

        domain, siteName, country = diracSite.split('.')
        recName = '%s-disk' % siteName
        inp = raw_input('Give a DIRAC name to the grid SE %s, default %s : ' % (gridSE, recName))
        diracSEName = inp
        if not inp:
          diracSEName = recName

        gLogger.notice('Adding new SE %s at site %s' % (diracSEName, diracSite))
        seSection = cfgPath('/Resources/StorageElements', diracSEName)
        changeSet.add((seSection, 'BackendType', seDict.get('GlueSEImplementationName', 'Unknown')))
        changeSet.add((seSection, 'Description', seDict.get('GlueSEName', 'Unknown')))
        bdiiVOs = set([re.sub('^VO:', '', rule) for rule in srmDict.get('GlueServiceAccessControlBaseRule', [])])
        seVOs = csVOs.intersection(bdiiVOs)
        changeSet.add((seSection, 'VO', ','.join(seVOs)))
        accessSection = cfgPath(seSection, 'AccessProtocol.1')
        changeSet.add((accessSection, 'Protocol', 'srm'))
        changeSet.add((accessSection, 'PluginName', 'SRM2'))
        endPoint = srmDict.get('GlueServiceEndpoint', '')
        host = urlparse(endPoint).hostname
        port = urlparse(endPoint).port
        changeSet.add((accessSection, 'Host', host))
        changeSet.add((accessSection, 'Port', port))
        changeSet.add((accessSection, 'Access', 'remote'))
        voPathSection = cfgPath(accessSection, 'VOPath')
        if 'VOPath' in seDict:
          path = seDict['VOPath']
          voFromPath = os.path.basename(path)
          if voFromPath != diracVO:
            gLogger.notice('\n!!! Warning: non-conventional VO path: %s\n' % path)
            changeSet.add((voPathSection, diracVO, path))
          path = os.path.dirname(path)
        else:
          # Try to guess the Path
          domain = '.'.join(host.split('.')[-2:])
          path = '/dpm/%s/home' % domain
        changeSet.add((accessSection, 'Path', path))
        changeSet.add((accessSection, 'SpaceToken', ''))
        changeSet.add((accessSection, 'WSUrl', '/srm/managerv2?SFN='))

        gLogger.notice('SE %s will be added with the following parameters' % diracSEName)
        changeList = sorted(changeSet)
        for entry in changeList:
          gLogger.notice(entry)
        yn = raw_input('Do you want to add new SE %s ? default yes [yes|no]: ' % diracSEName)
        if not yn or yn.lower().startswith('y'):
          changeSetFull = changeSetFull.union(changeSet)

  if dry:
    if changeSetFull:
      gLogger.notice('Skipping commit of the new SE data in a dry run')
    else:
      gLogger.notice("No new SE to be added")
    return S_OK()

  if changeSetFull:
    csAPI = CSAPI()
    csAPI.initialize()
    result = csAPI.downloadCSData()
    if not result['OK']:
      gLogger.error('Failed to initialize CSAPI object', result['Message'])
      DIRACExit(-1)
    changeList = sorted(changeSetFull)
    for section, option, value in changeList:
      csAPI.setOption(cfgPath(section, option), value)

    yn = raw_input('New SE data is accumulated\n Do you want to commit changes to CS ? default yes [yes|no]: ')
    if not yn or yn.lower().startswith('y'):
      result = csAPI.commit()
      if not result['OK']:
        gLogger.error("Error while commit to CS", result['Message'])
      else:
        gLogger.notice("Successfully committed %d changes to CS" % len(changeSetFull))
  else:
    gLogger.notice("No new SE to be added")

  return S_OK()
Example #15
0
class GOCDB2CSAgent(AgentModule):
    """ Class to retrieve information about service endpoints
      from GOCDB and update configuration stored by CS
  """
    def __init__(self, *args, **kwargs):
        """ c'tor
    """
        super(GOCDB2CSAgent, self).__init__(*args, **kwargs)
        self.GOCDBClient = None
        self.csAPI = None
        self.dryRun = False

    def initialize(self):
        """ Run at the agent initialization (normally every 500 cycles)
    """
        # client to connect to GOCDB
        self.GOCDBClient = GOCDBClient()
        self.dryRun = self.am_getOption('DryRun', self.dryRun)

        # API needed to update configuration stored by CS
        self.csAPI = CSAPI()
        return self.csAPI.initialize()

    def execute(self):
        """
    Execute GOCDB queries according to the function map
    and user request (options in configuration).
    """

        # __functionMap is at the end of the class definition
        for option, functionCall in GOCDB2CSAgent.__functionMap.iteritems():
            optionValue = self.am_getOption(option, True)
            if optionValue:
                result = functionCall(self)
                if not result['OK']:
                    self.log.error("%s() failed with message: %s" %
                                   (functionCall.__name__, result['Message']))
                else:
                    self.log.info("Successfully executed %s" %
                                  functionCall.__name__)

        return S_OK()

    def updatePerfSONARConfiguration(self):
        """
    Get current status of perfSONAR endpoints from GOCDB
    and update CS configuration accordingly.
    """
        log = self.log.getSubLogger('updatePerfSONAREndpoints')
        log.debug('Begin function ...')

        # get endpoints
        result = self.__getPerfSONAREndpoints()
        if not result['OK']:
            log.error("__getPerfSONAREndpoints() failed with message: %s" %
                      result['Message'])
            return S_ERROR('Unable to fetch perfSONAR endpoints from GOCDB.')
        endpointList = result['Value']

        # add DIRAC site name
        result = self.__addDIRACSiteName(endpointList)
        if not result['OK']:
            log.error("__addDIRACSiteName() failed with message: %s" %
                      result['Message'])
            return S_ERROR('Unable to extend the list with DIRAC site names.')
        extendedEndpointList = result['Value']

        # prepare dictionary with new configuration
        result = self.__preparePerfSONARConfiguration(extendedEndpointList)
        if not result['OK']:
            log.error(
                "__preparePerfSONARConfiguration() failed with message: %s" %
                result['Message'])
            return S_ERROR('Unable to prepare a new perfSONAR configuration.')
        finalConfiguration = result['Value']

        # update configuration according to the final status of endpoints
        self.__updateConfiguration(finalConfiguration)
        log.debug("Configuration updated succesfully")

        log.debug('End function.')
        return S_OK()

    def __getPerfSONAREndpoints(self):
        """
    Retrieve perfSONAR endpoint information directly from GOCDB.

    :return: List of perfSONAR endpoints (dictionaries) as stored by GOCDB.
    """

        log = self.log.getSubLogger('__getPerfSONAREndpoints')
        log.debug('Begin function ...')

        # get perfSONAR endpoints (latency and bandwidth) form GOCDB
        endpointList = []
        for endpointType in ['Latency', 'Bandwidth']:
            result = self.GOCDBClient.getServiceEndpointInfo(
                'service_type', 'net.perfSONAR.%s' % endpointType)

            if not result['OK']:
                log.error("getServiceEndpointInfo() failed with message: %s" %
                          result['Message'])
                return S_ERROR('Could not fetch %s endpoints from GOCDB' %
                               endpointType.lower())

            log.debug('Number of %s endpoints: %s' %
                      (endpointType.lower(), len(result['Value'])))
            endpointList.extend(result['Value'])

        log.debug('Number of perfSONAR endpoints: %s' % len(endpointList))
        log.debug('End function.')
        return S_OK(endpointList)

    def __preparePerfSONARConfiguration(self, endpointList):
        """
    Prepare a dictionary with a new CS configuration of perfSONAR endpoints.

    :return: Dictionary where keys are configuration paths (options and sections)
             and values are values of corresponding options
             or None in case of a path pointing to a section.
    """

        log = self.log.getSubLogger('__preparePerfSONARConfiguration')
        log.debug('Begin function ...')

        # static elements of a path
        rootPath = '/Resources/Sites'
        extPath = 'Network'
        baseOptionName = 'Enabled'
        options = {baseOptionName: 'True', 'ServiceType': 'perfSONAR'}

        # enable GOCDB endpoints in configuration
        newConfiguration = {}
        for endpoint in endpointList:
            if endpoint['DIRACSITENAME'] is None:
                continue

            split = endpoint['DIRACSITENAME'].split('.')
            path = cfgPath(rootPath, split[0], endpoint['DIRACSITENAME'],
                           extPath, endpoint['HOSTNAME'])
            for name, defaultValue in options.iteritems():
                newConfiguration[cfgPath(path, name)] = defaultValue

        # get current configuration
        currentConfiguration = {}
        for option in options.iterkeys():
            result = gConfig.getConfigurationTree(rootPath, extPath + '/',
                                                  '/' + option)
            if not result['OK']:
                log.error("getConfigurationTree() failed with message: %s" %
                          result['Message'])
                return S_ERROR('Unable to fetch perfSONAR endpoints from CS.')
            currentConfiguration.update(result['Value'])

        # disable endpoints that disappeared in GOCDB
        removedElements = set(currentConfiguration) - set(newConfiguration)
        newElements = set(newConfiguration) - set(currentConfiguration)

        addedEndpoints = len(newElements) / len(options)
        disabledEndpoints = 0
        for path in removedElements:
            if baseOptionName in path:
                newConfiguration[path] = 'False'
                if currentConfiguration[path] != 'False':
                    disabledEndpoints = disabledEndpoints + 1

        # inform what will be changed
        if addedEndpoints > 0:
            self.log.info(
                "%s new perfSONAR endpoints will be added to the configuration"
                % addedEndpoints)

        if disabledEndpoints > 0:
            self.log.info(
                "%s old perfSONAR endpoints will be disable in the configuration"
                % disabledEndpoints)

        if addedEndpoints == 0 and disabledEndpoints == 0:
            self.log.info("perfSONAR configuration is up-to-date")

        log.debug('End function.')
        return S_OK(newConfiguration)

    def __addDIRACSiteName(self, inputList):
        """
    Extend given list of GOCDB endpoints with DIRAC site name, i.e.
    add an entry "DIRACSITENAME" in dictionaries that describe endpoints.
    If given site name could not be found "DIRACSITENAME" is set to 'None'.

    :return: List of perfSONAR endpoints (dictionaries).
    """

        log = self.log.getSubLogger('__addDIRACSiteName')
        log.debug('Begin function ...')

        # get site name dictionary
        result = getDIRACGOCDictionary()
        if not result['OK']:
            log.error("getDIRACGOCDictionary() failed with message: %s" %
                      result['Message'])
            return S_ERROR('Could not get site name dictionary')

        # reverse the dictionary (assume 1 to 1 relation)
        DIRACGOCDict = result['Value']
        GOCDIRACDict = dict(zip(DIRACGOCDict.values(), DIRACGOCDict.keys()))

        # add DIRAC site names
        outputList = []
        for entry in inputList:
            try:
                entry['DIRACSITENAME'] = GOCDIRACDict[entry['SITENAME']]
            except KeyError:
                self.log.warn("No dictionary entry for %s. " %
                              entry['SITENAME'])
                entry['DIRACSITENAME'] = None
            outputList.append(entry)

        log.debug('End function.')
        return S_OK(outputList)

    def __updateConfiguration(self, setElements=None, delElements=None):
        """
    Update configuration stored by CS.
    """
        if setElements is None:
            setElements = {}
        if delElements is None:
            delElements = []

        log = self.log.getSubLogger('__updateConfiguration')
        log.debug('Begin function ...')

        # assure existence and proper value of a section or an option
        for path, value in setElements.iteritems():

            if value is None:
                section = path
            else:
                split = path.rsplit('/', 1)
                section = split[0]

            try:
                result = self.csAPI.createSection(section)
                if not result['OK']:
                    log.error("createSection() failed with message: %s" %
                              result['Message'])
            except Exception as e:
                log.error("Exception in createSection(): %s" %
                          repr(e).replace(',)', ')'))

            if value is not None:
                try:
                    result = self.csAPI.setOption(path, value)
                    if not result['OK']:
                        log.error("setOption() failed with message: %s" %
                                  result['Message'])
                except Exception as e:
                    log.error("Exception in setOption(): %s" %
                              repr(e).replace(',)', ')'))

        # delete elements in the configuration
        for path in delElements:
            result = self.csAPI.delOption(path)
            if not result['OK']:
                log.warn("delOption() failed with message: %s" %
                         result['Message'])

                result = self.csAPI.delSection(path)
                if not result['OK']:
                    log.warn("delSection() failed with message: %s" %
                             result['Message'])

        if self.dryRun:
            log.info("Dry Run: CS won't be updated")
            self.csAPI.showDiff()
        else:
            # update configuration stored by CS
            result = self.csAPI.commit()
            if not result['OK']:
                log.error("commit() failed with message: %s" %
                          result['Message'])
                return S_ERROR("Could not commit changes to CS.")
            else:
                log.info("Committed changes to CS")

        log.debug('End function.')
        return S_OK()

    # define mapping between an agent option in the configuration and a function call
    __functionMap = {
        'UpdatePerfSONARS': updatePerfSONARConfiguration,
    }
Example #16
0
class NetworkAgent(AgentModule):
    """
    AccountingSystem agent to processes messages containing perfSONAR network metrics.
    Results are stored in the accounting database.
    """

    #: default number of seconds after which network accounting
    #: objects are removed from the temporary buffer
    BUFFER_TIMEOUT = 3600

    def initialize(self):

        self.log = gLogger.getSubLogger("NetworkAgent")

        # API initialization is required to get an up-to-date configuration from the CS
        self.csAPI = CSAPI()
        self.csAPI.initialize()

        # temporary buffer for network accounting objects + some parameters
        self.buffer = {}  # { {addTime: datetime.now(), object: Network() }, ... }
        self.bufferTimeout = self.am_getOption("BufferTimeout", NetworkAgent.BUFFER_TIMEOUT)

        # internal list of message queue consumers
        self.consumers = []

        # host-to-dirac name dictionary
        self.nameDictionary = {}

        # statistics
        self.messagesCount = 0  # number of received messages
        self.messagesCountOld = 0  # previous number of received messages (used to check connection status)

        self.skippedMessagesCount = 0  # number of skipped messages (errors, unsupported metrics, etc.)
        self.PLRMetricCount = 0  # number of received packet-loss-rate metrics
        self.OWDMetricCount = 0  # number of received one-way-delay metrics
        self.skippedMetricCount = 0  # number of skipped metrics (errors, invalid data, etc.)
        self.insertedCount = 0  # number of properly inserted accounting objects
        self.removedCount = 0  # number of removed accounting objects (missing data)

        return S_OK()

    def finalize(self):
        """
        Gracefully close all consumer connections and commit last records to the DB.
        """

        for consumer in self.consumers:
            consumer.close()

        self.commitData()

        return S_OK()

    def execute(self):
        """
        During each cycle update the internal host-to-dirac name dictionary,
        check the consumers status (restart them if necessary),
        commit data stored in the buffer and show statistics.
        """

        self.updateNameDictionary()
        self.checkConsumers()
        self.commitData()
        self.showStatistics()

        return S_OK()

    def updateNameDictionary(self):
        """
        Update the internal host-to-dirac name dictionary.
        """

        result = gConfig.getConfigurationTree("/Resources/Sites", "Network/", "/Enabled")
        if not result["OK"]:
            self.log.error("getConfigurationTree() failed with message: %s" % result["Message"])
            return S_ERROR("Unable to fetch perfSONAR endpoints from CS.")

        tmpDict = {}
        for path, value in result["Value"].items():
            if value == "True":
                elements = path.split("/")
                diracName = elements[4]
                hostName = elements[6]
                tmpDict[hostName] = diracName

        self.nameDictionary = tmpDict

    def checkConsumers(self):
        """
        Check whether consumers exist and work properly.
        (Re)create consumers if needed.
        """

        # recreate consumers if there are any problems
        if not self.consumers or self.messagesCount == self.messagesCountOld:

            for consumer in self.consumers:
                consumer.close()

            for uri in self.am_getOption("MessageQueueURI", "").replace(" ", "").split(","):
                result = createConsumer(uri, self.processMessage)
                if not result["OK"]:
                    self.log.error("Failed to create a consumer from URI: %s" % uri)
                    continue
                else:
                    self.log.info("Successfully created a consumer from URI: %s" % uri)

                self.consumers.append(result["Value"])

            if self.consumers:
                return S_OK("Successfully created at least one consumer")
            return S_ERROR("Failed to create at least one consumer")

        # if everything is OK just update the counter
        else:
            self.messagesCountOld = self.messagesCount

    def processMessage(self, headers, body):
        """
        Process a message containing perfSONAR data and store the result in the Accounting DB.
        Supports packet-loss-rate and one-way-delay metrics send in raw data streams.

        Function is designed to be an MQConsumer callback function.
        """

        self.messagesCount += 1
        metadata = {
            "SourceIP": body["meta"]["source"],
            "SourceHostName": body["meta"]["input_source"],
            "DestinationIP": body["meta"]["destination"],
            "DestinationHostName": body["meta"]["input_destination"],
        }

        try:
            metadata["Source"] = self.nameDictionary[body["meta"]["input_source"]]
            metadata["Destination"] = self.nameDictionary[body["meta"]["input_destination"]]
        except KeyError as error:
            # messages with unsupported source or destination host name can be safely skipped
            self.skippedMessagesCount += 1
            self.log.debug('Host "%s" does not exist in the host-to-dirac name dictionary (message skipped)' % error)
            return S_OK()

        metadataKey = ""
        for value in metadata.values():
            metadataKey += value

        timestamps = sorted(body["datapoints"])
        for timestamp in timestamps:
            try:
                date = datetime.utcfromtimestamp(float(timestamp))

                # create a key that allows to join packet-loss-rate and one-way-delay
                # metrics in one network accounting record
                networkAccountingObjectKey = "%s%s" % (metadataKey, str(date))

                # use existing or create a new temporary accounting
                # object to store the data in DB
                if networkAccountingObjectKey in self.buffer:
                    net = self.buffer[networkAccountingObjectKey]["object"]

                    timeDifference = datetime.now() - self.buffer[networkAccountingObjectKey]["addTime"]
                    if timeDifference.total_seconds() > 60:
                        self.log.warn("Object was taken from buffer after %s" % (timeDifference))
                else:
                    net = Network()
                    net.setStartTime(date)
                    net.setEndTime(date)
                    net.setValuesFromDict(metadata)

                # get data stored in metric
                metricData = body["datapoints"][timestamp]

                # look for supported event types
                if headers["event-type"] == "packet-loss-rate":
                    self.PLRMetricCount += 1
                    if metricData < 0 or metricData > 1:
                        raise Exception("Invalid PLR metric (%s)" % (metricData))

                    net.setValueByKey("PacketLossRate", metricData * 100)
                elif headers["event-type"] == "histogram-owdelay":
                    self.OWDMetricCount += 1

                    # calculate statistics from histogram
                    OWDMin = 999999
                    OWDMax = 0
                    total = 0
                    count = 0
                    for value, items in metricData.items():
                        floatValue = float(value)
                        total += floatValue * items
                        count += items
                        OWDMin = min(OWDMin, floatValue)
                        OWDMax = max(OWDMax, floatValue)
                    OWDAvg = float(total) / count

                    # skip metrics with invalid data
                    if OWDAvg < 0 or OWDMin < 0 or OWDMax < 0:
                        raise Exception("Invalid OWD metric (%s, %s, %s)" % (OWDMin, OWDAvg, OWDMax))
                    else:
                        # approximate jitter value
                        net.setValueByKey("Jitter", OWDMax - OWDMin)
                        net.setValueByKey("OneWayDelay", OWDAvg)

                else:
                    self.skippedMetricCount += 1
                    continue

                self.buffer[networkAccountingObjectKey] = {"addTime": datetime.now(), "object": net}

            # suppress all exceptions to protect the listener thread
            except Exception as e:
                self.skippedMetricCount += 1
                self.log.warn("Metric skipped because of an exception: %s" % e)

        return S_OK()

    def commitData(self):
        """
        Iterates through all object in the temporary buffer and commit objects to DB
        if both packet-loss-rate and one-way-delay values are set.

        Objects in the buffer older than self.bufferTimeout seconds which still have
        missing data are removed permanently (a warning is issued).
        """

        now = datetime.now()
        removed = False

        for key, value in self.buffer.items():
            result = value["object"].checkValues()
            if not result["OK"]:
                if (now - value["addTime"]).total_seconds() > self.bufferTimeout:
                    del self.buffer[key]
                    self.removedCount += 1
                    removed = True
            else:
                value["object"].delayedCommit()
                del self.buffer[key]
                self.insertedCount += 1

        if removed:
            self.log.warn("Network accounting object(s) has been removed because of missing data")

        return S_OK()

    def showStatistics(self):
        """Display different statistics as info messages in the log file."""

        self.log.info("\tReceived messages:           %s" % self.messagesCount)
        self.log.info("\tSkipped messages:            %s" % self.skippedMessagesCount)
        self.log.info("\tPacket-Loss-Rate metrics:    %s" % self.PLRMetricCount)
        self.log.info("\tOne-Way-Delay metrics:       %s" % self.OWDMetricCount)
        self.log.info("\tSkipped metrics:             %s" % self.skippedMetricCount)
        self.log.info("")
        self.log.info("\tObjects in the buffer:       %s" % len(self.buffer))
        self.log.info("\tObjects inserted to DB:      %s" % self.insertedCount)
        self.log.info("\tPermanently removed objects: %s" % self.removedCount)

        return S_OK()
Example #17
0
class GOCDB2CSAgent (AgentModule):
  """ Class to retrieve information about service endpoints
      from GOCDB and update configuration stored by CS
  """

  def __init__(self, *args, **kwargs):
    """ c'tor
    """
    super(GOCDB2CSAgent, self).__init__(*args, **kwargs)
    self.GOCDBClient = None
    self.csAPI = None
    self.dryRun = False

  def initialize(self):
    """ Run at the agent initialization (normally every 500 cycles)
    """
    # client to connect to GOCDB
    self.GOCDBClient = GOCDBClient()
    self.dryRun = self.am_getOption('DryRun', self.dryRun)

    # API needed to update configuration stored by CS
    self.csAPI = CSAPI()
    return self.csAPI.initialize()

  def execute(self):
    """
    Execute GOCDB queries according to the function map
    and user request (options in configuration).
    """

    # __functionMap is at the end of the class definition
    for option, functionCall in GOCDB2CSAgent.__functionMap.iteritems():
      optionValue = self.am_getOption(option, True)
      if optionValue:
        result = functionCall(self)
        if not result['OK']:
          self.log.error("%s() failed with message: %s" % (functionCall.__name__, result['Message']))
        else:
          self.log.info("Successfully executed %s" % functionCall.__name__)

    return S_OK()

  def updatePerfSONARConfiguration(self):
    """
    Get current status of perfSONAR endpoints from GOCDB
    and update CS configuration accordingly.
    """
    log = self.log.getSubLogger('updatePerfSONAREndpoints')
    log.debug('Begin function ...')

    # get endpoints
    result = self.__getPerfSONAREndpoints()
    if not result['OK']:
      log.error("__getPerfSONAREndpoints() failed with message: %s" % result['Message'])
      return S_ERROR('Unable to fetch perfSONAR endpoints from GOCDB.')
    endpointList = result['Value']

    # add DIRAC site name
    result = self.__addDIRACSiteName(endpointList)
    if not result['OK']:
      log.error("__addDIRACSiteName() failed with message: %s" % result['Message'])
      return S_ERROR('Unable to extend the list with DIRAC site names.')
    extendedEndpointList = result['Value']

    # prepare dictionary with new configuration
    result = self.__preparePerfSONARConfiguration(extendedEndpointList)
    if not result['OK']:
      log.error("__preparePerfSONARConfiguration() failed with message: %s" % result['Message'])
      return S_ERROR('Unable to prepare a new perfSONAR configuration.')
    finalConfiguration = result['Value']

    # update configuration according to the final status of endpoints
    self.__updateConfiguration(finalConfiguration)
    log.debug("Configuration updated succesfully")

    log.debug('End function.')
    return S_OK()

  def __getPerfSONAREndpoints(self):
    """
    Retrieve perfSONAR endpoint information directly from GOCDB.

    :return: List of perfSONAR endpoints (dictionaries) as stored by GOCDB.
    """

    log = self.log.getSubLogger('__getPerfSONAREndpoints')
    log.debug('Begin function ...')

    # get perfSONAR endpoints (latency and bandwidth) form GOCDB
    endpointList = []
    for endpointType in ['Latency', 'Bandwidth']:
      result = self.GOCDBClient.getServiceEndpointInfo('service_type', 'net.perfSONAR.%s' % endpointType)

      if not result['OK']:
        log.error("getServiceEndpointInfo() failed with message: %s" % result['Message'])
        return S_ERROR('Could not fetch %s endpoints from GOCDB' % endpointType.lower())

      log.debug('Number of %s endpoints: %s' % (endpointType.lower(), len(result['Value'])))
      endpointList.extend(result['Value'])

    log.debug('Number of perfSONAR endpoints: %s' % len(endpointList))
    log.debug('End function.')
    return S_OK(endpointList)

  def __preparePerfSONARConfiguration(self, endpointList):
    """
    Prepare a dictionary with a new CS configuration of perfSONAR endpoints.

    :return: Dictionary where keys are configuration paths (options and sections)
             and values are values of corresponding options
             or None in case of a path pointing to a section.
    """

    log = self.log.getSubLogger('__preparePerfSONARConfiguration')
    log.debug('Begin function ...')

    # static elements of a path
    rootPath = '/Resources/Sites'
    extPath = 'Network'
    baseOptionName = 'Enabled'
    options = {baseOptionName: 'True', 'ServiceType': 'perfSONAR'}

    # enable GOCDB endpoints in configuration
    newConfiguration = {}
    for endpoint in endpointList:
      if endpoint['DIRACSITENAME'] is None:
        continue

      split = endpoint['DIRACSITENAME'].split('.')
      path = cfgPath(rootPath, split[0], endpoint['DIRACSITENAME'], extPath, endpoint['HOSTNAME'])
      for name, defaultValue in options.iteritems():
        newConfiguration[cfgPath(path, name)] = defaultValue

    # get current configuration
    currentConfiguration = {}
    for option in options.iterkeys():
      result = gConfig.getConfigurationTree(rootPath, extPath + '/', '/' + option)
      if not result['OK']:
        log.error("getConfigurationTree() failed with message: %s" % result['Message'])
        return S_ERROR('Unable to fetch perfSONAR endpoints from CS.')
      currentConfiguration.update(result['Value'])

    # disable endpoints that disappeared in GOCDB
    removedElements = set(currentConfiguration) - set(newConfiguration)
    newElements = set(newConfiguration) - set(currentConfiguration)

    addedEndpoints = len(newElements) / len(options)
    disabledEndpoints = 0
    for path in removedElements:
      if baseOptionName in path:
        newConfiguration[path] = 'False'
        if currentConfiguration[path] != 'False':
          disabledEndpoints = disabledEndpoints + 1

    # inform what will be changed
    if addedEndpoints > 0:
      self.log.info("%s new perfSONAR endpoints will be added to the configuration" % addedEndpoints)

    if disabledEndpoints > 0:
      self.log.info("%s old perfSONAR endpoints will be disable in the configuration" % disabledEndpoints)

    if addedEndpoints == 0 and disabledEndpoints == 0:
      self.log.info("perfSONAR configuration is up-to-date")

    log.debug('End function.')
    return S_OK(newConfiguration)

  def __addDIRACSiteName(self, inputList):
    """
    Extend given list of GOCDB endpoints with DIRAC site name, i.e.
    add an entry "DIRACSITENAME" in dictionaries that describe endpoints.
    If given site name could not be found "DIRACSITENAME" is set to 'None'.

    :return: List of perfSONAR endpoints (dictionaries).
    """

    log = self.log.getSubLogger('__addDIRACSiteName')
    log.debug('Begin function ...')

    # get site name dictionary
    result = getDIRACGOCDictionary()
    if not result['OK']:
      log.error("getDIRACGOCDictionary() failed with message: %s" % result['Message'])
      return S_ERROR('Could not get site name dictionary')

    # reverse the dictionary (assume 1 to 1 relation)
    DIRACGOCDict = result['Value']
    GOCDIRACDict = dict(zip(DIRACGOCDict.values(), DIRACGOCDict.keys()))

    # add DIRAC site names
    outputList = []
    for entry in inputList:
      try:
        entry['DIRACSITENAME'] = GOCDIRACDict[entry['SITENAME']]
      except KeyError:
        self.log.warn("No dictionary entry for %s. " % entry['SITENAME'])
        entry['DIRACSITENAME'] = None
      outputList.append(entry)

    log.debug('End function.')
    return S_OK(outputList)

  def __updateConfiguration(self, setElements=None, delElements=None):
    """
    Update configuration stored by CS.
    """
    if setElements is None:
      setElements = {}
    if delElements is None:
      delElements = []

    log = self.log.getSubLogger('__updateConfiguration')
    log.debug('Begin function ...')

    # assure existence and proper value of a section or an option
    for path, value in setElements.iteritems():

      if value is None:
        section = path
      else:
        split = path.rsplit('/', 1)
        section = split[0]

      try:
        result = self.csAPI.createSection(section)
        if not result['OK']:
          log.error("createSection() failed with message: %s" % result['Message'])
      except Exception as e:
        log.error("Exception in createSection(): %s" % repr(e).replace(',)', ')'))

      if value is not None:
        try:
          result = self.csAPI.setOption(path, value)
          if not result['OK']:
            log.error("setOption() failed with message: %s" % result['Message'])
        except Exception as e:
          log.error("Exception in setOption(): %s" % repr(e).replace(',)', ')'))

    # delete elements in the configuration
    for path in delElements:
      result = self.csAPI.delOption(path)
      if not result['OK']:
        log.warn("delOption() failed with message: %s" % result['Message'])

        result = self.csAPI.delSection(path)
        if not result['OK']:
          log.warn("delSection() failed with message: %s" % result['Message'])

    if self.dryRun:
      log.info("Dry Run: CS won't be updated")
      self.csAPI.showDiff()
    else:
      # update configuration stored by CS
      result = self.csAPI.commit()
      if not result['OK']:
        log.error("commit() failed with message: %s" % result['Message'])
        return S_ERROR("Could not commit changes to CS.")
      else:
        log.info("Committed changes to CS")

    log.debug('End function.')
    return S_OK()

  # define mapping between an agent option in the configuration and a function call
  __functionMap = {'UpdatePerfSONARS': updatePerfSONARConfiguration,
                   }
def checkUnusedSEs():
  
  global vo, dry
  
  result = getGridSRMs( vo, unUsed = True )
  if not result['OK']:
    gLogger.error( 'Failed to look up SRMs in BDII', result['Message'] )
  siteSRMDict = result['Value']

  # Evaluate VOs
  result = getVOs()
  if result['OK']:
    csVOs = set( result['Value'] )
  else:
    csVOs = set( [vo] ) 

  changeSetFull = set()

  for site in siteSRMDict:
    for gridSE in siteSRMDict[site]:
      changeSet = set()
      seDict = siteSRMDict[site][gridSE]['SE']
      srmDict = siteSRMDict[site][gridSE]['SRM']
      # Check the SRM version
      version = srmDict.get( 'GlueServiceVersion', '' )
      if not ( version and version.startswith( '2' ) ):
        gLogger.debug( 'Skipping SRM service with version %s' % version )
        continue 
      result = getDIRACSiteName( site )
      if not result['OK']:
        gLogger.notice( 'Unused se %s is detected at unused site %s' % ( gridSE, site ) )
        gLogger.notice( 'Consider adding site %s to the DIRAC CS' % site ) 
        continue
      diracSites = result['Value']
      yn = raw_input( '\nDo you want to add new SRM SE %s at site(s) %s ? default yes [yes|no]: ' % ( gridSE, str( diracSites ) ) )
      if not yn or yn.lower().startswith( 'y' ):
        if len( diracSites ) > 1:
          prompt = 'Which DIRAC site the new SE should be attached to ?'
          for i, s in enumerate( diracSites ):
            prompt += '\n[%d] %s' % ( i, s )
          prompt += '\nEnter your choice number: '    
          inp = raw_input( prompt )
          try:
            ind = int( inp )
          except:
            gLogger.notice( 'Can not interpret your choice: %s, try again later' % inp )
            continue
          diracSite = diracSites[ind]
        else:
          diracSite = diracSites[0]       
      
        domain, siteName, country = diracSite.split( '.' )
        recName = '%s-disk' % siteName
        inp = raw_input( 'Give a DIRAC name to the grid SE %s, default %s : ' % ( gridSE, recName ) )
        diracSEName = inp
        if not inp:
          diracSEName = recName  
          
        gLogger.notice( 'Adding new SE %s at site %s' % ( diracSEName, diracSite ) )        
        seSection = cfgPath( '/Resources/StorageElements', diracSEName )
        changeSet.add( ( seSection, 'BackendType', seDict.get( 'GlueSEImplementationName', 'Unknown' ) ) )
        changeSet.add( ( seSection, 'Description', seDict.get( 'GlueSEName', 'Unknown' ) ) )  
        bdiiVOs = set( [ re.sub( '^VO:', '', rule ) for rule in srmDict.get( 'GlueServiceAccessControlBaseRule', [] ) ] )
        seVOs = csVOs.intersection( bdiiVOs )  
        changeSet.add( ( seSection, 'VO', ','.join( seVOs ) ) )
        accessSection = cfgPath( seSection, 'AccessProtocol.1' )
        changeSet.add( ( accessSection, 'Protocol', 'srm' ) )
        changeSet.add( ( accessSection, 'ProtocolName', 'SRM2' ) )
        endPoint = srmDict.get( 'GlueServiceEndpoint', '' )
        result = pfnparse( endPoint )
        if not result['OK']:
          gLogger.error( 'Can not get the SRM service end point. Skipping ...' )
          continue
        host = result['Value']['Host']
        port = result['Value']['Port']
        changeSet.add( ( accessSection, 'Host', host ) ) 
        changeSet.add( ( accessSection, 'Port', port ) ) 
        changeSet.add( ( accessSection, 'Access', 'remote' ) )
        voPathSection = cfgPath( accessSection, 'VOPath' )
        if 'VOPath' in seDict:
          path = seDict['VOPath']
          voFromPath = os.path.basename( path )
          if voFromPath != diracVO:
            gLogger.notice( '\n!!! Warning: non-conventional VO path: %s\n' % path )
            changeSet.add( ( voPathSection, diracVO, path ) )
          path = os.path.dirname( path )  
        else:  
          # Try to guess the Path
          domain = '.'.join( host.split( '.' )[-2:] )
          path = '/dpm/%s/home' % domain
        changeSet.add( ( accessSection, 'Path', path ) ) 
        changeSet.add( ( accessSection, 'SpaceToken', '' ) )
        changeSet.add( ( accessSection, 'WSUrl', '/srm/managerv2?SFN=' ) ) 
        
        gLogger.notice( 'SE %s will be added with the following parameters' % diracSEName )
        changeList = list( changeSet )
        changeList.sort()
        for entry in changeList:
          gLogger.notice( entry )
        yn = raw_input( 'Do you want to add new SE %s ? default yes [yes|no]: ' % diracSEName )   
        if not yn or yn.lower().startswith( 'y' ):
          changeSetFull = changeSetFull.union( changeSet )        
          
  if dry:
    if changeSetFull:
      gLogger.notice( 'Skipping commit of the new SE data in a dry run' )
    else:
      gLogger.notice( "No new SE to be added" )
    return S_OK()

  if changeSetFull:
    csAPI = CSAPI()
    csAPI.initialize()
    result = csAPI.downloadCSData()
    if not result['OK']:
      gLogger.error( 'Failed to initialize CSAPI object', result['Message'] )
      DIRACExit( -1 )
    changeList = list( changeSetFull )
    changeList.sort()
    for section, option, value in changeList:
      csAPI.setOption( cfgPath( section, option ), value )

    yn = raw_input( 'New SE data is accumulated\n Do you want to commit changes to CS ? default yes [yes|no]: ' )
    if not yn or yn.lower().startswith( 'y' ):
      result = csAPI.commit()
      if not result['OK']:
        gLogger.error( "Error while commit to CS", result['Message'] )
      else:
        gLogger.notice( "Successfully committed %d changes to CS" % len( changeSetFull ) )
  else:
    gLogger.notice( "No new SE to be added" )

  return S_OK()
def checkUnusedSEs():

    global vo, dry

    result = getGridSRMs(vo, unUsed=True)
    if not result["OK"]:
        gLogger.error("Failed to look up SRMs in BDII", result["Message"])
    siteSRMDict = result["Value"]

    # Evaluate VOs
    result = getVOs()
    if result["OK"]:
        csVOs = set(result["Value"])
    else:
        csVOs = {vo}

    changeSetFull = set()

    for site in siteSRMDict:
        for gridSE in siteSRMDict[site]:
            changeSet = set()
            seDict = siteSRMDict[site][gridSE]["SE"]
            srmDict = siteSRMDict[site][gridSE]["SRM"]
            # Check the SRM version
            version = srmDict.get("GlueServiceVersion", "")
            if not (version and version.startswith("2")):
                gLogger.debug("Skipping SRM service with version %s" % version)
                continue
            result = getDIRACSiteName(site)
            if not result["OK"]:
                gLogger.notice("Unused se %s is detected at unused site %s" % (gridSE, site))
                gLogger.notice("Consider adding site %s to the DIRAC CS" % site)
                continue
            diracSites = result["Value"]
            yn = raw_input(
                "\nDo you want to add new SRM SE %s at site(s) %s ? default yes [yes|no]: " % (gridSE, str(diracSites))
            )
            if not yn or yn.lower().startswith("y"):
                if len(diracSites) > 1:
                    prompt = "Which DIRAC site the new SE should be attached to ?"
                    for i, s in enumerate(diracSites):
                        prompt += "\n[%d] %s" % (i, s)
                    prompt += "\nEnter your choice number: "
                    inp = raw_input(prompt)
                    try:
                        ind = int(inp)
                    except:
                        gLogger.notice("Can not interpret your choice: %s, try again later" % inp)
                        continue
                    diracSite = diracSites[ind]
                else:
                    diracSite = diracSites[0]

                domain, siteName, country = diracSite.split(".")
                recName = "%s-disk" % siteName
                inp = raw_input("Give a DIRAC name to the grid SE %s, default %s : " % (gridSE, recName))
                diracSEName = inp
                if not inp:
                    diracSEName = recName

                gLogger.notice("Adding new SE %s at site %s" % (diracSEName, diracSite))
                seSection = cfgPath("/Resources/StorageElements", diracSEName)
                changeSet.add((seSection, "BackendType", seDict.get("GlueSEImplementationName", "Unknown")))
                changeSet.add((seSection, "Description", seDict.get("GlueSEName", "Unknown")))
                bdiiVOs = set(
                    [re.sub("^VO:", "", rule) for rule in srmDict.get("GlueServiceAccessControlBaseRule", [])]
                )
                seVOs = csVOs.intersection(bdiiVOs)
                changeSet.add((seSection, "VO", ",".join(seVOs)))
                accessSection = cfgPath(seSection, "AccessProtocol.1")
                changeSet.add((accessSection, "Protocol", "srm"))
                changeSet.add((accessSection, "PluginName", "SRM2"))
                endPoint = srmDict.get("GlueServiceEndpoint", "")
                host = urlparse(endPoint).hostname
                port = urlparse(endPoint).port
                changeSet.add((accessSection, "Host", host))
                changeSet.add((accessSection, "Port", port))
                changeSet.add((accessSection, "Access", "remote"))
                voPathSection = cfgPath(accessSection, "VOPath")
                if "VOPath" in seDict:
                    path = seDict["VOPath"]
                    voFromPath = os.path.basename(path)
                    if voFromPath != diracVO:
                        gLogger.notice("\n!!! Warning: non-conventional VO path: %s\n" % path)
                        changeSet.add((voPathSection, diracVO, path))
                    path = os.path.dirname(path)
                else:
                    # Try to guess the Path
                    domain = ".".join(host.split(".")[-2:])
                    path = "/dpm/%s/home" % domain
                changeSet.add((accessSection, "Path", path))
                changeSet.add((accessSection, "SpaceToken", ""))
                changeSet.add((accessSection, "WSUrl", "/srm/managerv2?SFN="))

                gLogger.notice("SE %s will be added with the following parameters" % diracSEName)
                changeList = list(changeSet)
                changeList.sort()
                for entry in changeList:
                    gLogger.notice(entry)
                yn = raw_input("Do you want to add new SE %s ? default yes [yes|no]: " % diracSEName)
                if not yn or yn.lower().startswith("y"):
                    changeSetFull = changeSetFull.union(changeSet)

    if dry:
        if changeSetFull:
            gLogger.notice("Skipping commit of the new SE data in a dry run")
        else:
            gLogger.notice("No new SE to be added")
        return S_OK()

    if changeSetFull:
        csAPI = CSAPI()
        csAPI.initialize()
        result = csAPI.downloadCSData()
        if not result["OK"]:
            gLogger.error("Failed to initialize CSAPI object", result["Message"])
            DIRACExit(-1)
        changeList = list(changeSetFull)
        changeList.sort()
        for section, option, value in changeList:
            csAPI.setOption(cfgPath(section, option), value)

        yn = raw_input("New SE data is accumulated\n Do you want to commit changes to CS ? default yes [yes|no]: ")
        if not yn or yn.lower().startswith("y"):
            result = csAPI.commit()
            if not result["OK"]:
                gLogger.error("Error while commit to CS", result["Message"])
            else:
                gLogger.notice("Successfully committed %d changes to CS" % len(changeSetFull))
    else:
        gLogger.notice("No new SE to be added")

    return S_OK()