2 '''Script that uploads to the new dropBox.
5 __author__ =
'Miguel Ojeda'
6 __copyright__ =
'Copyright 2012, CERN CMS'
7 __credits__ = [
'Giacomo Govi',
'Salvatore Di Guida',
'Miguel Ojeda',
'Andreas Pfeiffer']
8 __license__ =
'Unknown'
9 __maintainer__ =
'Miguel Ojeda'
10 __email__ =
'mojedasa@cern.ch'
28 defaultBackend =
'online'
29 defaultHostname =
'cms-conddb-prod.cern.ch'
30 defaultUrlTemplate =
'https://%s/dropBox/'
31 defaultTemporaryFile =
'upload.tar.bz2'
32 defaultNetrcHost =
'DropBox'
33 defaultWorkflow =
'offline'
49 '''A CERN SSO exception.
54 '''Returns the required CERN SSO cookies for a URL using Kerberos.
56 They can be used with any HTTP client (libcurl, wget, urllib...).
58 If you wish to make secure SSL connections to the CERN SSO
59 (i.e. verify peers/hosts), you may need to install the CERN-CA-certs package.
60 Use secure == False to skip this (i.e. this is the same as curl
61 -k/--insecure). Not recommended: tell users to install them or use lxplus6.
63 The same way, if you have a self-signed certificate in your target URL
64 you can use secureTarget == False as well. Note that this option
65 is provided in order to be able to use a secure SSL connection to CERN SSO,
66 even if the connection to your target URL is not secure. Note that
67 you will probably need the CERN-CA-certs package after you get a certificate
68 signed by the CERN CA (https://cern.ch/ca), even if you did not need it
71 Note that this method *does* a query to the given URL if successful.
73 This was implemented outside the HTTP class for two main reasons:
75 * The only thing needed to use CERN SSO is the cookies, therefore
76 this function is useful alone as well (e.g. as a simple replacement
77 of the cern-get-sso-cookie script or as a Python port of
78 the WWW::CERNSSO::Auth Perl package -- this one does not write
79 any file and can be used in-memory, by the way).
81 * We do not need to use the curl handler of the HTTP class.
82 This way we do not overwrite any options in that one and we use
83 only a temporary one here for getting the cookie.
85 TODO: Support also Certificate/Key authentication.
86 TODO: Support also Username/Password authentication.
87 TODO: Review the error paths.
88 TODO: Why PERLSESSID was used in the original code?
89 TODO: Retry if timeouts are really common (?)
93 response = cStringIO.StringIO()
94 curl.setopt(curl.WRITEFUNCTION, response.write)
96 code = curl.getinfo(curl.RESPONSE_CODE)
97 response = response.getvalue()
98 effectiveUrl = curl.getinfo(curl.EFFECTIVE_URL)
99 return (code, response, effectiveUrl)
104 CERN_SSO_CURL_USER_AGENT_KRB =
'curl-sso-kerberos/%s' % VERSION
105 CERN_SSO_CURL_AUTHERR =
'HTTP Error 401.2 - Unauthorized'
106 CERN_SSO_CURL_ADFS_EP =
'/adfs/ls/auth'
107 CERN_SSO_CURL_ADFS_SIGNIN =
'wa=wsignin1.0'
108 CERN_SSO_CURL_CAPATH =
'/etc/pki/tls/certs'
110 logging.debug(
'secureTarget = %s', secureTarget)
111 logging.debug(
'secure = %s', secure)
116 curl.setopt(curl.COOKIEFILE,
'')
119 curl.setopt(curl.TIMEOUT, 10)
120 curl.setopt(curl.CONNECTTIMEOUT, 10)
123 curl.setopt(curl.USERAGENT, CERN_SSO_CURL_USER_AGENT_KRB)
124 curl.setopt(curl.HTTPAUTH, curl.HTTPAUTH_GSSNEGOTIATE)
125 curl.setopt(curl.USERPWD,
':')
129 curl.setopt(curl.FOLLOWLOCATION, 1)
130 curl.setopt(curl.UNRESTRICTED_AUTH, 1)
133 curl.setopt(curl.HEADER, 0)
137 curl.setopt(curl.SSL_VERIFYPEER, 1)
138 curl.setopt(curl.SSL_VERIFYHOST, 2)
139 curl.setopt(curl.CAPATH, CERN_SSO_CURL_CAPATH)
141 curl.setopt(curl.SSL_VERIFYPEER, 0)
142 curl.setopt(curl.SSL_VERIFYHOST, 0)
145 logging.debug(
'Connecting to %s', url)
146 curl.setopt(curl.URL, url)
147 (code, response, effectiveUrl) = perform()
149 if CERN_SSO_CURL_ADFS_EP
not in effectiveUrl:
150 raise CERNSSOError(
'Not behind SSO or we already have the cookie.')
153 logging.debug(
'Redirected to IDP %s', effectiveUrl)
157 curl.setopt(curl.SSL_VERIFYPEER, 1)
158 curl.setopt(curl.SSL_VERIFYHOST, 2)
159 curl.setopt(curl.CAPATH, CERN_SSO_CURL_CAPATH)
161 curl.setopt(curl.SSL_VERIFYPEER, 0)
162 curl.setopt(curl.SSL_VERIFYHOST, 0)
164 curl.setopt(curl.URL, effectiveUrl)
165 (code, response, effectiveUrl) = perform()
167 if CERN_SSO_CURL_AUTHERR
in response:
168 raise CERNSSOError(
'Authentication error: Redirected to IDP Authentication error %s' % effectiveUrl)
170 match = re.search(
'form .+?action="([^"]+)"', response)
172 raise CERNSSOError(
'Something went wrong: could not find the expected redirection form (do you have a valid Kerberos ticket? -- see klist and kinit).')
175 spUrl = match.groups()[0]
176 logging.debug(
'Redirected (via form) to SP %s', spUrl)
178 formPairs = re.findall(
'input type="hidden" name="([^"]+)" value="([^"]+)"', response)
183 htmlParser = HTMLParser.HTMLParser()
184 formPairs = [(x[0], htmlParser.unescape(x[1]))
for x
in formPairs]
188 curl.setopt(curl.SSL_VERIFYPEER, 1)
189 curl.setopt(curl.SSL_VERIFYHOST, 2)
190 curl.setopt(curl.CAPATH, CERN_SSO_CURL_CAPATH)
192 curl.setopt(curl.SSL_VERIFYPEER, 0)
193 curl.setopt(curl.SSL_VERIFYHOST, 0)
195 curl.setopt(curl.URL, spUrl)
196 curl.setopt(curl.POSTFIELDS, urllib.urlencode(formPairs))
197 curl.setopt(curl.POST, 1)
198 (code, response, effectiveUrl) = perform()
200 if CERN_SSO_CURL_ADFS_SIGNIN
in effectiveUrl:
201 raise CERNSSOError(
'Something went wrong: still on the auth page.')
204 return curl.getinfo(curl.INFO_COOKIELIST)
208 '''A common HTTP exception.
210 self.code is the response HTTP code as an integer.
211 self.response is the response body (i.e. page).
220 self.
args = (response.split(
'<p>')[1].
split(
'</p>')[0], )
226 '''Class used for querying URLs using the HTTP protocol.
229 retryCodes = frozenset([502, 503])
237 self.curl.setopt(self.curl.COOKIEFILE,
'')
238 self.curl.setopt(self.curl.SSL_VERIFYPEER, 0)
239 self.curl.setopt(self.curl.SSL_VERIFYHOST, 0)
243 '''Returns the list of cookies.
246 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.
265 '''Allows to set a proxy.
268 self.curl.setopt(self.curl.PROXY, proxy)
272 '''Allows to set a timeout.
275 self.curl.setopt(self.curl.TIMEOUT, timeout)
279 '''Allows to set retries.
281 The retries are a sequence of the seconds to wait per retry.
283 The retries are done on:
284 * PyCurl errors (includes network problems, e.g. not being able
285 to connect to the host).
286 * 502 Bad Gateway (for the moment, to avoid temporary
287 Apache-CherryPy issues).
288 * 503 Service Temporarily Unavailable (for when we update
295 def query(self, url, data = None, files = None, keepCookies = True):
296 '''Queries a URL, optionally with some data (dictionary).
298 If no data is specified, a GET request will be used.
299 If some data is specified, a POST request will be used.
301 If files is specified, it must be a dictionary like data but
302 the values are filenames.
304 By default, cookies are kept in-between requests.
306 A HTTPError exception is raised if the response's HTTP code is not 200.
315 data4log = copy.copy(data)
317 if 'password' in data4log.keys():
318 data4log[
'password'] =
'*'
323 logging.debug(
'Querying %s with data %s and files %s (retries left: %s, current sleep: %s)...', url, data4log, files, len(retries), retries[0])
325 time.sleep(retries.pop(0))
328 self.curl.setopt(self.curl.URL, url)
329 self.curl.setopt(self.curl.HTTPGET, 1)
331 if data
is not None or files
is not None:
337 finalData.update(data)
339 if files
is not None:
340 for (key, fileName)
in files.items():
341 finalData[key] = (self.curl.FORM_FILE, fileName)
343 self.curl.setopt(self.curl.HTTPPOST, finalData.items())
345 response = cStringIO.StringIO()
346 self.curl.setopt(self.curl.WRITEFUNCTION, response.write)
349 code = self.curl.getinfo(self.curl.RESPONSE_CODE)
351 if code
in self.
retryCodes and len(retries) > 0:
352 logging.debug(
'Retrying since we got the %s error code...', code)
356 raise HTTPError(code, response.getvalue())
358 return response.getvalue()
360 except pycurl.error
as e:
361 if len(retries) == 0:
364 logging.debug(
'Retrying since we got the %s pycurl exception...', str(e))
368 '''Adds the required CERN SSO cookies for a URL using Kerberos.
370 After calling this, you can use query() for your SSO-protected URLs.
372 This method will use your Kerberos ticket to sign in automatically
373 in CERN SSO (i.e. no password required).
375 If you do not have a ticket yet, use kinit.
377 If you wish to make secure SSL connections to the CERN SSO
378 (i.e. verify peers/hosts), you may need to install the CERN-CA-certs package.
379 Use secure == False to skip this (i.e. this is the same as curl
380 -k/--insecure). Not recommended: tell users to install them or use lxplus6.
382 The same way, if you have a self-signed certificate in your target URL
383 you can use secureTarget == False as well. Note that this option
384 is provided in order to be able to use a secure SSL connection to CERN SSO,
385 even if the connection to your target URL is not secure. Note that
386 you will probably need the CERN-CA-certs package after you get a certificate
387 signed by the CERN CA (https://cern.ch/ca), even if you did not need it
390 Note that this method *does* a query to the given URL if successful.
392 Note that you may need different cookies for different URLs/applications.
394 Note that this method may raise also CERNSSOError exceptions.
398 self.curl.setopt(self.curl.COOKIELIST, cookie)
404 tarInfo = tarFile.gettarinfo(fileobj = fileobj, arcname = arcname)
406 tarInfo.uid = tarInfo.gid = tarInfo.mtime = 0
407 tarInfo.uname = tarInfo.gname =
'root'
408 tarFile.addfile(tarInfo, fileobj)
412 '''A dropBox API class.
415 def __init__(self, hostname = defaultHostname, urlTemplate = defaultUrlTemplate):
418 self.http.setBaseUrl(urlTemplate % hostname)
422 '''Signs in the server via CERN SSO.
426 logging.info(
'%s: Signing in via CERN SSO...', self.
hostname)
428 logging.info(
'%s: Signing in via CERN SSO (insecure)...', self.
hostname)
434 if 'cms-conddb-prod' in self.
hostname:
449 self.http.addCERNSSOCookies(
'signInSSO', secureTarget, secure)
453 '''Signs in the server.
456 logging.info(
'%s: Signing in...', self.
hostname)
457 self.http.query(
'signIn', {
458 'username': username,
459 'password': password,
464 '''Signs out the server.
467 logging.info(
'%s: Signing out...', self.
hostname)
468 self.http.query(
'signOut')
472 '''Updates this script, if a new version is found.
475 logging.info(
'%s: Checking for updates...', self.
hostname)
476 version = int(self.http.query(
'getUploadScriptVersion'))
478 if version <= __version__:
479 logging.info(
'%s: Up to date.', self.
hostname)
482 logging.info(
'%s: There is a newer version (%s) than the current one (%s): Updating...', self.
hostname, version, __version__)
484 logging.info(
'%s: Downloading new version...', self.
hostname)
485 uploadScript = self.http.query(
'getUploadScript')
489 logging.info(
'%s: Saving new version...', self.
hostname)
490 with open(sys.argv[0],
'wb')
as f:
491 f.write(uploadScript)
493 logging.info(
'%s: Executing new version...', self.
hostname)
494 os.execl(sys.executable, *([sys.executable] + sys.argv))
497 def uploadFile(self, filename, backend = defaultBackend, temporaryFile = defaultTemporaryFile):
498 '''Uploads a file to the dropBox.
500 The filename can be without extension, with .db or with .txt extension.
501 It will be stripped and then both .db and .txt files are used.
504 basepath = filename.rsplit(
'.db', 1)[0].rsplit(
'.txt', 1)[0]
505 basename = os.path.basename(basepath)
507 logging.info(
'%s: %s: Creating tar file...', self.
hostname, basename)
509 tarFile = tarfile.open(temporaryFile,
'w:bz2')
511 with open(
'%s.db' % basepath,
'rb')
as data:
514 with tempfile.NamedTemporaryFile()
as metadata:
515 with open(
'%s.txt' % basepath,
'rb')
as originalMetadata:
516 json.dump(json.load(originalMetadata), metadata, sort_keys =
True, indent = 4)
523 logging.info(
'%s: %s: Calculating hash...', self.
hostname, basename)
525 fileHash = hashlib.sha1()
526 with open(temporaryFile,
'rb')
as f:
528 data = f.read(4 * 1024 * 1024)
533 fileHash.update(data)
535 fileHash = fileHash.hexdigest()
537 logging.info(
'%s: %s: Hash: %s', self.
hostname, basename, fileHash)
539 logging.info(
'%s: %s: Uploading file for the %s backend...', self.
hostname, basename, backend)
540 os.rename(temporaryFile, fileHash)
541 self.http.query(
'uploadFile', {
543 'fileName': basename,
545 'uploadedFile': fileHash,
551 '''Like raw_input() but with a default and automatic strip().
554 answer = raw_input(prompt)
556 return answer.strip()
558 return default.strip()
562 '''Like getInput() but tailored to get target workflows (synchronization options).
566 workflow =
getInput(defaultWorkflow, prompt)
568 if workflow
in frozenset([
'offline',
'hlt',
'express',
'prompt',
'pcl']):
571 logging.error(
'Please specify one of the allowed workflows. See above for the explanation on each of them.')
575 '''Makes the user choose from a list of options.
582 return optionsList[int(index)]
584 logging.error(
'Please specify an index of the list (i.e. integer).')
586 logging.error(
'The index you provided is not in the given list.')
590 '''Like raw_input() but repeats if nothing is provided and automatic strip().
594 answer = raw_input(prompt)
596 return answer.strip()
598 logging.error(
'You need to provide a value.')
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