CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
conddblib.py
Go to the documentation of this file.
1 '''CMS Conditions DB Python library.
2 '''
3 
4 __author__ = 'Miguel Ojeda'
5 __copyright__ = 'Copyright 2013, CERN'
6 __credits__ = ['Giacomo Govi', 'Miguel Ojeda', 'Andreas Pfeiffer']
7 __license__ = 'Unknown'
8 __maintainer__ = 'Miguel Ojeda'
9 __email__ = 'mojedasa@cern.ch'
10 
11 
12 import os
13 import hashlib
14 import logging
15 
16 import sqlalchemy
17 import sqlalchemy.ext.declarative
18 
19 
20 logger = logging.getLogger(__name__)
21 
22 # Set initial level to WARN. This so that log statements don't occur in
23 # the absense of explicit logging being enabled.
24 if logger.level == logging.NOTSET:
25  logger.setLevel(logging.WARN)
26 
27 
28 class EnumMetaclass(type):
29  def __init__(cls, name, bases, dct):
30  cls._members = sorted([member for member in dir(cls) if not member.startswith('_')])
31  cls._map = dict([(member, getattr(cls, member)) for member in cls._members])
32  cls._reversemap = dict([(value, key) for (key, value) in cls._map.items()])
33  super(EnumMetaclass, cls).__init__(name, bases, dct)
34 
35  def __len__(cls):
36  return len(cls._members)
37 
38  def __getitem__(cls, key):
39  '''Returns the value for this key (if the key is an integer,
40  the value is the nth member from the sorted members list).
41  '''
42 
43  if isinstance(key, int):
44  # for tuple() and list()
45  key = cls._members[key]
46  return cls._map[key]
47 
48  def __call__(cls, value):
49  '''Returns the key for this value.
50  '''
51 
52  return cls._reversemap[value]
53 
54 
55 class Enum(object):
56  '''A la PEP 435, simplified.
57  '''
58 
59  __metaclass__ = EnumMetaclass
60 
61 
62 # Utility functions
63 def hash(data):
64  return hashlib.sha1(data).hexdigest()
65 
66 
67 # Constants
68 empty_label = '-'
69 
70 name_length = 100
71 description_length = 4000
72 hash_length = len(hash(''))
73 
74 web_experts_email = 'cms-cond-dev@cern.ch'
75 offline_db_experts_email = 'cms-offlinedb-exp@cern.ch'
76 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'
77 
78 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)
79 database_help = '''
80  The database parameter (--db) refers to the database where the tool
81  will connect to read all the data. By default, the production account
82  (through Frontier) will be used.
83 
84  In subcommands which take a source and a destination, --db always refers to
85  the source, and --destdb to the destination. For both of them the following
86  rules apply.
87 
88  The database parameter can be an official alias, a filename or any
89  valid SQLAlchemy URL.
90 
91  The official aliases are the following strings (first column):
92 
93  Alias Level Database RO/RW Notes
94  ------------ ----------- ------------- ---------- -------------------------------
95 
96  pro Production Frontier (ADG) read-only Default.
97  arc Archive Frontier read-only
98  int Integration Frontier read-only
99  dev Development Frontier read-only
100  boost Production Frontier read-only
101  boostprep Development Frontier read-only
102 
103  orapro Production Oracle (ADG) read-only Password required.
104  oraarc Archive Oracle read-only Password required.
105  oraint Integration Oracle read-write Password required.
106  oradev Development Oracle read-write Password required.
107  oraboost Production Oracle (ADG) read-write Password required.
108  oraboostprep Development Oracle read-write Password required.
109 
110  onlineorapro Production Oracle read-write Password required. Online only.
111  onlineoraint Online Int Oracle read-write Password required. Online only.
112 
113  Most of the time, if you are a regular user, you will want to read/copy
114  conditions from the Frontier production account. Therefore, you can omit
115  the --db parameter, unless you want to read from somewhere else,
116  e.g. from your local SQLite file.
117 
118  In addition, the parameter may be a filename (path) pointing to a local
119  SQLite file, e.g.
120 
121  file.db
122  relative/path/to/file.db
123  /absolute/path/to/file.db
124 
125  Finally, any valid SQLAlchemy URL can be used. This allows full
126  flexibility in cases where it may be needed, e.g.
127 
128  sqlite:// In-memory, volatile SQLite DB.
129  oracle://user@devdb11 Your private Oracle DB in devdb11 [*]
130 
131  [*] See https://account.cern.ch/ -> Services for more information
132  on personal Oracle accounts.
133 
134  For the official aliases, the password will be asked automatically
135  interactively. The same applies for Oracle URLs where the password
136  was not provided inside it, e.g.:
137 
138  oracle://user@devdb11 The tool will prompt you for the password.
139  oracle://user:pass@devdb11 Password inlined. [+]
140 
141  [+] Caution: Never write passwords in command-line parameters in
142  multi-user machines (e.g. lxplus), since other users can see them
143  in the process table (e.g. ps).
144 
145  This means that both the official aliases and the filenames are shortcuts
146  to the full SQLAlchemy URL equivalents, e.g. the following are the same:
147 
148  relative/path/to/file.db === sqlite:///relative/path/to/file.db
149  /absolute/path/to/file.db === sqlite:////absolute/path/to/file.db
150 '''
151 
153  any = 'any'
154  validation = 'validation'
155  mc = 'mc'
156  runmc = 'runmc'
157  hlt = 'hlt'
158  express = 'express'
159  prompt = 'prompt'
160  pcl = 'pcl'
161 
162 class TimeType(Enum):
163  run = 'Run'
164  time = 'Time'
165  lumi = 'Lumi'
166  hash = 'Hash'
167  user = 'User'
168 
169 
170 # Schema definition
171 _Base = sqlalchemy.ext.declarative.declarative_base()
172 
173 
174 class Tag(_Base):
175  __tablename__ = 'TAG'
176 
177  name = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
178  time_type = sqlalchemy.Column(sqlalchemy.Enum(*tuple(TimeType)), nullable=False)
179  object_type = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
180  synchronization = sqlalchemy.Column(sqlalchemy.Enum(*tuple(Synchronization)), nullable=False)
181  description = sqlalchemy.Column(sqlalchemy.String(description_length), nullable=False)
182  last_validated_time = sqlalchemy.Column(sqlalchemy.BIGINT, nullable=False)
183  end_of_validity = sqlalchemy.Column(sqlalchemy.BIGINT, nullable=False)
184  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
185  modification_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
186 
187  iovs = sqlalchemy.orm.relationship('IOV')
188 
189 
190 class IOV(_Base):
191  __tablename__ = 'IOV'
192 
193  tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('TAG.name'), primary_key=True)
194  since = sqlalchemy.Column(sqlalchemy.BIGINT, primary_key=True)
195  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, primary_key=True)
196  payload_hash = sqlalchemy.Column(sqlalchemy.ForeignKey('PAYLOAD.hash'), nullable=False)
197 
198  tag = sqlalchemy.orm.relationship('Tag')
199  payload = sqlalchemy.orm.relationship('Payload')
200 
201 
202 class Payload(_Base):
203  __tablename__ = 'PAYLOAD'
204 
205  hash = sqlalchemy.Column(sqlalchemy.CHAR(hash_length), primary_key=True)
206  object_type = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
207  data = sqlalchemy.Column(sqlalchemy.BLOB, nullable=False)
208  streamer_info = sqlalchemy.Column(sqlalchemy.BLOB, nullable=False)
209  version = sqlalchemy.Column(sqlalchemy.String(20), nullable=False)
210  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
211 
212 
214  __tablename__ = 'GLOBAL_TAG'
215 
216  name = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
217  validity = sqlalchemy.Column(sqlalchemy.BIGINT, nullable=False)
218  description = sqlalchemy.Column(sqlalchemy.String(description_length), nullable=False)
219  release = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
220  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
221  snapshot_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
222 
223 
225  __tablename__ = 'GLOBAL_TAG_MAP'
226 
227  global_tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('GLOBAL_TAG.name'), primary_key=True)
228  record = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
229  label = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
230  tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('TAG.name'), nullable=False)
231 
232  global_tag = sqlalchemy.orm.relationship('GlobalTag')
233  tag = sqlalchemy.orm.relationship('Tag')
234 
235 
236 # CondDB object
237 class Connection(object):
238 
239  def __init__(self, url, init=False):
240  # Workaround to avoid creating files if not present.
241  # Python's sqlite3 module does not use sqlite3_open_v2(),
242  # and therefore we cannot disable SQLITE_OPEN_CREATE.
243  # Only in the case of creating a new database we skip the check.
244  if url.drivername == 'sqlite':
245 
246  if not init and url.database is not None and not os.path.isfile(url.database):
247  # url.database is None if opening a in-memory DB, e.g. 'sqlite://'
248  raise Exception('SQLite database %s not found.' % url.database)
249 
250  self.engine = sqlalchemy.create_engine(url)
251 
252  enabled_foreign_keys = self.engine.execute('pragma foreign_keys').scalar()
253  supports_foreign_keys = enabled_foreign_keys is not None
254  if not supports_foreign_keys:
255  logger.warning('Your SQLite database does not support foreign keys, so constraints will not be checked. Please upgrade.')
256  elif not enabled_foreign_keys:
257  self.engine.execute('pragma foreign_keys = on')
258 
259  else:
260  self.engine = sqlalchemy.create_engine(url)
261 
262  self._session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker(bind=self.engine))
263 
264  self._is_frontier = url.drivername == 'oracle+frontier'
265  self._is_oracle = url.drivername == 'oracle'
266  self._is_sqlite = url.drivername == 'sqlite'
267 
268  self._is_read_only = self._is_frontier or url.host in {
269  'cms_orcon_adg',
270  'cmsarc_lb',
271  }
272 
273  self._is_official = self._is_frontier or url.host in {
274  'cms_orcon_adg',
275  'cmsarc_lb',
276  'cms_orcoff_int',
277  'cms_orcoff_prep',
278  'cms_orcon_prod',
279  'cmsintr_lb',
280  }
281 
282  def session(self):
283  return self._session()
284 
285  @property
286  def metadata(self):
287  return _Base.metadata
288 
289  @property
290  def is_frontier(self):
291  return self._is_frontier
292 
293  @property
294  def is_oracle(self):
295  return self._is_oracle
296 
297  @property
298  def is_sqlite(self):
299  return self._is_sqlite
300 
301  @property
302  def is_read_only(self):
303  return self._is_read_only
304 
305  @property
306  def is_official(self):
307  return self._is_official
308 
309  def is_valid(self):
310  '''Tests whether the current DB looks like a valid CMS Conditions one.
311  '''
312  engine_connection = self.engine.connect()
313  #ret = all([self.engine.dialect.has_table(engine_connection, table.__tablename__) for table in [Tag, IOV, Payload, GlobalTag, GlobalTagMap]])
314  # temporarely avoid the check on the GT tables - there are releases in use where C++ does not create these tables.
315  ret = all([self.engine.dialect.has_table(engine_connection, table.__tablename__) for table in [Tag, IOV, Payload]])
316  engine_connection.close()
317  return ret
318 
319  def init(self, drop=False):
320  '''Initializes a database.
321  '''
322 
323  if drop:
324  logger.debug('Dropping tables...')
325  self.metadata.drop_all(self.engine)
326  else:
327  if self.is_valid():
328  raise Exception('Looks like the database is already a valid CMS Conditions one. Please use drop=True if you really want to scratch it.')
329 
330  logger.debug('Creating tables...')
331  self.metadata.create_all(self.engine)
332 
333  # TODO: Create indexes
334  #logger.debug('Creating indexes...')
335 
336 
337 # Connection helpers
339  import subprocess
340  return subprocess.Popen(['cmsGetFnConnect', 'frontier://%s' % database], stdout = subprocess.PIPE).communicate()[0].strip()
341 
342 
343 def _getCMSFrontierSQLAlchemyConnectionString(database, schema = 'cms_conditions'):
344  import urllib
345  return 'oracle+frontier://@%s/%s' % (urllib.quote_plus(_getCMSFrontierConnectionString(database)), schema)
346 
347 
348 def _getCMSOracleSQLAlchemyConnectionString(database, schema = 'cms_conditions'):
349  return 'oracle://%s@%s' % (schema, database)
350 
351 
352 # Entry point
353 
354 def make_url(database='pro'):
355 
356  schema = 'cms_conditions' # set the default
357  if ':' in database and '://' not in database: # check if we really got a shortcut like "pro:<schema>" (and not a url like proto://...), if so, disentangle
358  database, schema = database.split(':')
359  logging.debug(' ... using db "%s", schema "%s"' % (database, schema) )
360 
361  # Lazy in order to avoid calls to cmsGetFnConnect
362  mapping = {
363  'pro': lambda: _getCMSFrontierSQLAlchemyConnectionString('PromptProd', schema),
364  'arc': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierArc', schema),
365  'int': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierInt', schema),
366  'dev': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierPrep', schema),
367 
368  'orapro': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcon_adg', schema),
369  'oraarc': lambda: _getCMSOracleSQLAlchemyConnectionString('cmsarc_lb', schema),
370  'oraint': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_int', schema),
371  'oradev': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_prep', schema),
372 
373  'onlineorapro': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcon_prod', schema),
374  'onlineoraint': lambda: _getCMSOracleSQLAlchemyConnectionString('cmsintr_lb', schema),
375  }
376 
377  if database in mapping:
378  database = mapping[database]()
379 
380  logging.debug('connection string set to "%s"' % database)
381 
382  try:
383  url = sqlalchemy.engine.url.make_url(database)
384  except sqlalchemy.exc.ArgumentError:
385  url = sqlalchemy.engine.url.make_url('sqlite:///%s' % database)
386  return url
387 
388 def connect(url, init=False, verbose=0):
389  '''Returns a Connection instance to the CMS Condition DB.
390 
391  See database_help for the description of the database parameter.
392 
393  The verbosity level is as follows:
394 
395  0 = No output (default).
396  1 = SQL statements issued, including their parameters.
397  2 = In addition, results of the queries (all rows and the column headers).
398  '''
399 
400  if url.drivername == 'oracle' and url.password is None:
401  import getpass
402  url.password = getpass.getpass('Password for %s: ' % str(url))
403 
404  if verbose >= 1:
405  logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
406 
407  if verbose >= 2:
408  logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
409 
410  return Connection(url, init=init)
411 
412 
413 def _exists(session, primary_key, value):
414  ret = None
415  try:
416  ret = session.query(primary_key).\
417  filter(primary_key == value).\
418  count() != 0
419  except sqlalchemy.exc.OperationalError:
420  pass
421 
422  return ret
423 
424 def _inserted_before(timestamp):
425  '''To be used inside filter().
426  '''
427 
428  if timestamp is None:
429  # XXX: Returning None does not get optimized (skipped) by SQLAlchemy,
430  # and returning True does not work in Oracle (generates "and 1"
431  # which breaks Oracle but not SQLite). For the moment just use
432  # this dummy condition.
433  return sqlalchemy.literal(True) == sqlalchemy.literal(True)
434 
435  return conddb.IOV.insertion_time <= _parse_timestamp(timestamp)
436 
437 def listObject(session, name, snapshot=None):
438 
439  is_tag = _exists(session, Tag.name, name)
440  result = {}
441  if is_tag:
442  result['type'] = 'Tag'
443  result['name'] = session.query(Tag).get(name).name
444  result['timeType'] = session.query(Tag.time_type).\
445  filter(Tag.name == name).\
446  scalar()
447 
448  result['iovs'] = session.query(IOV.since, IOV.insertion_time, IOV.payload_hash, Payload.object_type).\
449  join(IOV.payload).\
450  filter(
451  IOV.tag_name == name,
452  _inserted_before(snapshot),
453  ).\
454  order_by(IOV.since.desc(), IOV.insertion_time.desc()).\
455  from_self().\
456  order_by(IOV.since, IOV.insertion_time).\
457  all()
458 
459  try:
460  is_global_tag = _exists(session, GlobalTag.name, name)
461  if is_global_tag:
462  result['type'] = 'GlobalTag'
463  result['name'] = session.query(GlobalTag).get(name)
464  result['tags'] = session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
465  filter(GlobalTagMap.global_tag_name == name).\
466  order_by(GlobalTagMap.record, GlobalTagMap.label).\
467  all()
468  except sqlalchemy.exc.OperationalError:
469  sys.stderr.write("No table for GlobalTags found in DB.\n\n")
470 
471  if not is_tag and not is_global_tag:
472  raise Exception('There is no tag or global tag named %s in the database.' % name)
473 
474  return result
475 
476 def getPayload(session, hash):
477  # get payload from DB:
478  data, payloadType = session.query(Payload.data, Payload.object_type).filter(Payload.hash == hash).one()
479  return data
def _exists
Definition: conddblib.py:413
tuple _Base
Definition: conddblib.py:171
static void * communicate(void *obj)
Definition: DQMNet.cc:1246
def _getCMSFrontierConnectionString
Definition: conddblib.py:338
def _inserted_before
Definition: conddblib.py:424
def listObject
Definition: conddblib.py:437
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def make_url
Definition: conddblib.py:354
def _getCMSOracleSQLAlchemyConnectionString
Definition: conddblib.py:348
def connect
Definition: conddblib.py:388
dbl *** dir
Definition: mlp_gen.cc:35
def hash
Definition: conddblib.py:63
T get(const Candidate &c)
Definition: component.h:55
def _getCMSFrontierSQLAlchemyConnectionString
Definition: conddblib.py:343
double scalar(const CLHEP::HepGenMatrix &m)
Return the matrix as a scalar. Raise an assertion if the matris is not .
Definition: matutil.cc:183
def getPayload
Definition: conddblib.py:476