7 import xml.etree.ElementTree
as ET
8 from TrigConfStorage.TriggerCrestUtil
import TriggerCrestUtil
11 from AthenaCommon.Logging
import logging
12 log = logging.getLogger(
'TriggerConfigAccessBase.py')
16 with open(filename,
'r')
as fp:
17 config = json.load(fp)
18 filetype = config[
'filetype']
23 NONE = (
"Config",
"None",
"None")
24 L1MENU = (
"L1Menu",
"l1menu",
"L1M")
25 HLTMENU = (
"HLTMenu",
"hltmenu",
"HLTM")
26 L1PS = (
"L1PrescalesSet",
"l1prescale",
"L1PS")
27 HLTPS = (
"HLTPrescalesSet",
"hltprescale",
"HLTPS")
28 BGS = (
"L1BunchGroupsSet",
"bunchgroupset",
"BGS")
29 HLTJO = (
"HLTJobOptions",
"joboptions",
"JO")
30 HLTMON = (
"HLTMonitoring",
"hltmonitoringsummary",
"MGS")
31 def __init__(self, basename, filetype, crestkey):
36 if isinstance(other,str):
39 return self.
filetype == other.filetype
41 return not self.
__eq__(other)
45 ConfigLoader derived classes hold the information of the configuration source
46 and define the method to load the configuration
52 checks that the in-file specification of the configuration type matches the expected type
55 raise RuntimeError(
"Can not load file with filetype '%s' when expecting '%s'" % (config[
'filetype'], self.
configType.filetype))
59 super(ConfigFileLoader,self).
__init__(configType)
63 config = json.load(fp)
69 outfn = os.path.basename(self.filename)
70 if outfn.endswith(
".json"):
71 outfn = outfn.rsplit(
'.',1)[0]
72 return outfn +
".out.json"
75 """Class to load from json string"""
77 super(ConfigDirectLoader,self).
__init__(configType)
88 class ConfigDBLoader(ConfigLoader):
89 def __init__(self, configType, dbalias, dbkey):
98 query template is a dictionary of queries, identified by schema version,
99 similar to TrigConf::TrigDBMenuLoader::m_hltQueries and TrigConf::TrigDBMenuLoader::m_l1Queries
105 """ looks for file, first absolute, then by resolving envvar pathenv"""
106 if os.access(filename,os.R_OK):
108 pathlist = os.getenv(pathenv,
'').
split(os.pathsep)
109 for path
in pathlist:
110 f = os.path.join( path, filename )
111 if os.access( f, os.R_OK ):
113 raise RuntimeError(
"Can't read file %s, neither locally nor in %s" % (filename, pathenv) )
117 dblookupFile = ConfigDBLoader.getResolvedFileName(
"dblookup.xml",
"CORAL_DBLOOKUP_PATH")
118 dbp = ET.parse(dblookupFile)
119 listOfServices =
None
120 for logSvc
in dbp.iter(
"logicalservice"):
121 if logSvc.attrib[
"name"] != dbalias:
123 listOfServices = [ serv.attrib[
"name"]
for serv
in logSvc.iter(
"service") ]
124 if len(listOfServices) == 0:
125 raise RuntimeError(
"DB %s has no services listed in %s" % (dbalias, dblookupFile))
127 if listOfServices
is None:
128 raise RuntimeError(
"DB %s not available in %s" % (dbalias, dblookupFile))
130 if "FRONTIER_SERVER" not in os.environ:
133 listOfServices = [svc
for svc
in listOfServices
if not svc.startswith(
"frontier:")]
136 credentials = dict.fromkeys(listOfServices)
138 for svc
in filter(
lambda s : s.startswith(
"frontier:"), listOfServices):
139 credentials[svc] = dict()
140 credentials[svc][
"user"] = svc
141 credentials[svc][
"password"] =
""
144 authFile = ConfigDBLoader.getResolvedFileName(
"authentication.xml",
"CORAL_AUTH_PATH")
145 except Exception
as e:
146 log.warning(
"File authentication.xml is not available! Oracle connection cannot be established. Exception message is: %s",e)
148 for svc
in filter(
lambda s : s.startswith(
"oracle:"), listOfServices):
149 ap = ET.parse(authFile)
151 for con
in filter(
lambda c: c.attrib[
"name"]==svc, ap.iter(
"connection")):
152 credentials[svc] = dict([(par.attrib[
"name"],par.attrib[
"value"])
for par
in con])
155 raise RuntimeError(
"No credentials found for connection %s from service %s for db %s" % (con,svc,dbalias))
157 raise RuntimeError(
"More than 1 connection found in %s for service %s" % (authFile, svc))
163 ''' Read schema from connection string '''
164 if connStr.startswith(
"oracle:"):
165 [_, schema] = connStr.split(
"/")[-2:]
168 if connStr.startswith(
"frontier:"):
170 pattern =
r"frontier://ATLF/\(\)/(.*)"
171 m = re.match(pattern, connStr)
173 raise RuntimeError(
"connection string '%s' doesn't match the pattern '%s'?" % (connStr, pattern))
174 (schema, ) = m.groups()
177 if connStr.startswith(
"sqlite_file:"):
178 raise NotImplementedError(
"Python-loading of trigger configuration from sqlite has not yet been implemented")
182 ''' Read schema version form database, based on TrigConf::TrigDBLoader::schemaVersion '''
184 q =
"SELECT TS_TAG FROM {schema}.TRIGGER_SCHEMA TS"
185 query = ConfigDBLoader.getCoralQuery(session, q.format(**qdict))
186 cursor = query.execute()
189 versionTag = cursor.currentRow()[0].
data()
191 versionTagPrefix =
"Trigger-Run3-Schema-v"
192 if not versionTag.startswith(versionTagPrefix):
193 raise RuntimeError(
"Tag format error: Trigger schema version tag %s does not start with %s", versionTag, versionTagPrefix)
195 vstr = versionTag[len(versionTagPrefix)]
197 if not vstr.isdigit():
198 raise RuntimeError(
"Invalid argument when interpreting the version part %s of schema tag %s is %s", vstr, versionTag,
type(vstr))
200 log.debug(
"Found schema version %s", vstr)
203 except Exception
as e:
204 log.warning(
"Failed to read schema version: %r", e)
208 ''' Parse output, tables and condition from the query string into coral query object'''
209 query = session.nominalSchema().newQuery()
211 if qdict
is not None:
212 queryStr = queryStr.format(**qdict)
215 bindVars = coral.AttributeList()
216 bindVarsInQuery = re.findall(
r":(\w*)", queryStr)
217 if len(bindVarsInQuery) > 0
and qdict
is None:
218 log.error(
"Query has bound-variable syntax but no value dictionary is provided. Query: %s", queryStr)
219 for k
in bindVarsInQuery:
220 bindVars.extend(k,
"int")
221 bindVars[k].setData(qdict[k])
223 output = queryStr.split(
"SELECT")[1].
split(
"FROM")[0]
224 for field
in output.split(
','):
225 query.addToOutputList(field)
227 log.debug(
"Conversion for Coral of query: %s", queryStr)
229 for table
in queryStr.split(
"FROM")[1].
split(
"WHERE")[0].
split(
","):
230 tableSplit =
list(
filter(
None, table.split(
" ")))
232 query.addToTableList(tableSplit[0].
split(
".")[1], tableSplit[1])
234 if "WHERE" in queryStr:
235 cond = queryStr.split(
"WHERE")[1]
236 m = re.match(
"(.*)(?i: ORDER *BY )(.*)", cond)
238 where, order = m.groups()
239 query.setCondition(where, bindVars)
240 query.addToOrderList(order)
242 query.setCondition(cond, bindVars)
247 '''Choose query based on schema version, based on TrigConf::TrigDBLoader::getQueryDefinition '''
250 if vkey>maxDefVersion
and vkey<=schemaVersion:
253 if maxDefVersion == 0:
254 raise RuntimeError(
"No query available for schema version {0}".
format(schemaVersion))
256 return self.
query[maxDefVersion]
259 credentials: dict[str,Any] = ConfigDBLoader.getConnectionParameters(self.
dbalias)
262 log.error(
"No TriggerDB connections found for %s", self.
dbalias)
263 raise RuntimeError(f
"No TriggerDB connections found for {self.dbalias}")
265 svc = coral.ConnectionService()
266 svcconfig = svc.configuration()
267 svcconfig.disablePoolAutomaticCleanUp()
268 svcconfig.setConnectionTimeOut(0)
271 for credential
in credentials:
272 log.debug(
"Trying credentials %s",credential)
275 session = svc.connect(credential, coral.access_ReadOnly)
276 except Exception
as e:
277 log.warning(
"Failed to establish connection: %s",e)
278 failureMode =
max(1, failureMode)
282 if 'FRONTIER_SERVER' in os.environ
and os.environ[
'FRONTIER_SERVER']:
283 svcconfig.setConnectionRetrialPeriod(300)
284 svcconfig.setConnectionRetrialTimeOut(3600)
286 svcconfig.setConnectionRetrialPeriod(1)
287 svcconfig.setConnectionRetrialTimeOut(1)
290 session.transaction().
start(
True)
291 self.
schema = ConfigDBLoader.getSchema(credential)
292 qdict = {
"schema" : self.
schema,
"dbkey" : self.
dbkey }
295 schemaVersion = ConfigDBLoader.readSchemaVersion(qdict, session)
298 query = ConfigDBLoader.getCoralQuery(session, qstr, qdict)
299 cursor = query.execute()
301 except Exception
as e:
302 log.warning(f
"DB query on {credential} failed to execute.")
303 log.warning(
"Exception message: %r", e)
304 failureMode =
max(2, failureMode)
308 if not cursor.next():
310 log.warning(f
"DB query on {credential} returned empty result, likely due to non-existing key {self.dbkey}")
314 configblob = cursor.currentRow()[0].
data()
315 if type(configblob)
is not str:
316 configblob = configblob.readline()
317 config = json.loads(configblob)
318 session.transaction().
commit()
324 log.error(
"TriggerDB query: could not connect to any source for %s", self.
configType.basename)
325 log.error(
"Considered sources: %s",
", ".
join(credentials))
326 raise RuntimeError(
"TriggerDB query: could not connect to any source", self.
configType.basename)
328 log.error(
"Query failed due to wrong definition for %s", self.
configType.basename)
329 log.error(
"DB query was: %s", qstr.format(**qdict))
330 raise RuntimeError(
"Query failed due to wrong definition", self.
configType.basename)
331 elif failureMode == 3:
332 log.error(
"DB key %s does not exist for %s", self.
dbkey, self.
configType.basename)
333 raise KeyError(
"DB key does not exist", self.
dbkey, self.
configType.basename)
335 raise RuntimeError(
"Query failed for unknown reason")
342 def __init__(self, *, configType, dbalias, dbkey, crestServer):
351 With CREST all queries are defined in the CREST server
356 """get payload from crest server using request library
359 hash (str): the query part of the url as required by the REST api
362 RuntimeError: when connection or query failed
365 dict: the json content
369 url = f
"{self.crestServer}/payloads/data"
374 preq = requests.Request(method=
'GET', url=url, params=params).prepare()
375 with requests.Session()
as session:
377 resp = session.send(preq)
378 except requests.ConnectionError
as exc:
379 log.error(f
"Could not connect to crest server {self.crestServer} ({exc})")
380 raise RuntimeError(f
"Could not connect to CREST server {self.crestServer}")
382 if resp.status_code != 200:
383 log.error(f
"Error: HTTP GET request '{preq.url}' failed")
384 raise RuntimeError(f
"Query {hash} to crest failed with status code {resp.status_code}")
386 config = json.loads(resp.content)
387 self.confirmConfigType(config)
392 dblookupFile = ConfigDBLoader.getResolvedFileName(
"dblookup.xml",
"CORAL_DBLOOKUP_PATH")
393 dbp = ET.parse(dblookupFile)
394 for logSvc
in dbp.iter(
"logicalservice"):
395 if logSvc.attrib[
"name"] != dbalias:
398 for serv
in logSvc.iter(
"service"):
399 if serv.attrib[
"name"].startswith(
"oracle://"):
400 oracleService = serv.attrib[
"name"]
401 oracleServer = oracleService.split(
'/')[3]
403 raise RuntimeError(f
"DB {dbalias} has no oracle services listed in {dblookupFile}")
404 raise RuntimeError(f
"DB {dbalias} is not listed in {dblookupFile}")
408 self.
schema = ConfigCrestLoader._getDBSchemaName(self.
dbalias)
409 crest_conn = TriggerCrestUtil.getCrestConnection(self.
schema)
410 if crest_conn
is None:
411 raise RuntimeError(f
"Oracle db {self.schema} is not accessible through the crest server {self.crestServer}")
412 hash = f
"triggerdb://{crest_conn}/{self.configType.crestkey}/{self.dbkey}"
422 base class to hold the configuration (dict)
423 and provides basic functions to access and print
425 def __init__(self, configType, mainkey, filename = None, jsonString = None, dbalias = None, dbkey = None, useCrest=False, crestServer=""):
426 self.
_getLoader(configType = configType, filename = filename, jsonString = jsonString, dbalias = dbalias, dbkey = dbkey,
427 useCrest=useCrest, crestServer=crestServer)
431 def _getLoader(self, *, configType, filename = None, jsonString = None, dbalias = None, dbkey = None,
432 useCrest:bool =
False, crestServer:str =
""):
435 elif dbalias
and dbkey:
443 raise RuntimeError(
"Neither input file, nor JSON nor db alias and key provided")
461 """ returns the configuration """
466 print(json.dumps(self.
_config, indent = 4, separators=(
',',
': ')))
472 return self[
"filetype"]
475 """ print summary info, should be overwritten by derived classes """
476 log.info(
"Configuration name: {0}".
format(self.
name()))
477 log.info(
"Configuration size: {0}".
format(len(self)))
481 filename: str = self.
loader.getWriteFilename()
482 with open(filename,
'w')
as fh:
483 json.dump(self.
config(), fh, indent = 4, separators=(
',',
': '))
484 log.info(
"Wrote file %s", filename)