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 authPathEnvVar =
'COND_AUTH_PATH'
22 schema_name =
'CMS_CONDITIONS'
23 dbuser_name =
'cms_conditions'
24 dbreader_user_name =
'cms_cond_general_r'
25 dbwriter_user_name =
'cms_cond_general_w'
26 devdbwriter_user_name =
'cms_cond_general_w'
27 logger = logging.getLogger(__name__)
35 ORAPRO =
'cms_orcon_adg'
38 ORAINT =
'cms_orcoff_int'
39 ORADEV =
'cms_orcoff_prep'
40 ONLINEORAPRO =
'cms_orcon_prod'
41 ONLINEORAINT =
'cmsintr_lb'
45 if logger.level == logging.NOTSET:
46 logger.setLevel(logging.WARN)
50 return hashlib.sha1(data.encode(
'ascii')).hexdigest()
57 description_length = 4000
58 hash_length = len(
hash(
''))
60 web_experts_email =
'cms-cond-dev@cern.ch'
61 offline_db_experts_email =
'cms-offlinedb-exp@cern.ch'
62 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'
64 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)
66 The database parameter (--db) refers to the database where the tool
67 will connect to read all the data. By default, the production account
68 (through Frontier) will be used.
70 In subcommands which take a source and a destination, --db always refers to
71 the source, and --destdb to the destination. For both of them the following
74 The database parameter can be an official alias, a filename or any
77 The official aliases are the following strings (first column):
79 Alias Level Database RO/RW Notes
80 ------------ ----------- ------------- ---------- -------------------------------
82 pro Production Frontier (ADG) read-only Default.
83 arc Archive Frontier read-only
84 int Integration Frontier read-only
85 dev Development Frontier read-only
86 boost Production Frontier read-only
87 boostprep Development Frontier read-only
89 orapro Production Oracle (ADG) read-only Password required.
90 oraarc Archive Oracle read-only Password required.
91 oraint Integration Oracle read-write Password required.
92 oradev Development Oracle read-write Password required.
94 onlineorapro Production Oracle read-write Password required. Online only.
95 onlineoraint Online Int Oracle read-write Password required. Online only.
97 Most of the time, if you are a regular user, you will want to read/copy
98 conditions from the Frontier production account. Therefore, you can omit
99 the --db parameter, unless you want to read from somewhere else,
100 e.g. from your local SQLite file.
102 In addition, the parameter may be a filename (path) pointing to a local
106 relative/path/to/file.db
107 /absolute/path/to/file.db
109 Finally, any valid SQLAlchemy URL can be used. This allows full
110 flexibility in cases where it may be needed, e.g.
112 sqlite:// In-memory, volatile SQLite DB.
113 oracle://user@devdb11 Your private Oracle DB in devdb11 [*]
115 [*] See https://account.cern.ch/ -> Services for more information
116 on personal Oracle accounts.
118 For the official aliases, the password will be asked automatically
119 interactively. The same applies for Oracle URLs where the password
120 was not provided inside it, e.g.:
122 oracle://user@devdb11 The tool will prompt you for the password.
123 oracle://user:pass@devdb11 Password inlined. [+]
125 [+] Caution: Never write passwords in command-line parameters in
126 multi-user machines (e.g. lxplus), since other users can see them
127 in the process table (e.g. ps).
129 This means that both the official aliases and the filenames are shortcuts
130 to the full SQLAlchemy URL equivalents, e.g. the following are the same:
132 relative/path/to/file.db === sqlite:///relative/path/to/file.db
133 /absolute/path/to/file.db === sqlite:////absolute/path/to/file.db
138 validation =
'validation'
156 _Base = sqlalchemy.ext.declarative.declarative_base()
160 if schema_name
is not None:
161 name =
'%s.%s' %(schema_name, table_name)
177 fqn =
'%s.%s' %(table, column)
178 if schema
is not None:
179 fqn =
'%s.%s' %(schema,fqn)
185 dbtype_name =
'%s_%s' %(baseType.__name__,backendName)
186 members[
'__tablename__'] = baseType.__tablename__
187 members[
'__table_args__'] =
None
188 if schemaName
is not None:
189 members[
'__table_args__'] = {
'schema': schemaName }
190 for k,v
in baseType.columns.items():
191 if isinstance(v[0],DbRef):
192 refColDbt = v[0].rtype.columns[v[0].rcol][0]
193 pk = (
True if v[1]==_Col.pk
else False)
195 members[k] = sqlalchemy.Column(refColDbt,sqlalchemy.ForeignKey(
fq_col(schemaName,v[0].rtype.__tablename__,v[0].rcol)),primary_key=
True)
197 nullable = (
False if v[1] == _Col.notNull
else True)
198 members[k] = sqlalchemy.Column(refColDbt,sqlalchemy.ForeignKey(
fq_col(schemaName,v[0].rtype.__tablename__,v[0].rcol)),nullable=nullable)
199 if v[0].rtype.__name__
not in deps_reg:
200 deps_reg.add(v[0].rtype.__name__)
201 reftype_name =
'%s_%s' %(v[0].rtype.__name__,backendName)
202 members[(v[0].rtype.__name__).lower()] = sqlalchemy.orm.relationship(reftype_name)
205 members[k] = sqlalchemy.Column(v[0],primary_key=
True)
207 nullable = (
True if v[1]==_Col.nullable
else False)
208 members[k] = sqlalchemy.Column(v[0],nullable=nullable)
209 dbType = type(dbtype_name,(_Base,),members)
211 if backendName
not in db_models.keys():
212 db_models[backendName] = {}
213 db_models[backendName][baseType.__name__] = dbType
217 if tp.__table_args__
is not None:
218 return tp.__table_args__[
'schema']
222 __tablename__ =
'TAG'
223 columns = {
'name': (sqlalchemy.String(name_length),_Col.pk),
224 'time_type': (sqlalchemy.Enum(*tuple(TimeType.__members__.keys())),_Col.notNull),
225 'object_type': (sqlalchemy.String(name_length),_Col.notNull),
226 'synchronization': (sqlalchemy.Enum(*tuple(Synchronization.__members__.keys())),_Col.notNull),
227 'description': (sqlalchemy.String(description_length),_Col.notNull),
228 'last_validated_time':(sqlalchemy.BIGINT,_Col.notNull),
229 'end_of_validity':(sqlalchemy.BIGINT,_Col.notNull),
230 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
231 'modification_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
234 __tablename__ =
'TAG_METADATA'
235 columns = {
'tag_name': (
DbRef(Tag,
'name'),_Col.pk),
236 'min_serialization_v': (sqlalchemy.String(20),_Col.notNull),
237 'min_since': (sqlalchemy.BIGINT,_Col.notNull),
238 'modification_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
241 __tablename__ =
'PAYLOAD'
242 columns = {
'hash': (sqlalchemy.CHAR(hash_length),_Col.pk),
243 'object_type': (sqlalchemy.String(name_length),_Col.notNull),
244 'data': (sqlalchemy.BLOB,_Col.notNull),
245 'streamer_info':(sqlalchemy.BLOB,_Col.notNull),
246 'version':(sqlalchemy.String(20),_Col.notNull),
247 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
251 __tablename__ =
'IOV'
252 columns = {
'tag_name':(
DbRef(Tag,
'name'),_Col.pk),
253 'since':(sqlalchemy.BIGINT,_Col.pk),
254 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.pk),
255 'payload_hash':(
DbRef(Payload,
'hash'),_Col.notNull) }
259 __tablename__ =
'GLOBAL_TAG'
260 columns = {
'name':(sqlalchemy.String(name_length),_Col.pk),
261 'validity': (sqlalchemy.BIGINT,_Col.notNull),
262 'description':(sqlalchemy.String(description_length),_Col.notNull),
263 'release':(sqlalchemy.String(name_length),_Col.notNull),
264 'insertion_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
265 'snapshot_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
268 __tablename__ =
'GLOBAL_TAG_MAP'
269 columns = {
'global_tag_name':(
DbRef(GlobalTag,
'name'),_Col.pk),
270 'record':(sqlalchemy.String(name_length),_Col.pk),
271 'label':(sqlalchemy.String(name_length),_Col.pk),
272 'tag_name':(
DbRef(Tag,
'name'),_Col.notNull) }
277 __tablename__ =
'TAG_LOG'
278 columns = {
'tag_name':(
DbRef(Tag,
'name'),_Col.pk),
279 'event_time':(sqlalchemy.TIMESTAMP,_Col.pk),
280 'action':(sqlalchemy.String(100),_Col.pk),
281 'user_name':(sqlalchemy.String(100),_Col.notNull),
282 'host_name':(sqlalchemy.String(100),_Col.notNull),
283 'command':(sqlalchemy.String(500),_Col.notNull),
284 'user_text':(sqlalchemy.String(4000),_Col.notNull) }
287 __tablename__ =
'RUN_INFO'
288 columns = {
'run_number':(sqlalchemy.BIGINT,_Col.pk),
289 'start_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
290 'end_time':(sqlalchemy.TIMESTAMP,_Col.notNull) }
293 __tablename__ =
'BOOST_RUN_MAP'
294 columns = {
'run_number':(sqlalchemy.BIGINT,_Col.pk),
295 'run_start_time':(sqlalchemy.TIMESTAMP,_Col.notNull),
296 'boost_version': (sqlalchemy.String(20),_Col.notNull) }
306 if url.drivername ==
'sqlite':
308 self.
engine = sqlalchemy.create_engine(url)
310 enabled_foreign_keys = self.
engine.execute(
'pragma foreign_keys').
scalar()
311 supports_foreign_keys = enabled_foreign_keys
is not None
312 if not supports_foreign_keys:
313 logger.warning(
'Your SQLite database does not support foreign keys, so constraints will not be checked. Please upgrade.')
314 elif not enabled_foreign_keys:
315 self.
engine.execute(
'pragma foreign_keys = on')
318 self.
engine = sqlalchemy.create_engine(url)
320 self.
_session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker(bind=self.
engine))
342 logging.debug(
' ... using db "%s", schema "%s"' % (url, self.
_schemaName) )
343 logging.debug(
'Loading db types...')
357 basename = theType.__name__
373 return _Base.metadata
396 '''Tests whether the current DB looks like a valid CMS Conditions one.
403 ret =
all([self.
engine.dialect.has_table(engine_connection, table.__tablename__,
getSchema(table))
for table
in [_Tag, _IOV, _Payload]])
404 engine_connection.close()
408 '''Initializes a database.
410 logging.info(
'Initializing database...')
412 logging.debug(
'Dropping tables...')
417 logging.debug(
'Creating tables...')
429 sessiondict[key %(session1._url.drivername,session1._url.host)] = session1
430 sessiondict[key %(session2._url.drivername,session2._url.host)] = session2
431 masterkey = key %(
'oracle',ONLINEORAPRO)
432 if masterkey
in sessiondict.keys():
433 return sessiondict[masterkey]
434 adgkey = key %(
'oracle',ORAPRO)
435 if adgkey
in sessiondict.keys():
436 return sessiondict[adgkey]
437 frontierkey = key %(
'frontier',PRO)
438 if frontierkey
in sessiondict.keys():
439 return sessiondict[frontierkey]
442 session = conn.session()
450 return subprocess.Popen([
'cmsGetFnConnect',
'frontier://%s' % database], stdout = subprocess.PIPE).
communicate()[0].
strip()
453 if technology ==
'frontier':
456 py3k = sys.version_info >= (3, 0)
461 elif technology ==
'oracle':
462 return '%s://%s@%s' % (technology, schema_name, service)
466 if database.startswith(
'sqlite:')
or database.startswith(
'sqlite_file:'):
467 ignore, database = database.split(
':',1)
469 if ':' in database
and '://' not in database:
470 database, schema = database.split(
':')
474 'pro' : (
'frontier',
'PromptProd', {
'R': schema_name }, ),
475 'arc' : (
'frontier',
'FrontierArc', {
'R': schema_name }, ),
476 'int' : (
'frontier',
'FrontierInt', {
'R': schema_name }, ),
477 'dev' : (
'frontier',
'FrontierPrep', {
'R': schema_name }, ),
479 'orapro': (
'oracle',
'cms_orcon_adg', {
'R': dbreader_user_name }, ),
480 'oraarc': (
'oracle',
'cmsarc_lb', {
'R': dbreader_user_name }, ),
482 'oraint': (
'oracle',
'cms_orcoff_int', {
'R': dbreader_user_name,
483 'W': dbwriter_user_name }, ),
484 'oradev': (
'oracle',
'cms_orcoff_prep', {
'R': dbreader_user_name,
485 'W': devdbwriter_user_name }, ),
486 'onlineorapro': (
'oracle',
'cms_orcon_prod', {
'R': dbreader_user_name,
487 'W': dbwriter_user_name }, ),
488 'onlineoraint': (
'oracle',
'cmsintr_lb', {
'R': dbreader_user_name,
489 'W': dbwriter_user_name }, ),
492 if database
in officialdbs.keys():
493 key = (
'R' if read_only
else 'W')
494 mapping = officialdbs[database]
497 schema_dict = mapping[2]
498 if key
in schema_dict.keys():
501 raise Exception(
"Read-only database %s://%s cannot be accessed in update mode." %(tech,service))
503 logging.debug(
'connection string set to "%s"' % database)
506 url = sqlalchemy.engine.url.make_url(database)
507 except sqlalchemy.exc.ArgumentError:
508 url = sqlalchemy.engine.url.make_url(
'sqlite:///%s' % database)
512 '''Returns a Connection instance to the CMS Condition DB.
514 See database_help for the description of the database parameter.
516 The verbosity level is as follows:
518 0 = No output (default).
519 1 = SQL statements issued, including their parameters.
520 2 = In addition, results of the queries (all rows and the column headers).
523 if url.drivername ==
'oracle':
524 if url.username
is None:
525 logging.error(
'Could not resolve the username for the connection %s. Please provide a connection in the format oracle://[user]:[pass]@[host]' %url )
526 raise Exception(
'Connection format error: %s' %url )
527 if url.password
is None:
529 if authPathEnvVar
in os.environ:
530 authPath = os.environ[authPathEnvVar]
532 if authPath
is not None:
533 authFile = os.path.join(authPath,
'.netrc')
534 if authFile
is not None:
535 entryKey = url.host.lower()+
"/"+url.username.lower()
536 logging.debug(
'Looking up credentials for %s in file %s ' %(entryKey,authFile) )
538 params = netrc.netrc( authFile ).authenticators(entryKey)
539 if params
is not None:
540 (username, account, password) = params
541 url.password = password
543 msg =
'The entry %s has not been found in the .netrc file.' %entryKey
547 pwd = getpass.getpass(
'Password for %s: ' %
str(url))
548 if pwd
is None or pwd ==
'':
549 pwd = getpass.getpass(
'Password for %s: ' %
str(url))
550 if pwd
is None or pwd ==
'':
551 raise Exception(
'Empty password provided, bailing out...')
555 logging.getLogger(
'sqlalchemy.engine').setLevel(logging.INFO)
558 logging.getLogger(
'sqlalchemy.engine').setLevel(logging.DEBUG)
566 ret = session.query(primary_key).\
567 filter(primary_key == value).\
569 except sqlalchemy.exc.OperationalError:
575 '''To be used inside filter().
578 if timestamp
is None:
583 return sqlalchemy.literal(
True) == sqlalchemy.literal(
True)
585 return conddb.IOV.insertion_time <= _parse_timestamp(timestamp)
589 is_tag =
_exists(session, Tag.name, name)
592 result[
'type'] =
'Tag'
593 result[
'name'] = session.query(Tag).get(name).name
594 result[
'timeType'] = session.query(Tag.time_type).\
595 filter(Tag.name == name).\
598 result[
'iovs'] = session.query(IOV.since, IOV.insertion_time, IOV.payload_hash, Payload.object_type).\
601 IOV.tag_name == name,
604 order_by(IOV.since.desc(), IOV.insertion_time.desc()).\
606 order_by(IOV.since, IOV.insertion_time).\
610 is_global_tag =
_exists(session, GlobalTag.name, name)
612 result[
'type'] =
'GlobalTag'
613 result[
'name'] = session.query(GlobalTag).get(name)
614 result[
'tags'] = session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
615 filter(GlobalTagMap.global_tag_name == name).\
616 order_by(GlobalTagMap.record, GlobalTagMap.label).\
618 except sqlalchemy.exc.OperationalError:
619 sys.stderr.write(
"No table for GlobalTags found in DB.\n\n")
621 if not is_tag
and not is_global_tag:
622 raise Exception(
'There is no tag or global tag named %s in the database.' % name)
628 data, payloadType = session.query(Payload.data, Payload.object_type).
filter(Payload.hash == hash).one()