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  offline = 'offline'
162 
163 class TimeType(Enum):
164  run = 'Run'
165  time = 'Time'
166  lumi = 'Lumi'
167  hash = 'Hash'
168  user = 'User'
169 
170 
171 # Schema definition
172 _Base = sqlalchemy.ext.declarative.declarative_base()
173 
174 
175 class Tag(_Base):
176  __tablename__ = 'TAG'
177 
178  name = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
179  time_type = sqlalchemy.Column(sqlalchemy.Enum(*tuple(TimeType)), nullable=False)
180  object_type = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
181  synchronization = sqlalchemy.Column(sqlalchemy.Enum(*tuple(Synchronization)), nullable=False)
182  description = sqlalchemy.Column(sqlalchemy.String(description_length), nullable=False)
183  last_validated_time = sqlalchemy.Column(sqlalchemy.BIGINT, nullable=False)
184  end_of_validity = sqlalchemy.Column(sqlalchemy.BIGINT, nullable=False)
185  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
186  modification_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
187 
188  iovs = sqlalchemy.orm.relationship('IOV')
189 
190 
191 class IOV(_Base):
192  __tablename__ = 'IOV'
193 
194  tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('TAG.name'), primary_key=True)
195  since = sqlalchemy.Column(sqlalchemy.BIGINT, primary_key=True)
196  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, primary_key=True)
197  payload_hash = sqlalchemy.Column(sqlalchemy.ForeignKey('PAYLOAD.hash'), nullable=False)
198 
199  tag = sqlalchemy.orm.relationship('Tag')
200  payload = sqlalchemy.orm.relationship('Payload')
201 
202 
203 class Payload(_Base):
204  __tablename__ = 'PAYLOAD'
205 
206  hash = sqlalchemy.Column(sqlalchemy.CHAR(hash_length), primary_key=True)
207  object_type = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
208  data = sqlalchemy.Column(sqlalchemy.BLOB, nullable=False)
209  streamer_info = sqlalchemy.Column(sqlalchemy.BLOB, nullable=False)
210  version = sqlalchemy.Column(sqlalchemy.String(20), nullable=False)
211  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
212 
213 
215  __tablename__ = 'GLOBAL_TAG'
216 
217  name = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
218  validity = sqlalchemy.Column(sqlalchemy.BIGINT, nullable=False)
219  description = sqlalchemy.Column(sqlalchemy.String(description_length), nullable=False)
220  release = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
221  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
222  snapshot_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
223 
224 
226  __tablename__ = 'GLOBAL_TAG_MAP'
227 
228  global_tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('GLOBAL_TAG.name'), primary_key=True)
229  record = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
230  label = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
231  tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('TAG.name'), nullable=False)
232 
233  global_tag = sqlalchemy.orm.relationship('GlobalTag')
234  tag = sqlalchemy.orm.relationship('Tag')
235 
236 
237 # CondDB object
238 class Connection(object):
239 
240  def __init__(self, url, init=False):
241  # Workaround to avoid creating files if not present.
242  # Python's sqlite3 module does not use sqlite3_open_v2(),
243  # and therefore we cannot disable SQLITE_OPEN_CREATE.
244  # Only in the case of creating a new database we skip the check.
245  if url.drivername == 'sqlite':
246 
247  if not init and url.database is not None and not os.path.isfile(url.database):
248  # url.database is None if opening a in-memory DB, e.g. 'sqlite://'
249  raise Exception('SQLite database %s not found.' % url.database)
250 
251  self.engine = sqlalchemy.create_engine(url)
252 
253  enabled_foreign_keys = self.engine.execute('pragma foreign_keys').scalar()
254  supports_foreign_keys = enabled_foreign_keys is not None
255  if not supports_foreign_keys:
256  logger.warning('Your SQLite database does not support foreign keys, so constraints will not be checked. Please upgrade.')
257  elif not enabled_foreign_keys:
258  self.engine.execute('pragma foreign_keys = on')
259 
260  else:
261  self.engine = sqlalchemy.create_engine(url)
262 
263  self._session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker(bind=self.engine))
264 
265  self._is_frontier = url.drivername == 'oracle+frontier'
266  self._is_oracle = url.drivername == 'oracle'
267  self._is_sqlite = url.drivername == 'sqlite'
268 
269  self._is_read_only = self._is_frontier or url.host in {
270  'cms_orcon_adg',
271  'cmsarc_lb',
272  }
273 
274  self._is_official = self._is_frontier or url.host in {
275  'cms_orcon_adg',
276  'cmsarc_lb',
277  'cms_orcoff_int',
278  'cms_orcoff_prep',
279  'cms_orcon_prod',
280  'cmsintr_lb',
281  }
282 
283  def session(self):
284  return self._session()
285 
286  @property
287  def metadata(self):
288  return _Base.metadata
289 
290  @property
291  def is_frontier(self):
292  return self._is_frontier
293 
294  @property
295  def is_oracle(self):
296  return self._is_oracle
297 
298  @property
299  def is_sqlite(self):
300  return self._is_sqlite
301 
302  @property
303  def is_read_only(self):
304  return self._is_read_only
305 
306  @property
307  def is_official(self):
308  return self._is_official
309 
310  def is_valid(self):
311  '''Tests whether the current DB looks like a valid CMS Conditions one.
312  '''
313  engine_connection = self.engine.connect()
314  #ret = all([self.engine.dialect.has_table(engine_connection, table.__tablename__) for table in [Tag, IOV, Payload, GlobalTag, GlobalTagMap]])
315  # temporarely avoid the check on the GT tables - there are releases in use where C++ does not create these tables.
316  ret = all([self.engine.dialect.has_table(engine_connection, table.__tablename__) for table in [Tag, IOV, Payload]])
317  engine_connection.close()
318  return ret
319 
320  def init(self, drop=False):
321  '''Initializes a database.
322  '''
323 
324  if drop:
325  logger.debug('Dropping tables...')
326  self.metadata.drop_all(self.engine)
327  else:
328  if self.is_valid():
329  raise Exception('Looks like the database is already a valid CMS Conditions one. Please use drop=True if you really want to scratch it.')
330 
331  logger.debug('Creating tables...')
332  self.metadata.create_all(self.engine)
333 
334  # TODO: Create indexes
335  #logger.debug('Creating indexes...')
336 
337 
338 # Connection helpers
340  import subprocess
341  return subprocess.Popen(['cmsGetFnConnect', 'frontier://%s' % database], stdout = subprocess.PIPE).communicate()[0].strip()
342 
343 
344 def _getCMSFrontierSQLAlchemyConnectionString(database, schema = 'cms_conditions'):
345  import urllib
346  return 'oracle+frontier://@%s/%s' % (urllib.quote_plus(_getCMSFrontierConnectionString(database)), schema)
347 
348 
349 def _getCMSOracleSQLAlchemyConnectionString(database, schema = 'cms_conditions'):
350  return 'oracle://%s@%s' % (schema, database)
351 
352 
353 # Entry point
354 
355 def make_url(database='pro'):
356 
357  schema = 'cms_conditions' # set the default
358  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
359  database, schema = database.split(':')
360  logging.debug(' ... using db "%s", schema "%s"' % (database, schema) )
361 
362  # Lazy in order to avoid calls to cmsGetFnConnect
363  mapping = {
364  'pro': lambda: _getCMSFrontierSQLAlchemyConnectionString('PromptProd', schema),
365  'arc': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierArc', schema),
366  'int': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierInt', schema),
367  'dev': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierPrep', schema),
368 
369  'orapro': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcon_adg', schema),
370  'oraarc': lambda: _getCMSOracleSQLAlchemyConnectionString('cmsarc_lb', schema),
371  'oraint': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_int', schema),
372  'oradev': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_prep', schema),
373 
374  'onlineorapro': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcon_prod', schema),
375  'onlineoraint': lambda: _getCMSOracleSQLAlchemyConnectionString('cmsintr_lb', schema),
376  }
377 
378  if database in mapping:
379  database = mapping[database]()
380 
381  logging.debug('connection string set to "%s"' % database)
382 
383  try:
384  url = sqlalchemy.engine.url.make_url(database)
385  except sqlalchemy.exc.ArgumentError:
386  url = sqlalchemy.engine.url.make_url('sqlite:///%s' % database)
387  return url
388 
389 def connect(url, init=False, verbose=0):
390  '''Returns a Connection instance to the CMS Condition DB.
391 
392  See database_help for the description of the database parameter.
393 
394  The verbosity level is as follows:
395 
396  0 = No output (default).
397  1 = SQL statements issued, including their parameters.
398  2 = In addition, results of the queries (all rows and the column headers).
399  '''
400 
401  if url.drivername == 'oracle' and url.password is None:
402  import getpass
403  url.password = getpass.getpass('Password for %s: ' % str(url))
404 
405  if verbose >= 1:
406  logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
407 
408  if verbose >= 2:
409  logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
410 
411  return Connection(url, init=init)
412 
413 
414 def _exists(session, primary_key, value):
415  ret = None
416  try:
417  ret = session.query(primary_key).\
418  filter(primary_key == value).\
419  count() != 0
420  except sqlalchemy.exc.OperationalError:
421  pass
422 
423  return ret
424 
425 def _inserted_before(timestamp):
426  '''To be used inside filter().
427  '''
428 
429  if timestamp is None:
430  # XXX: Returning None does not get optimized (skipped) by SQLAlchemy,
431  # and returning True does not work in Oracle (generates "and 1"
432  # which breaks Oracle but not SQLite). For the moment just use
433  # this dummy condition.
434  return sqlalchemy.literal(True) == sqlalchemy.literal(True)
435 
436  return conddb.IOV.insertion_time <= _parse_timestamp(timestamp)
437 
438 def listObject(session, name, snapshot=None):
439 
440  is_tag = _exists(session, Tag.name, name)
441  result = {}
442  if is_tag:
443  result['type'] = 'Tag'
444  result['name'] = session.query(Tag).get(name).name
445  result['timeType'] = session.query(Tag.time_type).\
446  filter(Tag.name == name).\
447  scalar()
448 
449  result['iovs'] = session.query(IOV.since, IOV.insertion_time, IOV.payload_hash, Payload.object_type).\
450  join(IOV.payload).\
451  filter(
452  IOV.tag_name == name,
453  _inserted_before(snapshot),
454  ).\
455  order_by(IOV.since.desc(), IOV.insertion_time.desc()).\
456  from_self().\
457  order_by(IOV.since, IOV.insertion_time).\
458  all()
459 
460  try:
461  is_global_tag = _exists(session, GlobalTag.name, name)
462  if is_global_tag:
463  result['type'] = 'GlobalTag'
464  result['name'] = session.query(GlobalTag).get(name)
465  result['tags'] = session.query(GlobalTagMap.record, GlobalTagMap.label, GlobalTagMap.tag_name).\
466  filter(GlobalTagMap.global_tag_name == name).\
467  order_by(GlobalTagMap.record, GlobalTagMap.label).\
468  all()
469  except sqlalchemy.exc.OperationalError:
470  sys.stderr.write("No table for GlobalTags found in DB.\n\n")
471 
472  if not is_tag and not is_global_tag:
473  raise Exception('There is no tag or global tag named %s in the database.' % name)
474 
475  return result
476 
477 def getPayload(session, hash):
478  # get payload from DB:
479  data, payloadType = session.query(Payload.data, Payload.object_type).filter(Payload.hash == hash).one()
480  return data
def _exists
Definition: conddblib.py:414
tuple _Base
Definition: conddblib.py:172
static void * communicate(void *obj)
Definition: DQMNet.cc:1246
def _getCMSFrontierConnectionString
Definition: conddblib.py:339
def _inserted_before
Definition: conddblib.py:425
def listObject
Definition: conddblib.py:438
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def make_url
Definition: conddblib.py:355
def _getCMSOracleSQLAlchemyConnectionString
Definition: conddblib.py:349
def connect
Definition: conddblib.py:389
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:344
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:477