CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
upload_popcon.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 '''Script that uploads to the new CMS conditions uploader.
3 Adapted to the new infrastructure from v6 of the upload.py script for the DropBox from Miguel Ojeda.
4 '''
5 
6 __author__ = 'Andreas Pfeiffer'
7 __copyright__ = 'Copyright 2015, CERN CMS'
8 __credits__ = ['Giacomo Govi', 'Salvatore Di Guida', 'Miguel Ojeda', 'Andreas Pfeiffer']
9 __license__ = 'Unknown'
10 __maintainer__ = 'Andreas Pfeiffer'
11 __email__ = 'andreas.pfeiffer@cern.ch'
12 
13 
14 import os
15 import sys
16 import optparse
17 import hashlib
18 import tarfile
19 import netrc
20 import getpass
21 import errno
22 import sqlite3
23 import json
24 import tempfile
25 
26 defaultBackend = 'online'
27 defaultHostname = 'cms-conddb-prod.cern.ch'
28 defaultDevHostname = 'cms-conddb-dev.cern.ch'
29 defaultUrlTemplate = 'https://%s/cmsDbUpload/'
30 defaultTemporaryFile = 'upload.tar.bz2'
31 defaultNetrcHost = 'ConditionUploader'
32 defaultWorkflow = 'offline'
33 
34 # common/http.py start (plus the "# Try to extract..." section bit)
35 import time
36 import logging
37 import cStringIO
38 
39 import pycurl
40 import copy
41 
42 
44  '''A common HTTP exception.
45 
46  self.code is the response HTTP code as an integer.
47  self.response is the response body (i.e. page).
48  '''
49 
50  def __init__(self, code, response):
51  self.code = code
52  self.response = response
53 
54  # Try to extract the error message if possible (i.e. known error page format)
55  try:
56  self.args = (response.split('<p>')[1].split('</p>')[0], )
57  except Exception:
58  self.args = (self.response, )
59 
60 
61 CERN_SSO_CURL_CAPATH = '/etc/pki/tls/certs'
62 
63 class HTTP(object):
64  '''Class used for querying URLs using the HTTP protocol.
65  '''
66 
67  retryCodes = frozenset([502, 503])
68 
69  def __init__(self):
70  self.setBaseUrl()
71  self.setRetries()
72 
73  self.curl = pycurl.Curl()
74  self.curl.setopt(self.curl.COOKIEFILE, '') # in memory
75 
76  #-toDo: make sure we have the right options set here to use ssl
77  #-review(2015-09-25): check and see - action: AP
78  # self.curl.setopt(self.curl.SSL_VERIFYPEER, 1)
79  self.curl.setopt(self.curl.SSL_VERIFYPEER, 0)
80  self.curl.setopt(self.curl.SSL_VERIFYHOST, 2)
81 
82  self.baseUrl = None
83 
84  self.token = None
85 
86  def getCookies(self):
87  '''Returns the list of cookies.
88  '''
89  return self.curl.getinfo(self.curl.INFO_COOKIELIST)
90 
91  def discardCookies(self):
92  '''Discards cookies.
93  '''
94  self.curl.setopt(self.curl.COOKIELIST, 'ALL')
95 
96 
97  def setBaseUrl(self, baseUrl = ''):
98  '''Allows to set a base URL which will be prefixed to all the URLs
99  that will be queried later.
100  '''
101  self.baseUrl = baseUrl
102 
103 
104  def setProxy(self, proxy = ''):
105  '''Allows to set a proxy.
106  '''
107  self.curl.setopt(self.curl.PROXY, proxy)
108 
109 
110  def setTimeout(self, timeout = 0):
111  '''Allows to set a timeout.
112  '''
113  self.curl.setopt(self.curl.TIMEOUT, timeout)
114 
115 
116  def setRetries(self, retries = ()):
117  '''Allows to set retries.
118 
119  The retries are a sequence of the seconds to wait per retry.
120 
121  The retries are done on:
122  * PyCurl errors (includes network problems, e.g. not being able
123  to connect to the host).
124  * 502 Bad Gateway (for the moment, to avoid temporary
125  Apache-CherryPy issues).
126  * 503 Service Temporarily Unavailable (for when we update
127  the frontends).
128  '''
129  self.retries = retries
130 
131  def getToken(self, username, password):
132 
133  url = self.baseUrl + 'token'
134 
135  self.curl.setopt(pycurl.URL, url)
136  self.curl.setopt(pycurl.VERBOSE, 0)
137 
138  #-toDo: check if/why these are needed ...
139  #-ap: hmm ...
140  # self.curl.setopt(pycurl.DNS_CACHE_TIMEOUT, 0)
141  # self.curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)
142  #-end hmmm ...
143  #-review(2015-09-25): check and see - action: AP
144 
145 
146  self.curl.setopt(pycurl.HTTPHEADER, ['Accept: application/json'])
147  # self.curl.setopt( self.curl.POST, {})
148  self.curl.setopt(self.curl.HTTPGET, 0)
149 
150  response = cStringIO.StringIO()
151  self.curl.setopt(pycurl.WRITEFUNCTION, response.write)
152  self.curl.setopt(pycurl.USERPWD, '%s:%s' % (username, password) )
153 
154  logging.debug('going to connect to server at: %s' % url )
155 
156  self.curl.perform()
157  code = self.curl.getinfo(pycurl.RESPONSE_CODE)
158  logging.debug('got: %s ', str(code))
159 
160  try:
161  self.token = json.loads( response.getvalue() )['token']
162  except Exception as e:
163  logging.error('http::getToken> got error from server: %s ', str(e) )
164  if 'No JSON object could be decoded' in str(e):
165  return None
166  logging.error("error getting token: %s", str(e))
167  return None
168 
169  logging.debug('token: %s', self.token)
170  logging.debug('returning: %s', response.getvalue())
171 
172  return response.getvalue()
173 
174  def query(self, url, data = None, files = None, keepCookies = True):
175  '''Queries a URL, optionally with some data (dictionary).
176 
177  If no data is specified, a GET request will be used.
178  If some data is specified, a POST request will be used.
179 
180  If files is specified, it must be a dictionary like data but
181  the values are filenames.
182 
183  By default, cookies are kept in-between requests.
184 
185  A HTTPError exception is raised if the response's HTTP code is not 200.
186  '''
187 
188  if not keepCookies:
189  self.discardCookies()
190 
191  url = self.baseUrl + url
192 
193  # make sure the logs are safe ... at least somewhat :)
194  data4log = copy.copy(data)
195  if data4log:
196  if 'password' in data4log.keys():
197  data4log['password'] = '*'
198 
199  retries = [0] + list(self.retries)
200 
201  while True:
202  logging.debug('Querying %s with data %s and files %s (retries left: %s, current sleep: %s)...', url, data4log, files, len(retries), retries[0])
203 
204  time.sleep(retries.pop(0))
205 
206  try:
207  self.curl.setopt(self.curl.URL, url)
208  self.curl.setopt(self.curl.HTTPGET, 1)
209 
210  # from now on we use the token we got from the login
211  self.curl.setopt(pycurl.USERPWD, '%s:""' % ( str(self.token), ) )
212  self.curl.setopt(pycurl.HTTPHEADER, ['Accept: application/json'])
213 
214  if data is not None or files is not None:
215  # If there is data or files to send, use a POST request
216 
217  finalData = {}
218 
219  if data is not None:
220  finalData.update(data)
221 
222  if files is not None:
223  for (key, fileName) in files.items():
224  finalData[key] = (self.curl.FORM_FILE, fileName)
225  self.curl.setopt( self.curl.HTTPPOST, finalData.items() )
226 
227  self.curl.setopt(pycurl.VERBOSE, 0)
228 
229  response = cStringIO.StringIO()
230  self.curl.setopt(self.curl.WRITEFUNCTION, response.write)
231  self.curl.perform()
232 
233  code = self.curl.getinfo(self.curl.RESPONSE_CODE)
234 
235  if code in self.retryCodes and len(retries) > 0:
236  logging.debug('Retrying since we got the %s error code...', code)
237  continue
238 
239  if code != 200:
240  raise HTTPError(code, response.getvalue())
241 
242  return response.getvalue()
243 
244  except pycurl.error as e:
245  if len(retries) == 0:
246  raise e
247  logging.debug('Retrying since we got the %s pycurl exception...', str(e))
248 
249 # common/http.py end
250 
251 def addToTarFile(tarFile, fileobj, arcname):
252  tarInfo = tarFile.gettarinfo(fileobj = fileobj, arcname = arcname)
253  tarInfo.mode = 0o400
254  tarInfo.uid = tarInfo.gid = tarInfo.mtime = 0
255  tarInfo.uname = tarInfo.gname = 'root'
256  tarFile.addfile(tarInfo, fileobj)
257 
258 class ConditionsUploader(object):
259  '''Upload conditions to the CMS conditions uploader service.
260  '''
261 
262  def __init__(self, hostname = defaultHostname, urlTemplate = defaultUrlTemplate):
263  self.hostname = hostname
264  self.urlTemplate = urlTemplate
265  self.userName = None
266  self.http = None
267  self.password = None
268 
269  def setHost( self, hostname ):
270  self.hostname = hostname
271 
272  def signIn(self, username, password):
273  ''' init the server.
274  '''
275  self.http = HTTP()
276  self.http.setBaseUrl(self.urlTemplate % self.hostname)
277  '''Signs in the server.
278  '''
279 
280  logging.info('%s: Signing in user %s ...', self.hostname, username)
281  try:
282  self.token = self.http.getToken(username, password)
283  except Exception as e:
284  logging.error("Caught exception when trying to get token for user %s from %s: %s" % (username, self.hostname, str(e)) )
285  return False
286 
287  if not self.token:
288  logging.error("could not get token for user %s from %s" % (username, self.hostname) )
289  return False
290 
291  logging.debug( "got: '%s'", str(self.token) )
292  self.userName = username
293  self.password = password
294  return True
295 
296  def signInAgain(self):
297  return self.signIn( self.userName, self.password )
298 
299  def signOut(self):
300  '''Signs out the server.
301  '''
302 
303  logging.info('%s: Signing out...', self.hostname)
304  # self.http.query('logout')
305  self.token = None
306 
307 
308  def uploadFile(self, filename, backend = defaultBackend, temporaryFile = defaultTemporaryFile):
309  '''Uploads a file to the dropBox.
310 
311  The filename can be without extension, with .db or with .txt extension.
312  It will be stripped and then both .db and .txt files are used.
313  '''
314 
315  basepath = filename.rsplit('.db', 1)[0].rsplit('.txt', 1)[0]
316  metadataFilename = '%s.txt' % basepath
317  with open(metadataFilename, 'rb') as metadataFile:
318  metadata = json.load( metadataFile )
319  # When dest db = prep the hostname has to be set to dev.
320  forceHost = False
321  destDb = metadata['destinationDatabase']
322  ret = False
323  if destDb.startswith('oracle://cms_orcon_prod') or destDb.startswith('oracle://cms_orcoff_prep'):
324  if destDb.startswith('oracle://cms_orcoff_prep'):
325  self.setHost( defaultDevHostname )
326  self.signInAgain()
327  forceHost = True
328  ret = self._uploadFile(filename, backend, temporaryFile)
329  if forceHost:
330  # set back the hostname to the original global setting
331  self.setHost( defaultHostname )
332  self.signInAgain()
333  else:
334  logging.error("DestinationDatabase %s is not valid. Skipping the upload." %destDb)
335  return ret
336 
337  def _uploadFile(self, filename, backend = defaultBackend, temporaryFile = defaultTemporaryFile):
338 
339  basepath = filename.rsplit('.db', 1)[0].rsplit('.txt', 1)[0]
340  basename = os.path.basename(basepath)
341 
342  logging.debug('%s: %s: Creating tar file for upload ...', self.hostname, basename)
343 
344  try:
345  tarFile = tarfile.open(temporaryFile, 'w:bz2')
346 
347  with open('%s.db' % basepath, 'rb') as data:
348  addToTarFile(tarFile, data, 'data.db')
349  except Exception as e:
350  msg = 'Error when creating tar file. \n'
351  msg += 'Please check that you have write access to the directory you are running,\n'
352  msg += 'and that you have enough space on this disk (df -h .)\n'
353  logging.error(msg)
354  raise Exception(msg)
355 
356  with tempfile.NamedTemporaryFile() as metadata:
357  with open('%s.txt' % basepath, 'rb') as originalMetadata:
358  json.dump(json.load(originalMetadata), metadata, sort_keys = True, indent = 4)
359 
360  metadata.seek(0)
361  addToTarFile(tarFile, metadata, 'metadata.txt')
362 
363  tarFile.close()
364 
365  logging.debug('%s: %s: Calculating hash...', self.hostname, basename)
366 
367  fileHash = hashlib.sha1()
368  with open(temporaryFile, 'rb') as f:
369  while True:
370  data = f.read(4 * 1024 * 1024)
371  if not data:
372  break
373  fileHash.update(data)
374 
375  fileHash = fileHash.hexdigest()
376  fileInfo = os.stat(temporaryFile)
377  fileSize = fileInfo.st_size
378 
379  logging.debug('%s: %s: Hash: %s', self.hostname, basename, fileHash)
380 
381  logging.info('%s: %s: Uploading file (%s, size %s) to the %s backend...', self.hostname, basename, fileHash, fileSize, backend)
382  os.rename(temporaryFile, fileHash)
383  try:
384  ret = self.http.query('uploadFile',
385  {
386  'backend': backend,
387  'fileName': basename,
388  'userName': self.userName,
389  },
390  files = {
391  'uploadedFile': fileHash,
392  }
393  )
394  except Exception as e:
395  logging.error('Error from uploading: %s' % str(e))
396  ret = json.dumps( { "status": -1, "upload" : { 'itemStatus' : { basename : {'status':'failed', 'info':str(e)}}}, "error" : str(e)} )
397 
398  os.unlink(fileHash)
399 
400  statusInfo = json.loads(ret)['upload']
401  logging.debug( 'upload returned: %s', statusInfo )
402 
403  okTags = []
404  skippedTags = []
405  failedTags = []
406  for tag, info in statusInfo['itemStatus'].items():
407  logging.debug('checking tag %s, info %s', tag, str(json.dumps(info, indent=4,sort_keys=True)) )
408  if 'ok' in info['status'].lower() :
409  okTags.append( tag )
410  logging.info('tag %s successfully uploaded', tag)
411  if 'skip' in info['status'].lower() :
412  skippedTags.append( tag )
413  logging.warning('found tag %s to be skipped. reason: \n ... \t%s ', tag, info['info'])
414  if 'fail' in info['status'].lower() :
415  failedTags.append( tag )
416  logging.error('found tag %s failed to upload. reason: \n ... \t%s ', tag, info['info'])
417 
418  if len(okTags) > 0: logging.info ("tags sucessfully uploaded: %s ", str(okTags) )
419  if len(skippedTags) > 0: logging.warning("tags SKIPped to upload : %s ", str(skippedTags) )
420  if len(failedTags) > 0: logging.error ("tags FAILed to upload : %s ", str(failedTags) )
421 
422  fileLogURL = 'https://%s/logs/dropBox/getFileLog?fileHash=%s'
423  logging.info('file log at: %s', fileLogURL % (self.hostname,fileHash))
424 
425  return len(okTags)>0
double split
Definition: MVATrainer.cc:139
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run