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.
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'
27 defaultBackend =
'online'
28 defaultHostname =
'cms-conddb-prod.cern.ch'
29 defaultDevHostname =
'cms-conddb-dev.cern.ch'
30 defaultUrlTemplate =
'https://%s/cmsDbUpload/'
31 defaultTemporaryFile =
'upload.tar.bz2'
32 defaultNetrcHost =
'ConditionUploader'
33 defaultWorkflow =
'offline'
44 '''Like raw_input() but with a default and automatic strip().
47 answer = raw_input(prompt)
51 return default.strip()
55 '''Like getInput() but tailored to get target workflows (synchronization options).
59 workflow =
getInput(defaultWorkflow, prompt)
61 if workflow
in frozenset([
'offline',
'hlt',
'express',
'prompt',
'pcl']):
64 logging.error(
'Please specify one of the allowed workflows. See above for the explanation on each of them.')
68 '''Makes the user choose from a list of options.
75 return optionsList[int(index)]
77 logging.error(
'Please specify an index of the list (i.e. integer).')
79 logging.error(
'The index you provided is not in the given list.')
83 '''Like raw_input() but repeats if nothing is provided and automatic strip().
87 answer = raw_input(prompt)
91 logging.error(
'You need to provide a value.')
94 def runWizard(basename, dataFilename, metadataFilename):
96 print '''\nWizard for metadata for %s
98 I will ask you some questions to fill the metadata file. For some of the questions there are defaults between square brackets (i.e. []), leave empty (i.e. hit Enter) to use them.''' % basename
102 dataConnection = sqlite3.connect(dataFilename)
103 dataCursor = dataConnection.cursor()
104 dataCursor.execute(
'select name from sqlite_master where type == "table"')
105 tables = set(zip(*dataCursor.fetchall())[0])
109 dataCursor.execute(
'select NAME from TAG')
114 inputTags = dataCursor.fetchall()
115 if len(inputTags) == 0:
117 inputTags = zip(*inputTags)[0]
122 if len(inputTags) == 0:
123 print '\nI could not find any input tag in your data file, but you can still specify one manually.'
126 '\nWhich is the input tag (i.e. the tag to be read from the SQLite data file)?\ne.g. BeamSpotObject_ByRun\ninputTag: ')
129 print '\nI found the following input tags in your SQLite data file:'
130 for (index, inputTag)
in enumerate(inputTags):
131 print ' %s) %s' % (index, inputTag)
134 '\nWhich is the input tag (i.e. the tag to be read from the SQLite data file)?\ne.g. 0 (you select the first in the list)\ninputTag [0]: ')
136 destinationDatabase =
''
138 while ( destinationDatabase !=
'oracle://cms_orcon_prod/CMS_CONDITIONS' and destinationDatabase !=
'oracle://cms_orcoff_prep/CMS_CONDITIONS' ):
141 '\nWhich is the destination database where the tags should be exported? \nPossible choices: oracle://cms_orcon_prod/CMS_CONDITIONS (for prod) or oracle://cms_orcoff_prep/CMS_CONDITIONS (for prep) \ndestinationDatabase: '
144 '\nPlease choose one of the two valid destinations: \noracle://cms_orcon_prod/CMS_CONDITIONS (for prod) or oracle://cms_orcoff_prep/CMS_CONDITIONS (for prep) \
145 \ndestinationDatabase: '
147 raise Exception(
'No valid destination chosen. Bailing out...')
153 '\nWhich is the given since? (if not specified, the one from the SQLite data file will be taken -- note that even if specified, still this may not be the final since, depending on the synchronization options you select later: if the synchronization target is not offline, and the since you give is smaller than the next possible one (i.e. you give a run number earlier than the one which will be started/processed next in prompt/hlt/express), the DropBox will move the since ahead to go to the first safe run instead of the value you gave)\ne.g. 1234\nsince []: ')
162 logging.error(
'The since value has to be an integer or empty (null).')
165 '\nWrite any comments/text you may want to describe your request\ne.g. Muon alignment scenario for...\nuserText []: ')
170 '\nWhich is the next destination tag to be added (leave empty to stop)?\ne.g. BeamSpotObjects_PCL_byRun_v0_offline\ndestinationTag []: ')
171 if not destinationTag:
172 if len(destinationTags) == 0:
173 logging.error(
'There must be at least one destination tag.')
177 if destinationTag
in destinationTags:
179 'You already added this destination tag. Overwriting the previous one with this new one.')
181 destinationTags[destinationTag] = {
185 'destinationDatabase': destinationDatabase,
186 'destinationTags': destinationTags,
187 'inputTag': inputTag,
189 'userText': userText,
192 metadata = json.dumps(metadata, sort_keys=
True, indent=4)
193 print '\nThis is the generated metadata:\n%s' % metadata
196 '\nIs it fine (i.e. save in %s and *upload* the conditions if this is the latest file)?\nAnswer [n]: ' % metadataFilename).lower() ==
'y':
198 logging.info(
'Saving generated metadata in %s...', metadataFilename)
199 with open(metadataFilename,
'wb')
as metadataFile:
200 metadataFile.write(metadata)
203 '''A common HTTP exception.
205 self.code is the response HTTP code as an integer.
206 self.response is the response body (i.e. page).
215 self.
args = (response.split(
'<p>')[1].
split(
'</p>')[0], )
220 CERN_SSO_CURL_CAPATH =
'/etc/pki/tls/certs'
223 '''Class used for querying URLs using the HTTP protocol.
226 retryCodes = frozenset([502, 503])
233 self.curl.setopt(self.curl.COOKIEFILE,
'')
238 self.curl.setopt(self.curl.SSL_VERIFYPEER, 0)
239 self.curl.setopt(self.curl.SSL_VERIFYHOST, 2)
246 '''Returns the list of cookies.
248 return self.curl.getinfo(self.curl.INFO_COOKIELIST)
253 self.curl.setopt(self.curl.COOKIELIST,
'ALL')
257 '''Allows to set a base URL which will be prefixed to all the URLs
258 that will be queried later.
264 '''Allows to set a proxy.
266 self.curl.setopt(self.curl.PROXY, proxy)
270 '''Allows to set a timeout.
272 self.curl.setopt(self.curl.TIMEOUT, timeout)
276 '''Allows to set retries.
278 The retries are a sequence of the seconds to wait per retry.
280 The retries are done on:
281 * PyCurl errors (includes network problems, e.g. not being able
282 to connect to the host).
283 * 502 Bad Gateway (for the moment, to avoid temporary
284 Apache-CherryPy issues).
285 * 503 Service Temporarily Unavailable (for when we update
294 self.curl.setopt(pycurl.URL, url)
295 self.curl.setopt(pycurl.VERBOSE, 0)
305 self.curl.setopt(pycurl.HTTPHEADER, [
'Accept: application/json'])
307 self.curl.setopt(self.curl.HTTPGET, 0)
309 response = cStringIO.StringIO()
310 self.curl.setopt(pycurl.WRITEFUNCTION, response.write)
311 self.curl.setopt(pycurl.USERPWD,
'%s:%s' % (username, password) )
313 logging.debug(
'going to connect to server at: %s' % url )
316 code = self.curl.getinfo(pycurl.RESPONSE_CODE)
317 logging.debug(
'got: %s ', str(code))
320 self.
token = json.loads( response.getvalue() )[
'token']
321 except Exception
as e:
322 logging.error(
'http::getToken> got error from server: %s ', str(e) )
323 if 'No JSON object could be decoded' in str(e):
325 logging.error(
"error getting token: %s", str(e))
328 logging.debug(
'token: %s', self.
token)
329 logging.debug(
'returning: %s', response.getvalue())
331 return response.getvalue()
333 def query(self, url, data = None, files = None, keepCookies = True):
334 '''Queries a URL, optionally with some data (dictionary).
336 If no data is specified, a GET request will be used.
337 If some data is specified, a POST request will be used.
339 If files is specified, it must be a dictionary like data but
340 the values are filenames.
342 By default, cookies are kept in-between requests.
344 A HTTPError exception is raised if the response's HTTP code is not 200.
353 data4log = copy.copy(data)
355 if 'password' in data4log.keys():
356 data4log[
'password'] =
'*'
361 logging.debug(
'Querying %s with data %s and files %s (retries left: %s, current sleep: %s)...', url, data4log, files, len(retries), retries[0])
363 time.sleep(retries.pop(0))
366 self.curl.setopt(self.curl.URL, url)
367 self.curl.setopt(self.curl.HTTPGET, 1)
370 self.curl.setopt(pycurl.USERPWD,
'%s:""' % ( str(self.
token), ) )
371 self.curl.setopt(pycurl.HTTPHEADER, [
'Accept: application/json'])
373 if data
is not None or files
is not None:
379 finalData.update(data)
381 if files
is not None:
382 for (key, fileName)
in files.items():
383 finalData[key] = (self.curl.FORM_FILE, fileName)
384 self.curl.setopt( self.curl.HTTPPOST, finalData.items() )
386 self.curl.setopt(pycurl.VERBOSE, 0)
388 response = cStringIO.StringIO()
389 self.curl.setopt(self.curl.WRITEFUNCTION, response.write)
392 code = self.curl.getinfo(self.curl.RESPONSE_CODE)
394 if code
in self.
retryCodes and len(retries) > 0:
395 logging.debug(
'Retrying since we got the %s error code...', code)
399 raise HTTPError(code, response.getvalue())
401 return response.getvalue()
403 except pycurl.error
as e:
404 if len(retries) == 0:
406 logging.debug(
'Retrying since we got the %s pycurl exception...', str(e))
411 tarInfo = tarFile.gettarinfo(fileobj = fileobj, arcname = arcname)
413 tarInfo.uid = tarInfo.gid = tarInfo.mtime = 0
414 tarInfo.uname = tarInfo.gname =
'root'
415 tarFile.addfile(tarInfo, fileobj)
418 '''Upload conditions to the CMS conditions uploader service.
421 def __init__(self, hostname = defaultHostname, urlTemplate = defaultUrlTemplate):
436 '''Signs in the server.
439 logging.info(
'%s: Signing in user %s ...', self.
hostname, username)
441 self.
token = self.http.getToken(username, password)
442 except Exception
as e:
443 logging.error(
"Caught exception when trying to get token for user %s from %s: %s" % (username, self.
hostname, str(e)) )
447 logging.error(
"could not get token for user %s from %s" % (username, self.
hostname) )
450 logging.debug(
"got: '%s'", str(self.
token) )
459 '''Signs out the server.
462 logging.info(
'%s: Signing out...', self.
hostname)
468 '''Updates this script, if a new version is found.
471 logging.debug(
'%s: Checking if a newer version of this script is available ...', self.
hostname)
472 version = int(self.http.query(
'getUploadScriptVersion'))
474 if version <= __version__:
475 logging.debug(
'%s: Script is up-to-date.', self.
hostname)
478 logging.info(
'%s: Updating to a newer version (%s) than the current one (%s): downloading ...', self.
hostname, version, __version__)
480 uploadScript = self.http.query(
'getUploadScript')
484 logging.info(
'%s: ... saving the new version ...', self.
hostname)
485 with open(sys.argv[0],
'wb')
as f:
486 f.write(uploadScript)
488 logging.info(
'%s: ... executing the new version...', self.
hostname)
489 os.execl(sys.executable, *([sys.executable] + sys.argv))
492 def uploadFile(self, filename, backend = defaultBackend, temporaryFile = defaultTemporaryFile):
493 '''Uploads a file to the dropBox.
495 The filename can be without extension, with .db or with .txt extension.
496 It will be stripped and then both .db and .txt files are used.
499 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
500 basename = os.path.basename(basepath)
502 logging.debug(
'%s: %s: Creating tar file for upload ...', self.
hostname, basename)
505 tarFile = tarfile.open(temporaryFile,
'w:bz2')
507 with open(
'%s.db' % basepath,
'rb')
as data:
509 except Exception
as e:
510 msg =
'Error when creating tar file. \n'
511 msg +=
'Please check that you have write access to the directory you are running,\n'
512 msg +=
'and that you have enough space on this disk (df -h .)\n'
516 with tempfile.NamedTemporaryFile()
as metadata:
517 with open(
'%s.txt' % basepath,
'rb')
as originalMetadata:
518 json.dump(json.load(originalMetadata), metadata, sort_keys =
True, indent = 4)
525 logging.debug(
'%s: %s: Calculating hash...', self.
hostname, basename)
527 fileHash = hashlib.sha1()
528 with open(temporaryFile,
'rb')
as f:
530 data = f.read(4 * 1024 * 1024)
533 fileHash.update(data)
535 fileHash = fileHash.hexdigest()
536 fileInfo = os.stat(temporaryFile)
537 fileSize = fileInfo.st_size
539 logging.debug(
'%s: %s: Hash: %s', self.
hostname, basename, fileHash)
541 logging.info(
'%s: %s: Uploading file (%s, size %s) to the %s backend...', self.
hostname, basename, fileHash, fileSize, backend)
542 os.rename(temporaryFile, fileHash)
544 ret = self.http.query(
'uploadFile',
547 'fileName': basename,
551 'uploadedFile': fileHash,
554 except Exception
as e:
555 logging.error(
'Error from uploading: %s' % str(e))
556 ret = json.dumps( {
"status": -1,
"upload" : {
'itemStatus' : { basename : {
'status':
'failed',
'info':str(e)}}},
"error" : str(e)} )
560 statusInfo = json.loads(ret)[
'upload']
561 logging.debug(
'upload returned: %s', statusInfo )
566 for tag, info
in statusInfo[
'itemStatus'].items():
567 logging.debug(
'checking tag %s, info %s', tag, str(json.dumps(info, indent=4,sort_keys=
True)) )
568 if 'ok' in info[
'status'].lower() :
570 logging.info(
'tag %s successfully uploaded', tag)
571 if 'skip' in info[
'status'].lower() :
572 skippedTags.append( tag )
573 logging.warning(
'found tag %s to be skipped. reason: \n ... \t%s ', tag, info[
'info'])
574 if 'fail' in info[
'status'].lower() :
575 failedTags.append( tag )
576 logging.error(
'found tag %s failed to upload. reason: \n ... \t%s ', tag, info[
'info'])
578 if len(okTags) > 0: logging.info (
"tags sucessfully uploaded: %s ", str(okTags) )
579 if len(skippedTags) > 0: logging.warning(
"tags SKIPped to upload : %s ", str(skippedTags) )
580 if len(failedTags) > 0: logging.error (
"tags FAILed to upload : %s ", str(failedTags) )
582 fileLogURL =
'https://%s/logs/dropBox/getFileLog?fileHash=%s'
583 logging.info(
'file log at: %s', fileLogURL % (self.
hostname,fileHash))
591 (username, account, password) = netrc.netrc().authenticators(options.netrcHost)
595 'netrc entry "%s" not found: if you wish not to have to retype your password, you can add an entry in your .netrc file. However, beware of the risks of having your password stored as plaintext. Instead.',
599 defaultUsername = getpass.getuser()
600 if defaultUsername
is None:
601 defaultUsername =
'(not found)'
603 username =
getInput(defaultUsername,
'\nUsername [%s]: ' % defaultUsername)
604 password = getpass.getpass(
'Password: ')
607 return dropBox.signIn(username, password)
617 for filename
in arguments:
618 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
619 basename = os.path.basename(basepath)
620 dataFilename =
'%s.db' % basepath
621 metadataFilename =
'%s.txt' % basepath
623 logging.info(
'Checking %s...', basename)
627 with open(dataFilename,
'rb')
as dataFile:
630 errMsg =
'Impossible to open SQLite data file %s' %dataFilename
631 logging.error( errMsg )
633 ret[
'error'] = errMsg
638 with open(metadataFilename,
'rb')
as metadataFile:
641 if e.errno != errno.ENOENT:
642 errMsg =
'Impossible to open file %s (for other reason than not existing)' %metadataFilename
643 logging.error( errMsg )
645 ret[
'error'] = errMsg
648 if getInput(
'y',
'\nIt looks like the metadata file %s does not exist. Do you want me to create it and help you fill it?\nAnswer [y]: ' % metadataFilename).lower() !=
'y':
649 errMsg =
'Metadata file %s does not exist' %metadataFilename
650 logging.error( errMsg )
652 ret[
'error'] = errMsg
655 runWizard(basename, dataFilename, metadataFilename)
663 logging.error(
"Error authenticating user. Aborting.")
664 return {
'status' : -2,
'error' :
"Error authenticating user. Aborting." }
667 dropBox._checkForUpdates()
670 for filename
in arguments:
671 backend = options.backend
672 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
673 metadataFilename =
'%s.txt' % basepath
674 with open(metadataFilename,
'rb')
as metadataFile:
675 metadata = json.load( metadataFile )
678 destDb = metadata[
'destinationDatabase']
679 if destDb.startswith(
'oracle://cms_orcon_prod')
or destDb.startswith(
'oracle://cms_orcoff_prep'):
680 if destDb.startswith(
'oracle://cms_orcoff_prep'):
681 dropBox.setHost( defaultDevHostname )
682 dropBox.signInAgain()
684 results[filename] = dropBox.uploadFile(filename, options.backend, options.temporaryFile)
687 dropBox.setHost( options.hostname )
688 dropBox.signInAgain()
690 results[filename] =
False
691 logging.error(
"DestinationDatabase %s is not valid. Skipping the upload." %destDb)
692 if not results[filename]:
696 ret[
'files'] = results
697 logging.debug(
"all files processed, logging out now.")
701 except HTTPError
as e:
702 logging.error(
'got HTTP error: %s', str(e))
703 return {
'status' : -1,
'error' : str(e) }
708 '''Uploads a bunch of files coming from Tier0.
709 This has the following requirements:
710 * Username/Password based authentication.
711 * Uses the online backend.
712 * Ignores errors related to the upload/content (e.g. duplicated file).
717 dropBox.signIn(username, password)
719 for filename
in filenames:
721 result = dropBox.uploadFile(filename, backend =
'test')
722 except HTTPError
as e:
727 logging.error(
'HTTP Exception 400 Bad Request: Upload-related, skipping. Message: %s', e)
739 logging.error(
'Error from dropbox, upload-related, skipping.')
749 parser = optparse.OptionParser(usage =
750 'Usage: %prog [options] <file> [<file> ...]\n'
753 parser.add_option(
'-d',
'--debug',
757 help =
'Switch on printing debug information. Default: %default',
760 parser.add_option(
'-b',
'--backend',
762 default = defaultBackend,
763 help =
'dropBox\'s backend to upload to. Default: %default',
766 parser.add_option(
'-H',
'--hostname',
768 default = defaultHostname,
769 help =
'dropBox\'s hostname. Default: %default',
772 parser.add_option(
'-u',
'--urlTemplate',
773 dest =
'urlTemplate',
774 default = defaultUrlTemplate,
775 help =
'dropBox\'s URL template. Default: %default',
778 parser.add_option(
'-f',
'--temporaryFile',
779 dest =
'temporaryFile',
780 default = defaultTemporaryFile,
781 help =
'Temporary file that will be used to store the first tar file. Note that it then will be moved to a file with the hash of the file as its name, so there will be two temporary files created in fact. Default: %default',
784 parser.add_option(
'-n',
'--netrcHost',
786 default = defaultNetrcHost,
787 help =
'The netrc host (machine) from where the username and password will be read. Default: %default',
790 (options, arguments) = parser.parse_args()
792 if len(arguments) < 1:
796 logLevel = logging.INFO
798 logLevel = logging.DEBUG
800 format =
'[%(asctime)s] %(levelname)s: %(message)s',
806 if not results.has_key(
'status'):
807 print 'Unexpected error.'
809 ret = results[
'status']
811 print "upload ended with code: %s" %ret
818 global defaultNetrcHost
820 (username, account, password) = netrc.netrc().authenticators(defaultNetrcHost)
822 filenames = [
'testFiles/localSqlite-top2']
827 if __name__ ==
'__main__':
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