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 Development Frontier read-only
101 
102  orapro Production Oracle (ADG) read-only Password required.
103  oraarc Archive Oracle read-only Password required.
104  oraint Integration Oracle read-write Password required.
105  oradev Development Oracle read-write Password required.
106  oraboost Development Oracle read-write Password required.
107 
108  onlineorapro Production Oracle read-write Password required. Online only.
109  onlineoraint Online Int Oracle read-write Password required. Online only.
110 
111  Most of the time, if you are a regular user, you will want to read/copy
112  conditions from the Frontier production account. Therefore, you can omit
113  the --db parameter, unless you want to read from somewhere else,
114  e.g. from your local SQLite file.
115 
116  In addition, the parameter may be a filename (path) pointing to a local
117  SQLite file, e.g.
118 
119  file.db
120  relative/path/to/file.db
121  /absolute/path/to/file.db
122 
123  Finally, any valid SQLAlchemy URL can be used. This allows full
124  flexibility in cases where it may be needed, e.g.
125 
126  sqlite:// In-memory, volatile SQLite DB.
127  oracle://user@devdb11 Your private Oracle DB in devdb11 [*]
128 
129  [*] See https://account.cern.ch/ -> Services for more information
130  on personal Oracle accounts.
131 
132  For the official aliases, the password will be asked automatically
133  interactively. The same applies for Oracle URLs where the password
134  was not provided inside it, e.g.:
135 
136  oracle://user@devdb11 The tool will prompt you for the password.
137  oracle://user:pass@devdb11 Password inlined. [+]
138 
139  [+] Caution: Never write passwords in command-line parameters in
140  multi-user machines (e.g. lxplus), since other users can see them
141  in the process table (e.g. ps).
142 
143  This means that both the official aliases and the filenames are shortcuts
144  to the full SQLAlchemy URL equivalents, e.g. the following are the same:
145 
146  relative/path/to/file.db === sqlite:///relative/path/to/file.db
147  /absolute/path/to/file.db === sqlite:////absolute/path/to/file.db
148 '''
149 
150 
152  offline = 'Offline'
153  hlt = 'HLT'
154  prompt = 'Prompt'
155 
156 
157 class TimeType(Enum):
158  run = 'Run'
159  time = 'Time'
160  lumi = 'Lumi'
161  hash = 'Hash'
162  user = 'User'
163 
164 
165 # Schema definition
166 _Base = sqlalchemy.ext.declarative.declarative_base()
167 
168 
169 class Tag(_Base):
170  __tablename__ = 'tag'
171 
172  name = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
173  time_type = sqlalchemy.Column(sqlalchemy.Enum(*tuple(TimeType)), nullable=False)
174  object_type = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
175  synchronization = sqlalchemy.Column(sqlalchemy.Enum(*tuple(Synchronization)), nullable=False)
176  description = sqlalchemy.Column(sqlalchemy.String(description_length), nullable=False)
177  last_validated_time = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
178  end_of_validity = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
179  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
180  modification_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
181 
182  iovs = sqlalchemy.orm.relationship('IOV')
183 
184 
185 class IOV(_Base):
186  __tablename__ = 'iov'
187 
188  tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('tag.name'), primary_key=True)
189  since = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
190  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, primary_key=True)
191  payload_hash = sqlalchemy.Column(sqlalchemy.ForeignKey('payload.hash'), nullable=False)
192 
193  tag = sqlalchemy.orm.relationship('Tag')
194  payload = sqlalchemy.orm.relationship('Payload')
195 
196 
197 class Payload(_Base):
198  __tablename__ = 'payload'
199 
200  hash = sqlalchemy.Column(sqlalchemy.CHAR(hash_length), primary_key=True)
201  object_type = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
202  data = sqlalchemy.Column(sqlalchemy.BLOB, nullable=False)
203  streamer_info = sqlalchemy.Column(sqlalchemy.BLOB, nullable=False)
204  version = sqlalchemy.Column(sqlalchemy.String(20), nullable=False)
205  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
206 
207 
209  __tablename__ = 'global_tag'
210 
211  name = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
212  validity = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
213  description = sqlalchemy.Column(sqlalchemy.String(description_length), nullable=False)
214  release = sqlalchemy.Column(sqlalchemy.String(name_length), nullable=False)
215  insertion_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
216  snapshot_time = sqlalchemy.Column(sqlalchemy.TIMESTAMP, nullable=False)
217 
218 
220  __tablename__ = 'global_tag_map'
221 
222  global_tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('global_tag.name'), primary_key=True)
223  record = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
224  label = sqlalchemy.Column(sqlalchemy.String(name_length), primary_key=True)
225  tag_name = sqlalchemy.Column(sqlalchemy.ForeignKey('tag.name'), nullable=False)
226 
227  global_tag = sqlalchemy.orm.relationship('GlobalTag')
228  tag = sqlalchemy.orm.relationship('Tag')
229 
230 
231 # CondDB object
233 
234  def __init__(self, url, init=False):
235  # Workaround to avoid creating files if not present.
236  # Python's sqlite3 module does not use sqlite3_open_v2(),
237  # and therefore we cannot disable SQLITE_OPEN_CREATE.
238  # Only in the case of creating a new database we skip the check.
239  if url.drivername == 'sqlite':
240 
241  if not init and url.database is not None and not os.path.isfile(url.database):
242  # url.database is None if opening a in-memory DB, e.g. 'sqlite://'
243  raise Exception('SQLite database %s not found.' % url.database)
244 
245  self.engine = sqlalchemy.create_engine(url)
246 
247  enabled_foreign_keys = self.engine.execute('pragma foreign_keys').scalar()
248  supports_foreign_keys = enabled_foreign_keys is not None
249  if not supports_foreign_keys:
250  logger.warning('Your SQLite database does not support foreign keys, so constraints will not be checked. Please upgrade.')
251  elif not enabled_foreign_keys:
252  self.engine.execute('pragma foreign_keys = on')
253 
254  else:
255  self.engine = sqlalchemy.create_engine(url)
256 
257  self._session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker(bind=self.engine))
258 
259  self._is_frontier = url.drivername == 'oracle+frontier'
260  self._is_oracle = url.drivername == 'oracle'
261  self._is_sqlite = url.drivername == 'sqlite'
262 
263  self._is_read_only = self._is_frontier or url.host in {
264  'cms_orcon_adg',
265  'cmsarc_lb',
266  }
267 
268  self._is_official = self._is_frontier or url.host in {
269  'cms_orcon_adg',
270  'cmsarc_lb',
271  'cms_orcoff_int',
272  'cms_orcoff_prep',
273  'cms_orcon_prod',
274  'cmsintr_lb',
275  }
276 
277  def session(self):
278  return self._session()
279 
280  @property
281  def metadata(self):
282  return _Base.metadata
283 
284  @property
285  def is_frontier(self):
286  return self._is_frontier
287 
288  @property
289  def is_oracle(self):
290  return self._is_oracle
291 
292  @property
293  def is_sqlite(self):
294  return self._is_sqlite
295 
296  @property
297  def is_read_only(self):
298  return self._is_read_only
299 
300  @property
301  def is_official(self):
302  return self._is_official
303 
304  def is_valid(self):
305  '''Tests whether the current DB looks like a valid CMS Conditions one.
306  '''
307 
308  engine_connection = self.engine.connect()
309  ret = all([self.engine.dialect.has_table(engine_connection, table.__tablename__) for table in [Tag, IOV, Payload, GlobalTag, GlobalTagMap]])
310  engine_connection.close()
311  return ret
312 
313  def init(self, drop=False):
314  '''Initializes a database.
315  '''
316 
317  if drop:
318  logger.debug('Dropping tables...')
319  self.metadata.drop_all(self.engine)
320  else:
321  if self.is_valid():
322  raise Exception('Looks like the database is already a valid CMS Conditions one. Please use drop=True if you really want to scratch it.')
323 
324  logger.debug('Creating tables...')
325  self.metadata.create_all(self.engine)
326 
327  # TODO: Create indexes
328  #logger.debug('Creating indexes...')
329 
330 
331 # Connection helpers
333  import subprocess
334  return subprocess.Popen(['cmsGetFnConnect', 'frontier://%s' % database], stdout = subprocess.PIPE).communicate()[0].strip()
335 
336 
337 def _getCMSFrontierSQLAlchemyConnectionString(database, schema = 'cms_conditions'):
338  import urllib
339  return 'oracle+frontier://@%s/%s' % (urllib.quote_plus(_getCMSFrontierConnectionString(database)), schema)
340 
341 
342 def _getCMSOracleSQLAlchemyConnectionString(database, schema = 'cms_conditions'):
343  return 'oracle://%s@%s' % (schema, database)
344 
345 
346 # Entry point
347 def connect(database='pro', init=False, verbose=0):
348  '''Returns a Connection instance to the CMS Condition DB.
349 
350  See database_help for the description of the database parameter.
351 
352  The verbosity level is as follows:
353 
354  0 = No output (default).
355  1 = SQL statements issued, including their parameters.
356  2 = In addition, results of the queries (all rows and the column headers).
357  '''
358 
359  # Lazy in order to avoid calls to cmsGetFnConnect
360  mapping = {
361  'pro': lambda: _getCMSFrontierSQLAlchemyConnectionString('PromptProd'),
362  'arc': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierArc'),
363  'int': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierInt'),
364  'dev': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierPrep'),
365  'boost': lambda: _getCMSFrontierSQLAlchemyConnectionString('FrontierPrep', 'cms_test_conditions'),
366 
367  'orapro': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcon_adg'),
368  'oraarc': lambda: _getCMSOracleSQLAlchemyConnectionString('cmsarc_lb'),
369  'oraint': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_int'),
370  'oradev': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_prep'),
371  'oraboost': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcoff_prep', 'cms_test_conditions'),
372 
373  'onlineorapro': lambda: _getCMSOracleSQLAlchemyConnectionString('cms_orcon_prod'),
374  'onlineoraint': lambda: _getCMSOracleSQLAlchemyConnectionString('cmsintr_lb'),
375  }
376 
377  if database in mapping:
378  database = mapping[database]()
379 
380  try:
381  url = sqlalchemy.engine.url.make_url(database)
382  if url.drivername == 'oracle' and url.password is None:
383  import getpass
384  url.password = getpass.getpass('Password for %s: ' % str(url))
385  except sqlalchemy.exc.ArgumentError:
386  url = sqlalchemy.engine.url.make_url('sqlite:///%s' % database)
387 
388  if verbose >= 1:
389  logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
390 
391  if verbose >= 2:
392  logging.getLogger('sqlalchemy.engine').setLevel(logging.DEBUG)
393 
394  return Connection(url, init=init)
395 
tuple _Base
Definition: conddblib.py:166
static void * communicate(void *obj)
Definition: DQMNet.cc:1246
def _getCMSFrontierConnectionString
Definition: conddblib.py:332
list object
Definition: dbtoconf.py:77
def _getCMSOracleSQLAlchemyConnectionString
Definition: conddblib.py:342
def connect
Definition: conddblib.py:347
dbl *** dir
Definition: mlp_gen.cc:35
def hash
Definition: conddblib.py:63
def _getCMSFrontierSQLAlchemyConnectionString
Definition: conddblib.py:337
double scalar(const CLHEP::HepGenMatrix &m)
Return the matrix as a scalar. Raise an assertion if the matris is not .
Definition: matutil.cc:183