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'
162 _Base = sqlalchemy.ext.declarative.declarative_base()
166 if schema_name
is not None:
167 name =
'%s.%s' %(schema_name, table_name)
183 fqn =
'%s.%s' %(table, column)
184 if schema
is not None:
185 fqn =
'%s.%s' %(schema,fqn)
191 dbtype_name =
'%s_%s' %(baseType.__name__,backendName)
192 members[
'__tablename__'] = baseType.__tablename__
193 members[
'__table_args__'] =
None
194 if schemaName
is not None:
195 members[
'__table_args__'] = {
'schema': schemaName }
196 for k,v
in baseType.columns.items():
197 if isinstance(v[0],DbRef):
198 refColDbt = v[0].rtype.columns[v[0].rcol][0]
199 pk = (
True if v[1]==_Col.pk
else False)
201 members[k] = sqlalchemy.Column(refColDbt,sqlalchemy.ForeignKey(
fq_col(schemaName,v[0].rtype.__tablename__,v[0].rcol)),primary_key=
True)
203 nullable = (
False if v[1] == _Col.notNull
else True)
204 members[k] = sqlalchemy.Column(refColDbt,sqlalchemy.ForeignKey(
fq_col(schemaName,v[0].rtype.__tablename__,v[0].rcol)),nullable=nullable)
205 if v[0].rtype.__name__
not in deps_reg:
206 deps_reg.add(v[0].rtype.__name__)
207 reftype_name =
'%s_%s' %(v[0].rtype.__name__,backendName)
208 members[(v[0].rtype.__name__).lower()] = sqlalchemy.orm.relationship(reftype_name)
211 members[k] = sqlalchemy.Column(v[0],primary_key=
True)
213 nullable = (
True if v[1]==_Col.nullable
else False)
214 members[k] = sqlalchemy.Column(v[0],nullable=nullable)
215 dbType = type(dbtype_name,(_Base,),members)
217 if backendName
not in db_models.keys():
218 db_models[backendName] = {}
219 db_models[backendName][baseType.__name__] = dbType
223 if tp.__table_args__
is not None:
224 return tp.__table_args__[
'schema']
228 __tablename__ =
'TAG'
229 columns = {
'name': (sqlalchemy.String(name_length),_Col.pk),
230 'time_type': (sqlalchemy.Enum(*tuple(TimeType.__members__.keys())),_Col.notNull),
231 'object_type': (sqlalchemy.String(name_length),_Col.notNull),
232 'synchronization': (sqlalchemy.Enum(*tuple(Synchronization.__members__.keys())),_Col.notNull),
233 'description': (sqlalchemy.String(description_length),_Col.notNull),
234 'last_validated_time':(sqlalchemy.BIGINT,_Col.notNull),
235 'end_of_validity':(sqlalchemy.BIGINT,_Col.notNull),
236 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
237 'modification_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
240 __tablename__ =
'TAG_METADATA'
241 columns = {
'tag_name': (
DbRef(Tag,
'name'),_Col.pk),
242 'min_serialization_v': (sqlalchemy.String(20),_Col.notNull),
243 'min_since': (sqlalchemy.BIGINT,_Col.notNull),
244 'modification_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
247 __tablename__ =
'PAYLOAD'
248 columns = {
'hash': (sqlalchemy.CHAR(hash_length),_Col.pk),
249 'object_type': (sqlalchemy.String(name_length),_Col.notNull),
250 'data': (sqlalchemy.BLOB,_Col.notNull),
251 'streamer_info':(sqlalchemy.BLOB,_Col.notNull),
252 'version':(sqlalchemy.String(20),_Col.notNull),
253 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
257 __tablename__ =
'IOV'
258 columns = {
'tag_name':(
DbRef(Tag,
'name'),_Col.pk),
259 'since':(sqlalchemy.BIGINT,_Col.pk),
260 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.pk),
261 'payload_hash':(
DbRef(Payload,
'hash'),_Col.notNull) }
265 __tablename__ =
'GLOBAL_TAG'
266 columns = {
'name':(sqlalchemy.String(name_length),_Col.pk),
267 'validity': (sqlalchemy.BIGINT,_Col.notNull),
268 'description':(sqlalchemy.String(description_length),_Col.notNull),
269 'release':(sqlalchemy.String(name_length),_Col.notNull),
270 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
271 'snapshot_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
274 __tablename__ =
'GLOBAL_TAG_MAP'
275 columns = {
'global_tag_name':(
DbRef(GlobalTag,
'name'),_Col.pk),
276 'record':(sqlalchemy.String(name_length),_Col.pk),
277 'label':(sqlalchemy.String(name_length),_Col.pk),
278 'tag_name':(
DbRef(Tag,
'name'),_Col.notNull) }
283 __tablename__ =
'TAG_LOG'
284 columns = {
'tag_name':(
DbRef(Tag,
'name'),_Col.pk),
285 'event_time':(sqlalchemy.TIMESTAMP,_Col.pk),
286 'action':(sqlalchemy.String(100),_Col.pk),
287 'user_name':(sqlalchemy.String(100),_Col.notNull),
288 'host_name':(sqlalchemy.String(100),_Col.notNull),
289 'command':(sqlalchemy.String(500),_Col.notNull),
290 'user_text':(sqlalchemy.String(4000),_Col.notNull) }
293 __tablename__ =
'RUN_INFO'
294 columns = {
'run_number':(sqlalchemy.BIGINT,_Col.pk),
295 'start_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
296 'end_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
299 __tablename__ =
'BOOST_RUN_MAP'
300 columns = {
'run_number':(sqlalchemy.BIGINT,_Col.pk),
301 'run_start_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
302 'boost_version': (sqlalchemy.String(20),_Col.notNull) }
312 if url.drivername ==
'sqlite':
314 self.
engine = sqlalchemy.create_engine(url)
316 enabled_foreign_keys = self.
engine.execute(
'pragma foreign_keys').
scalar()
317 supports_foreign_keys = enabled_foreign_keys
is not None
318 if not supports_foreign_keys:
319 logger.warning(
'Your SQLite database does not support foreign keys, so constraints will not be checked. Please upgrade.')
320 elif not enabled_foreign_keys:
321 self.
engine.execute(
'pragma foreign_keys = on')
324 self.
engine = sqlalchemy.create_engine(url)
326 self.
_session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker(bind=self.
engine))
348 logging.debug(
'Loading db types...')
362 basename = theType.__name__
378 return _Base.metadata
401 '''Tests whether the current DB looks like a valid CMS Conditions one.
408 ret =
all([self.
engine.dialect.has_table(engine_connection, table.__tablename__,
getSchema(table))
for table
in [_Tag, _IOV, _Payload]])
409 engine_connection.close()
413 '''Initializes a database.
415 logging.info(
'Initializing database...')
417 logging.debug(
'Dropping tables...')
422 logging.debug(
'Creating tables...')
434 sessiondict[key %(session1._url.drivername,session1._url.host)] = session1
435 sessiondict[key %(session2._url.drivername,session2._url.host)] = session2
436 masterkey = key %(
'oracle',ONLINEORAPRO)
437 if masterkey
in sessiondict.keys():
438 return sessiondict[masterkey]
439 adgkey = key %(
'oracle',ORAPRO)
440 if adgkey
in sessiondict.keys():
441 return sessiondict[adgkey]
442 frontierkey = key %(
'frontier',PRO)
443 if frontierkey
in sessiondict.keys():
444 return sessiondict[frontierkey]
447 session = conn.session()
455 return subprocess.Popen([
'cmsGetFnConnect',
'frontier://%s' % database], stdout = subprocess.PIPE).
communicate()[0].
strip()
458 if technology ==
'frontier':
461 py3k = sys.version_info >= (3, 0)
466 elif technology ==
'oracle':
467 return '%s://%s@%s' % (technology, schema_name, service)
471 if database.startswith(
'sqlite:')
or database.startswith(
'sqlite_file:'):
472 ignore, database = database.split(
':',1)
474 if ':' in database
and '://' not in database:
475 database, schema = database.split(
':')
479 'pro' : (
'frontier',
'PromptProd', {
'R': schema_name }, ),
480 'arc' : (
'frontier',
'FrontierArc', {
'R': schema_name }, ),
481 'int' : (
'frontier',
'FrontierInt', {
'R': schema_name }, ),
482 'dev' : (
'frontier',
'FrontierPrep', {
'R': schema_name }, ),
484 'orapro': (
'oracle',
'cms_orcon_adg', {
'R': dbreader_user_name }, ),
485 'oraarc': (
'oracle',
'cmsarc_lb', {
'R': dbreader_user_name }, ),
487 'oraint': (
'oracle',
'cms_orcoff_int', {
'R': dbreader_user_name,
488 'W': dbwriter_user_name }, ),
489 'oradev': (
'oracle',
'cms_orcoff_prep', {
'R': dbreader_user_name,
490 'W': dbwriter_user_name }, ),
491 'onlineorapro': (
'oracle',
'cms_orcon_prod', {
'R': dbreader_user_name,
492 'W': dbwriter_user_name }, ),
493 'onlineoraint': (
'oracle',
'cmsintr_lb', {
'R': dbreader_user_name,
494 'W': dbwriter_user_name }, ),
497 if database
in officialdbs.keys():
498 key = (
'R' if read_only
else 'W')
499 mapping = officialdbs[database]
502 schema_dict = mapping[2]
503 if key
in schema_dict.keys():
506 raise Exception(
"Read-only database %s://%s cannot be accessed in update mode." %(tech,service))
508 logging.debug(
'connection string set to "%s"' % database)
511 url = sqlalchemy.engine.url.make_url(database)
512 except sqlalchemy.exc.ArgumentError:
513 url = sqlalchemy.engine.url.make_url(
'sqlite:///%s' % database)
517 '''Returns a Connection instance to the CMS Condition DB.
519 See database_help for the description of the database parameter.
521 The verbosity level is as follows:
523 0 = No output (default).
524 1 = SQL statements issued, including their parameters.
525 2 = In addition, results of the queries (all rows and the column headers).
528 if url.drivername ==
'oracle':
529 if url.username
is None:
530 logging.error(
'Could not resolve the username for the connection %s. Please provide a connection in the format oracle://[user]:[pass]@[host]' %url )
531 raise Exception(
'Connection format error: %s' %url )
532 if url.password
is None:
534 if authPathEnvVar
in os.environ:
535 authPath = os.environ[authPathEnvVar]
536 explicit_auth =
False
537 if authPath
is not None:
538 dbkey_path = os.path.join(authPath,dbkey_folder)
539 if not os.path.exists(dbkey_path):
540 authFile = os.path.join(authPath,
'.netrc')
541 if os.path.exists(authFile):
542 entryKey = url.host.lower()+
"/"+url.username.lower()
543 logging.debug(
'Looking up credentials for %s in file %s ' %(entryKey,authFile) )
545 params = netrc.netrc( authFile ).authenticators(entryKey)
546 if params
is not None:
547 (username, account, password) = params
548 url.username = username
549 url.password = password
551 msg =
'The entry %s has not been found in the .netrc file.' %entryKey
556 import pluginCondDBPyBind11Interface
as credential_store
557 connect_for_update = ( url.username == dbwriter_user_name )
559 logging.debug(
'Using db key to get credentials for %s' %connection_string )
560 (username,password) = credential_store.get_db_credentials(connection_string,connect_for_update,authPath)
561 url.username = username
562 url.password = password
565 pwd = getpass.getpass(
'Password for %s: ' %
str(url))
566 if pwd
is None or pwd ==
'':
567 pwd = getpass.getpass(
'Password for %s: ' %
str(url))
568 if pwd
is None or pwd ==
'':
569 raise Exception(
'Empty password provided, bailing out...')
573 logging.getLogger(
'sqlalchemy.engine').setLevel(logging.INFO)
576 logging.getLogger(
'sqlalchemy.engine').setLevel(logging.DEBUG)
584 ret = session.query(primary_key).\
585 filter(primary_key == value).\
587 except sqlalchemy.exc.OperationalError:
593 '''To be used inside filter().
596 if timestamp
is None:
601 return sqlalchemy.literal(
True) == sqlalchemy.literal(
True)
603 return conddb.IOV.insertion_time <= _parse_timestamp(timestamp)
607 is_tag =
_exists(session, Tag.name, name)
610 result[
'type'] =
'Tag'
611 result[
'name'] = session.query(Tag).get(name).name
612 result[
'timeType'] = session.query(Tag.time_type).\
613 filter(Tag.name == name).\
616 result[
'iovs'] = session.query(IOV.since, IOV.insertion_time, IOV.payload_hash, Payload.object_type).\
619 IOV.tag_name == name,
622 order_by(IOV.since.desc(), IOV.insertion_time.desc()).\
624 order_by(IOV.since, IOV.insertion_time).\
628 is_global_tag =
_exists(session, GlobalTag.name, name)
630 result[
'type'] =
'GlobalTag'
631 result[
'name'] = session.query(GlobalTag).get(name)
632 result[
'tags'] = session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
633 filter(GlobalTagMap.global_tag_name == name).\
634 order_by(GlobalTagMap.record, GlobalTagMap.label).\
636 except sqlalchemy.exc.OperationalError:
637 sys.stderr.write(
"No table for GlobalTags found in DB.\n\n")
639 if not is_tag
and not is_global_tag:
640 raise Exception(
'There is no tag or global tag named %s in the database.' % name)
646 data, payloadType = session.query(Payload.data, Payload.object_type).
filter(Payload.hash == hash).
one()