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