1 '''CMS Conditions DB Python library.
4 __author__ =
'Miguel Ojeda'
5 __copyright__ =
'Copyright 2013, CERN'
6 __credits__ = [
'Giacomo Govi',
'Miguel Ojeda',
'Andreas Pfeiffer']
7 __license__ =
'Unknown'
8 __maintainer__ =
'Giacomo Govi'
9 __email__ =
'mojedasa@cern.ch'
17 import sqlalchemy.ext.declarative
19 from sqlalchemy
import Enum
21 schema_name =
'CMS_CONDITIONS'
22 dbuser_name =
'cms_conditions'
23 dbreader_user_name =
'cms_cond_general_r'
24 dbwriter_user_name =
'cms_cond_general_w'
25 logger = logging.getLogger(__name__)
28 authPathEnvVar =
'COND_AUTH_PATH'
29 dbkey_filename =
'db.key'
30 dbkey_folder = os.path.join(
'.cms_cond',dbkey_filename)
38 ORAPRO =
'cms_orcon_adg'
41 ORAINT =
'cms_orcoff_int'
42 ORADEV =
'cms_orcoff_prep'
43 ONLINEORAPRO =
'cms_orcon_prod'
44 ONLINEORAINT =
'cmsintr_lb'
48 if logger.level == logging.NOTSET:
49 logger.setLevel(logging.WARN)
53 return hashlib.sha1(data.encode(
'ascii')).hexdigest()
60 description_length = 4000
61 hash_length = len(
hash(
''))
63 web_experts_email =
'cms-cond-dev@cern.ch'
64 offline_db_experts_email =
'cms-offlinedb-exp@cern.ch'
65 offline_db_experts_phone =
'+41 22 76 70817, or 70817 from CERN; check https://twiki.cern.ch/twiki/bin/viewauth/CMS/DBShifterHelpPage if it does not work; availability depends on the state of the LHC'
67 contact_help =
'If you need assistance, please write an email to %s and %s. If you need immediate/urgent assistance, you can call the Offline DB expert on call (%s).' % (offline_db_experts_email, web_experts_email, offline_db_experts_phone)
69 The database parameter (--db) refers to the database where the tool
70 will connect to read all the data. By default, the production account
71 (through Frontier) will be used.
73 In subcommands which take a source and a destination, --db always refers to
74 the source, and --destdb to the destination. For both of them the following
77 The database parameter can be an official alias, a filename or any
80 The official aliases are the following strings (first column):
82 Alias Level Database RO/RW Notes
83 ------------ ----------- ------------- ---------- -------------------------------
85 pro Production Frontier (ADG) read-only Default.
86 arc Archive Frontier read-only
87 int Integration Frontier read-only
88 dev Development Frontier read-only
89 boost Production Frontier read-only
90 boostprep Development Frontier read-only
92 orapro Production Oracle (ADG) read-only Password required.
93 oraarc Archive Oracle read-only Password required.
94 oraint Integration Oracle read-write Password required.
95 oradev Development Oracle read-write Password required.
97 onlineorapro Production Oracle read-write Password required. Online only.
98 onlineoraint Online Int Oracle read-write Password required. Online only.
100 Most of the time, if you are a regular user, you will want to read/copy
101 conditions from the Frontier production account. Therefore, you can omit
102 the --db parameter, unless you want to read from somewhere else,
103 e.g. from your local SQLite file.
105 In addition, the parameter may be a filename (path) pointing to a local
109 relative/path/to/file.db
110 /absolute/path/to/file.db
112 Finally, any valid SQLAlchemy URL can be used. This allows full
113 flexibility in cases where it may be needed, e.g.
115 sqlite:// In-memory, volatile SQLite DB.
116 oracle://user@devdb11 Your private Oracle DB in devdb11 [*]
118 [*] See https://account.cern.ch/ -> Services for more information
119 on personal Oracle accounts.
121 For the official aliases, the password will be asked automatically
122 interactively. The same applies for Oracle URLs where the password
123 was not provided inside it, e.g.:
125 oracle://user@devdb11 The tool will prompt you for the password.
126 oracle://user:pass@devdb11 Password inlined. [+]
128 [+] Caution: Never write passwords in command-line parameters in
129 multi-user machines (e.g. lxplus), since other users can see them
130 in the process table (e.g. ps).
132 This means that both the official aliases and the filenames are shortcuts
133 to the full SQLAlchemy URL equivalents, e.g. the following are the same:
135 relative/path/to/file.db === sqlite:///relative/path/to/file.db
136 /absolute/path/to/file.db === sqlite:////absolute/path/to/file.db
140 return 'oracle://%s/%s'%(db_service,db_schema)
144 validation =
'validation'
153 synch_list = list(x.value
for x
in list(Synchronization))
164 _Base = sqlalchemy.ext.declarative.declarative_base()
168 if schema_name
is not None:
169 name =
'%s.%s' %(schema_name, table_name)
185 fqn =
'%s.%s' %(table, column)
186 if schema
is not None:
187 fqn =
'%s.%s' %(schema,fqn)
193 dbtype_name =
'%s_%s' %(baseType.__name__,backendName)
194 members[
'__tablename__'] = baseType.__tablename__
195 members[
'__table_args__'] =
None
196 if schemaName
is not None:
197 members[
'__table_args__'] = {
'schema': schemaName }
198 for k,v
in baseType.columns.items():
199 if isinstance(v[0],DbRef):
200 refColDbt = v[0].rtype.columns[v[0].rcol][0]
201 pk = (
True if v[1]==_Col.pk
else False)
203 members[k] = sqlalchemy.Column(refColDbt,sqlalchemy.ForeignKey(
fq_col(schemaName,v[0].rtype.__tablename__,v[0].rcol)),primary_key=
True)
205 nullable = (
False if v[1] == _Col.notNull
else True)
206 members[k] = sqlalchemy.Column(refColDbt,sqlalchemy.ForeignKey(
fq_col(schemaName,v[0].rtype.__tablename__,v[0].rcol)),nullable=nullable)
207 if v[0].rtype.__name__
not in deps_reg:
208 deps_reg.add(v[0].rtype.__name__)
209 reftype_name =
'%s_%s' %(v[0].rtype.__name__,backendName)
210 members[(v[0].rtype.__name__).lower()] = sqlalchemy.orm.relationship(reftype_name)
213 members[k] = sqlalchemy.Column(v[0],primary_key=
True)
215 nullable = (
True if v[1]==_Col.nullable
else False)
216 members[k] = sqlalchemy.Column(v[0],nullable=nullable)
217 dbType = type(dbtype_name,(_Base,),members)
219 if backendName
not in db_models.keys():
220 db_models[backendName] = {}
221 db_models[backendName][baseType.__name__] = dbType
225 if tp.__table_args__
is not None:
226 return tp.__table_args__[
'schema']
230 __tablename__ =
'TAG'
231 columns = {
'name': (sqlalchemy.String(name_length),_Col.pk),
232 'time_type': (sqlalchemy.Enum(*tuple(TimeType.__members__.keys())),_Col.notNull),
233 'object_type': (sqlalchemy.String(name_length),_Col.notNull),
234 'synchronization': (sqlalchemy.Enum(*tuple(Synchronization.__members__.keys())),_Col.notNull),
235 'description': (sqlalchemy.String(description_length),_Col.notNull),
236 'last_validated_time':(sqlalchemy.BIGINT,_Col.notNull),
237 'end_of_validity':(sqlalchemy.BIGINT,_Col.notNull),
238 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
239 'modification_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
242 __tablename__ =
'TAG_METADATA'
243 columns = {
'tag_name': (
DbRef(Tag,
'name'),_Col.pk),
244 'min_serialization_v': (sqlalchemy.String(20),_Col.notNull),
245 'min_since': (sqlalchemy.BIGINT,_Col.notNull),
246 'modification_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
249 __tablename__ =
'PAYLOAD'
250 columns = {
'hash': (sqlalchemy.CHAR(hash_length),_Col.pk),
251 'object_type': (sqlalchemy.String(name_length),_Col.notNull),
252 'data': (sqlalchemy.BLOB,_Col.notNull),
253 'streamer_info':(sqlalchemy.BLOB,_Col.notNull),
254 'version':(sqlalchemy.String(20),_Col.notNull),
255 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
259 __tablename__ =
'IOV'
260 columns = {
'tag_name':(
DbRef(Tag,
'name'),_Col.pk),
261 'since':(sqlalchemy.BIGINT,_Col.pk),
262 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.pk),
263 'payload_hash':(
DbRef(Payload,
'hash'),_Col.notNull) }
267 __tablename__ =
'GLOBAL_TAG'
268 columns = {
'name':(sqlalchemy.String(name_length),_Col.pk),
269 'validity': (sqlalchemy.BIGINT,_Col.notNull),
270 'description':(sqlalchemy.String(description_length),_Col.notNull),
271 'release':(sqlalchemy.String(name_length),_Col.notNull),
272 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
273 'snapshot_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
276 __tablename__ =
'GLOBAL_TAG_MAP'
277 columns = {
'global_tag_name':(
DbRef(GlobalTag,
'name'),_Col.pk),
278 'record':(sqlalchemy.String(name_length),_Col.pk),
279 'label':(sqlalchemy.String(name_length),_Col.pk),
280 'tag_name':(
DbRef(Tag,
'name'),_Col.notNull) }
285 __tablename__ =
'TAG_LOG'
286 columns = {
'tag_name':(
DbRef(Tag,
'name'),_Col.pk),
287 'event_time':(sqlalchemy.TIMESTAMP,_Col.pk),
288 'action':(sqlalchemy.String(100),_Col.pk),
289 'user_name':(sqlalchemy.String(100),_Col.notNull),
290 'host_name':(sqlalchemy.String(100),_Col.notNull),
291 'command':(sqlalchemy.String(500),_Col.notNull),
292 'user_text':(sqlalchemy.String(4000),_Col.notNull) }
295 __tablename__ =
'RUN_INFO'
296 columns = {
'run_number':(sqlalchemy.BIGINT,_Col.pk),
297 'start_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
298 'end_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
301 __tablename__ =
'BOOST_RUN_MAP'
302 columns = {
'run_number':(sqlalchemy.BIGINT,_Col.pk),
303 'run_start_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
304 'boost_version': (sqlalchemy.String(20),_Col.notNull) }
314 if url.drivername ==
'sqlite':
316 self.
engine = sqlalchemy.create_engine(url)
318 enabled_foreign_keys = self.
engine.execute(
'pragma foreign_keys').
scalar()
319 supports_foreign_keys = enabled_foreign_keys
is not None
320 if not supports_foreign_keys:
321 logger.warning(
'Your SQLite database does not support foreign keys, so constraints will not be checked. Please upgrade.')
322 elif not enabled_foreign_keys:
323 self.
engine.execute(
'pragma foreign_keys = on')
326 self.
engine = sqlalchemy.create_engine(url)
328 self.
_session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker(bind=self.
engine))
350 logging.debug(
'Loading db types...')
364 basename = theType.__name__
380 return _Base.metadata
403 '''Tests whether the current DB looks like a valid CMS Conditions one.
410 ret =
all([self.
engine.dialect.has_table(engine_connection, table.__tablename__,
getSchema(table))
for table
in [_Tag, _IOV, _Payload]])
411 engine_connection.close()
415 '''Initializes a database.
417 logging.info(
'Initializing database...')
419 logging.debug(
'Dropping tables...')
424 logging.debug(
'Creating tables...')
436 sessiondict[key %(session1._url.drivername,session1._url.host)] = session1
437 sessiondict[key %(session2._url.drivername,session2._url.host)] = session2
438 masterkey = key %(
'oracle',ONLINEORAPRO)
439 if masterkey
in sessiondict.keys():
440 return sessiondict[masterkey]
441 adgkey = key %(
'oracle',ORAPRO)
442 if adgkey
in sessiondict.keys():
443 return sessiondict[adgkey]
444 frontierkey = key %(
'frontier',PRO)
445 if frontierkey
in sessiondict.keys():
446 return sessiondict[frontierkey]
449 session = conn.session()
457 return subprocess.Popen([
'cmsGetFnConnect',
'frontier://%s' % database], stdout = subprocess.PIPE).
communicate()[0].
strip()
460 if technology ==
'frontier':
463 py3k = sys.version_info >= (3, 0)
468 elif technology ==
'oracle':
469 return '%s://%s@%s' % (technology, schema_name, service)
473 if database.startswith(
'sqlite:')
or database.startswith(
'sqlite_file:'):
474 ignore, database = database.split(
':',1)
476 if ':' in database
and '://' not in database:
477 database, schema = database.split(
':')
481 'pro' : (
'frontier',
'PromptProd', {
'R': schema_name }, ),
482 'arc' : (
'frontier',
'FrontierArc', {
'R': schema_name }, ),
483 'int' : (
'frontier',
'FrontierInt', {
'R': schema_name }, ),
484 'dev' : (
'frontier',
'FrontierPrep', {
'R': schema_name }, ),
486 'orapro': (
'oracle',
'cms_orcon_adg', {
'R': dbreader_user_name }, ),
487 'oraarc': (
'oracle',
'cmsarc_lb', {
'R': dbreader_user_name }, ),
489 'oraint': (
'oracle',
'cms_orcoff_int', {
'R': dbreader_user_name,
490 'W': dbwriter_user_name }, ),
491 'oradev': (
'oracle',
'cms_orcoff_prep', {
'R': dbreader_user_name,
492 'W': dbwriter_user_name }, ),
493 'onlineorapro': (
'oracle',
'cms_orcon_prod', {
'R': dbreader_user_name,
494 'W': dbwriter_user_name }, ),
495 'onlineoraint': (
'oracle',
'cmsintr_lb', {
'R': dbreader_user_name,
496 'W': dbwriter_user_name }, ),
499 if database
in officialdbs.keys():
500 key = (
'R' if read_only
else 'W')
501 mapping = officialdbs[database]
504 schema_dict = mapping[2]
505 if key
in schema_dict.keys():
508 raise Exception(
"Read-only database %s://%s cannot be accessed in update mode." %(tech,service))
510 logging.debug(
'connection string set to "%s"' % database)
513 url = sqlalchemy.engine.url.make_url(database)
514 except sqlalchemy.exc.ArgumentError:
515 url = sqlalchemy.engine.url.make_url(
'sqlite:///%s' % database)
519 '''Returns a Connection instance to the CMS Condition DB.
521 See database_help for the description of the database parameter.
523 The verbosity level is as follows:
525 0 = No output (default).
526 1 = SQL statements issued, including their parameters.
527 2 = In addition, results of the queries (all rows and the column headers).
530 if url.drivername ==
'oracle':
531 if url.username
is None:
532 logging.error(
'Could not resolve the username for the connection %s. Please provide a connection in the format oracle://[user]:[pass]@[host]' %url )
533 raise Exception(
'Connection format error: %s' %url )
534 if url.password
is None:
536 if authPathEnvVar
in os.environ:
537 authPath = os.environ[authPathEnvVar]
538 explicit_auth =
False
539 if authPath
is not None:
540 dbkey_path = os.path.join(authPath,dbkey_folder)
541 if not os.path.exists(dbkey_path):
542 authFile = os.path.join(authPath,
'.netrc')
543 if os.path.exists(authFile):
544 entryKey = url.host.lower()+
"/"+url.username.lower()
545 logging.debug(
'Looking up credentials for %s in file %s ' %(entryKey,authFile) )
547 params = netrc.netrc( authFile ).authenticators(entryKey)
548 if params
is not None:
549 (username, account, password) = params
550 url.username = username
551 url.password = password
553 msg =
'The entry %s has not been found in the .netrc file.' %entryKey
558 import pluginCondDBPyBind11Interface
as credential_store
559 connect_for_update = ( url.username == dbwriter_user_name )
561 logging.debug(
'Using db key to get credentials for %s' %connection_string )
562 (username,password) = credential_store.get_db_credentials(connection_string,connect_for_update,authPath)
563 url.username = username
564 url.password = password
567 pwd = getpass.getpass(
'Password for %s: ' %
str(url))
568 if pwd
is None or pwd ==
'':
569 pwd = getpass.getpass(
'Password for %s: ' %
str(url))
570 if pwd
is None or pwd ==
'':
571 raise Exception(
'Empty password provided, bailing out...')
575 logging.getLogger(
'sqlalchemy.engine').setLevel(logging.INFO)
578 logging.getLogger(
'sqlalchemy.engine').setLevel(logging.DEBUG)
586 ret = session.query(primary_key).\
587 filter(primary_key == value).\
589 except sqlalchemy.exc.OperationalError:
595 '''To be used inside filter().
598 if timestamp
is None:
603 return sqlalchemy.literal(
True) == sqlalchemy.literal(
True)
605 return conddb.IOV.insertion_time <= _parse_timestamp(timestamp)
609 is_tag =
_exists(session, Tag.name, name)
612 result[
'type'] =
'Tag'
613 result[
'name'] = session.query(Tag).get(name).name
614 result[
'timeType'] = session.query(Tag.time_type).\
615 filter(Tag.name == name).\
618 result[
'iovs'] = session.query(IOV.since, IOV.insertion_time, IOV.payload_hash, Payload.object_type).\
621 IOV.tag_name == name,
624 order_by(IOV.since.desc(), IOV.insertion_time.desc()).\
626 order_by(IOV.since, IOV.insertion_time).\
630 is_global_tag =
_exists(session, GlobalTag.name, name)
632 result[
'type'] =
'GlobalTag'
633 result[
'name'] = session.query(GlobalTag).get(name)
634 result[
'tags'] = session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
635 filter(GlobalTagMap.global_tag_name == name).\
636 order_by(GlobalTagMap.record, GlobalTagMap.label).\
638 except sqlalchemy.exc.OperationalError:
639 sys.stderr.write(
"No table for GlobalTags found in DB.\n\n")
641 if not is_tag
and not is_global_tag:
642 raise Exception(
'There is no tag or global tag named %s in the database.' % name)
648 data, payloadType = session.query(Payload.data, Payload.object_type).
filter(Payload.hash == hash).
one()