15 Python interface to the ConditionsDB
20 from basf2
import B2FATAL, B2ERROR, B2INFO, B2WARNING, conditions
22 from requests.packages.urllib3.fields
import RequestField
23 from requests.packages.urllib3.filepost
import encode_multipart_formdata
26 from versioning
import upload_global_tag, jira_global_tag_v2
27 from collections
import defaultdict
28 from concurrent.futures
import ThreadPoolExecutor, wait
as futures_wait
31 from typing
import Union
35 def encode_name(name):
36 """Escape name to be used in an url"""
37 return urllib.parse.quote(name, safe=
"")
40 def file_checksum(filename):
41 """Calculate md5 hash of file"""
42 md5hash = hashlib.md5()
43 with open(filename,
"rb")
as data:
44 md5hash.update(data.read())
45 return md5hash.hexdigest()
48 def chunks(container, chunk_size):
49 """Cut a container in chunks of max. chunk_size"""
52 chunk = tuple(itertools.islice(it, chunk_size))
58 def get_cdb_authentication_token(path=None):
60 Helper function for correctly retrieving the CDB authentication token (either via file or via issuing server).
62 :param path: Path to a file containing a CDB authentication token; if None, the function will use
63 a default path (``${HOME}/b2cdb_${BELLE2_USER}.token`` or ``/tmp/b2cdb_${BELLE2_USER}.token``) to look for a token.
69 path_to_token = os.path.join(os.getenv(
'HOME',
'/tmp'), f
'b2cdb_{os.getenv("BELLE2_USER", None)}.token')
72 if os.path.isfile(path_to_token):
73 with open(path_to_token)
as token_file:
74 response = requests.get(
'https://token.belle2.org/check', verify=
False, params={
'token': token_file.read().strip()})
75 if response.status_code == 400:
76 B2INFO(f
'The file {path_to_token} contains an invalid token, getting a new token...')
77 os.unlink(path_to_token)
78 elif response.status_code > 400:
79 B2WARNING(
"The validity of the existing token could not be checked. Trying to connect to CDB anyway.")
82 if not os.path.isfile(path_to_token):
83 username = os.environ[
'BELLE2_USER']
84 othername = input(f
'\nConfirm your DESY user name by pressing enter or type the correct one [{username}]: ')
87 password = getpass.getpass(
"Please type your DESY password to request a CDB token: ")
88 response = requests.get(
'https://token.belle2.org', verify=
False, auth=(username, password))
89 print(response.content)
90 if response.status_code == requests.codes.ok:
91 with open(path_to_token,
'wb')
as token_file:
92 os.chmod(path_to_token, stat.S_IRUSR | stat.S_IWUSR)
93 token_file.write(response.content)
95 B2ERROR(
'Failed to get token')
99 with open(path_to_token)
as token_file:
100 return token_file.read().strip()
103 def set_cdb_authentication_token(cdb_instance, auth_token=None):
105 Helper function for setting the CDB authentication token.
107 :param cdb_instance: An instance of the ``ConditionsDB`` class.
108 :param auth_token: A CDB authentication token: if ``None``, it is automatically retrieved from the issuing server
111 if auth_token
is not None:
112 cdb_instance.set_authentication_token(auth_token)
115 token = get_cdb_authentication_token(os.getenv(
'BELLE2_CDB_AUTH_TOKEN', default=
None))
116 cdb_instance.set_authentication_token(token)
120 """Simple class to present bearer token instead of username/password"""
123 """Construct from a token"""
128 """Update headers to include token"""
129 r.headers[
"X-Authorization"] = self.
_authtoken_authtoken
134 """Small container class to help compare payload information for efficient
135 comparison between globaltags"""
139 """Set all internal members from the json information of the payload and the iov.
142 payload (dict): json information of the payload as returned by REST api
143 iov (dict): json information of the iov as returned by REST api
146 iov = {
"payloadIovId":
None,
"expStart":
None,
"runStart":
None,
"expEnd":
None,
"runEnd":
None}
149 payload[
'payloadId'],
150 payload[
'basf2Module'][
'name'],
153 payload[
'payloadUrl'],
156 (iov[
"expStart"], iov[
"runStart"], iov[
"expEnd"], iov[
"runEnd"]),
159 def __init__(self, payload_id, name, revision, checksum, payload_url, base_url, iov_id=None, iov=None):
161 Create a new object from the given information
182 """Return the full url to the payload on the server"""
186 """Make object hashable"""
190 """Check if two payloads are equal"""
191 return (self.
namename, self.
checksumchecksum, self.
ioviov) == (other.name, other.checksum, other.iov)
194 """Sort payloads by name, iov, revision"""
195 return (self.
namename.lower(), self.
ioviov, self.
revisionrevision) < (other.name.lower(), other.iov, other.revision)
198 """return a human readable name for the IoV"""
199 if self.
ioviov
is None:
202 if self.
ioviov == (0, 0, -1, -1):
205 e1, r1, e2, r2 = self.
ioviov
207 if r1 == 0
and r2 == -1:
210 return f
"exp {e1}, runs {r1}+"
212 return f
"exp {e1}, run {r1}"
214 return f
"exp {e1}, runs {r1} - {r2}"
216 if e2 == -1
and r1 == 0:
217 return f
"exp {e1} - forever"
219 return f
"exp {e1}, run {r1} - forever"
220 elif r1 == 0
and r2 == -1:
221 return f
"exp {e1}-{e2}, all runs"
223 return f
"exp {e1}, run {r1} - exp {e2}, all runs"
225 return f
"exp {e1}, run {r1} - exp {e2}, run {r2}"
229 """Class to interface conditions db REST interface"""
232 BASE_URLS = [conditions.default_metadata_provider_url]
235 """Class to be thrown by request() if there is any error"""
240 """Resolve the list of server urls. If a url is given just return it.
241 Otherwise return servers listed in BELLE2_CONDB_SERVERLIST or the
245 given_url (str): Explicit base_url. If this is not None it will be
246 returned as is in a list of length 1
249 a list of urls to try for database connectivity
252 base_url_list = ConditionsDB.BASE_URLS[:]
253 base_url_env = os.environ.get(
"BELLE2_CONDB_SERVERLIST",
None)
254 if given_url
is not None:
255 base_url_list = [given_url]
256 elif base_url_env
is not None:
257 base_url_list = base_url_env.split()
258 B2INFO(
"Getting Conditions Database servers from Environment:")
259 for i, url
in enumerate(base_url_list, 1):
260 B2INFO(f
" {i}. {url}")
263 for url
in base_url_list:
264 if url.startswith(
"http://"):
265 full_list.append(
"https" + url[4:])
267 full_list.append(url)
270 def __init__(self, base_url=None, max_connections=10, retries=3):
272 Create a new instance of the interface
275 base_url (string): base url of the rest interface
276 max_connections (int): number of connections to keep open, mostly useful for threaded applications
277 retries (int): number of retries in case of connection problems
283 adapter = requests.adapters.HTTPAdapter(
284 pool_connections=max_connections, pool_maxsize=max_connections,
285 max_retries=retries, pool_block=
True
287 self.
_session_session.mount(
"http://", adapter)
288 self.
_session_session.mount(
"https://", adapter)
290 if "BELLE2_CONDB_PROXY" in os.environ:
292 "http": os.environ.get(
"BELLE2_CONDB_PROXY"),
293 "https": os.environ.get(
"BELLE2_CONDB_PROXY"),
296 base_url_list = ConditionsDB.get_base_urls(base_url)
298 for url
in base_url_list:
303 req.raise_for_status()
304 except requests.RequestException
as e:
305 B2WARNING(f
"Problem connecting to {url}:\n {e}\n Trying next server ...")
309 B2FATAL(
"No working database servers configured, giving up")
315 self.
_session_session.headers.update({
"Accept":
"application/json",
"Cache-Control":
"no-cache"})
319 Set authentication token when talking to the database
322 token (str): JWT to hand to the database. Will not be checked
327 def request(self, method, url, message=None, *args, **argk):
329 Request function, similar to requests.request but adding the base_url
332 method (str): GET, POST, etc.
333 url (str): url for the request, base_url will be prepended
334 message (str): message to show when starting the request and if it fails
336 All other arguments will be forwarded to requests.request.
338 if message
is not None:
343 except requests.exceptions.ConnectionError
as e:
344 B2FATAL(
"Could not access '" + self.
_base_url_base_url + url.lstrip(
"/") +
"': " + str(e))
346 if req.status_code >= 300:
350 response = req.json()
351 message = response.get(
"message",
"")
352 colon =
": " if message.strip()
else ""
353 error =
"Request {method} {url} returned {code} {reason}{colon}{message}".format(
354 method=method, url=url,
355 code=response[
"code"],
356 reason=response[
"reason"],
360 except json.JSONDecodeError:
362 error =
"Request {method} {url} returned non JSON response {code}: {content}".format(
363 method=method, url=url,
364 code=req.status_code,
368 if message
is not None:
373 if method !=
"HEAD" and req.status_code != requests.codes.no_content:
376 except json.JSONDecodeError
as e:
377 B2INFO(f
"Invalid response: {req.content}")
379 .format(e, method=method, url=url))
383 """Get a list of all globaltags. Returns a dictionary with the globaltag
384 names and the corresponding ids in the database"""
387 req = self.
requestrequest(
"GET",
"/globalTags")
389 B2ERROR(f
"Could not get the list of globaltags: {e}")
393 for tag
in req.json():
394 result[tag[
"name"]] = tag
399 """Check whether the globaltag with the given name exists."""
402 self.
requestrequest(
"GET",
"/globalTag/{globalTagName}".format(globalTagName=encode_name(name)))
409 """Get the id of the globaltag with the given name. Returns either the
410 id or None if the tag was not found"""
413 req = self.
requestrequest(
"GET",
"/globalTag/{globalTagName}".format(globalTagName=encode_name(name)))
415 B2ERROR(f
"Cannot find globaltag '{name}': {e}")
422 Get the dictionary describing the given globaltag type (currently
423 one of DEV or RELEASE). Returns None if tag type was not found.
426 req = self.
requestrequest(
"GET",
"/globalTagType")
428 B2ERROR(f
"Could not get list of valid globaltag types: {e}")
431 types = {e[
"name"]: e
for e
in req.json()}
436 B2ERROR(
"Unknown globaltag type: '{}', please use one of {}".format(name,
", ".join(types)))
441 Create a new globaltag
443 info = {
"name": name,
"description": description,
"modifiedBy": user,
"isDefault":
False}
445 req = self.
requestrequest(
"POST",
"/globalTag/DEV", f
"Creating globaltag {name}", json=info)
447 B2ERROR(f
"Could not create globaltag {name}: {e}")
454 Return list of all payloads in the given globaltag where each element is
455 a `PayloadInformation` instance
458 gobalTag (str): name of the globaltag
459 exp (int): if given limit the list of payloads to the ones valid for
460 the given exp,run combination
461 run (int): if given limit the list of payloads to the ones valid for
462 the given exp,run combination
463 message (str): additional message to show when downloading the
464 payload information. Will be directly appended to
465 "Obtaining lists of iovs for globaltag {globalTag}"
468 Both, exp and run, need to be given at the same time. Just supplying
469 an experiment or a run number will not work
471 globalTag = encode_name(globalTag)
475 msg = f
"Obtaining list of iovs for globaltag {globalTag}, exp={exp}, run={run}{message}"
476 req = self.
requestrequest(
"GET",
"/iovPayloads", msg, params={
'gtName': globalTag,
'expNumber': exp,
'runNumber': run})
478 msg = f
"Obtaining list of iovs for globaltag {globalTag}{message}"
479 req = self.
requestrequest(
"GET", f
"/globalTag/{globalTag}/globalTagPayloads", msg)
481 for item
in req.json():
482 payload = item[
"payload" if 'payload' in item
else "payloadId"]
483 if "payloadIov" in item:
484 iovs = [item[
'payloadIov']]
486 iovs = item[
'payloadIovs']
489 all_iovs.append(PayloadInformation.from_json(payload, iov))
496 Get a list of all defined payloads (for the given global_tag or by default for all).
497 Returns a dictionary which maps (module, checksum) to the payload id.
502 req = self.
requestrequest(
"GET",
"/globalTag/{global_tag}/payloads"
503 .format(global_tag=encode_name(global_tag)))
505 req = self.
requestrequest(
"GET",
"/payloads")
507 B2ERROR(f
"Cannot get list of payloads: {e}")
511 for payload
in req.json():
512 module = payload[
"basf2Module"][
"name"]
513 checksum = payload[
"checksum"]
514 result[(module, checksum)] = payload[
"payloadId"]
520 Check for the existence of payloads in the database.
523 payloads (list((str,str))): A list of payloads to check for. Each
524 payload needs to be a tuple of the name of the payload and the
525 md5 checksum of the payload file.
526 information (str): The information to be extracted from the
530 A dictionary with the payload identifiers (name, checksum) as keys
531 and the requested information as values for all payloads which are already
532 present in the database.
535 search_query = [{
"name": e[0],
"checksum": e[1]}
for e
in payloads]
537 req = self.
requestrequest(
"POST",
"/checkPayloads", json=search_query)
539 B2ERROR(f
"Cannot check for existing payloads: {e}")
543 for payload
in req.json():
544 module = payload[
"basf2Module"][
"name"]
545 checksum = payload[
"checksum"]
546 result[(module, checksum)] = payload[information]
552 Get the revision numbers of payloads in the database.
555 entries (list): A list of payload entries.
556 Each entry must have the attributes module and checksum.
562 result = self.
check_payloadscheck_payloads([(entry.module, entry.checksum)
for entry
in entries],
"revision")
566 for entry
in entries:
567 entry.revision = result.get((entry.module, entry.checksum), 0)
576 module (str): name of the module
577 filename (str): name of the file
578 checksum (str): md5 hexdigest of the file. Will be calculated automatically if not given
581 checksum = file_checksum(filename)
587 (filename, open(filename,
"rb").read(),
"application/x-root"),
588 (
"json", json.dumps({
"checksum": checksum,
"isDefault":
False}),
"application/json"),
593 for name, contents, mimetype
in files:
594 rf = RequestField(name=name, data=contents)
595 rf.make_multipart(content_type=mimetype)
598 post_body, content_type = encode_multipart_formdata(fields)
599 content_type =
''.join((
'multipart/mixed',) + content_type.partition(
';')[1:])
600 headers = {
'Content-Type': content_type}
605 req = self.
requestrequest(
"POST",
"/package/dbstore/module/{moduleName}/payload"
606 .format(moduleName=encode_name(module)),
607 data=post_body, headers=headers)
609 B2ERROR(f
"Could not create Payload: {e}")
612 return req.json()[
"payloadId"]
614 def create_iov(self, globalTagId, payloadId, firstExp, firstRun, finalExp, finalRun):
619 globalTagId (int): id of the globaltag, obtain with get_globalTagId()
620 payloadId (int): id of the payload, obtain from create_payload() or get_payloads()
621 firstExp (int): first experiment for which this iov is valid
622 firstRun (int): first run for which this iov is valid
623 finalExp (int): final experiment for which this iov is valid
624 finalRun (int): final run for which this iov is valid
627 payloadIovId of the created iov, None if creation was not successful
632 local_variables = locals()
633 variables = {e: int(local_variables[e])
for e
in
634 [
"globalTagId",
"payloadId",
"firstExp",
"firstRun",
"finalExp",
"finalRun"]}
636 B2ERROR(
"create_iov: All parameters need to be integers")
641 req = self.
requestrequest(
"POST",
"/globalTagPayload/{globalTagId},{payloadId}"
642 "/payloadIov/{firstExp},{firstRun},{finalExp},{finalRun}".format(**variables))
644 B2ERROR(f
"Could not create IOV: {e}")
647 return req.json()[
"payloadIovId"]
649 def get_iovs(self, globalTagName, payloadName=None):
650 """Return existing iovs for a given tag name. It returns a dictionary
651 which maps (payloadId, first runId, final runId) to iovId
654 globalTagName(str): Global tag name.
655 payloadName(str): Payload name (if None, selection by name is
660 req = self.
requestrequest(
"GET",
"/globalTag/{globalTagName}/globalTagPayloads"
661 .format(globalTagName=encode_name(globalTagName)))
667 for payload
in req.json():
668 payloadId = payload[
"payloadId"][
"payloadId"]
669 if payloadName
is not None:
670 if payload[
"payloadId"][
"basf2Module"][
"name"] != payloadName:
672 for iov
in payload[
"payloadIovs"]:
673 iovId = iov[
"payloadIovId"]
674 firstExp, firstRun = iov[
"expStart"], iov[
"runStart"]
675 finalExp, finalRun = iov[
"expEnd"], iov[
"runEnd"]
676 result[(payloadId, firstExp, firstRun, finalExp, finalRun)] = iovId
680 def upload(self, filename, global_tag, normalize=False, ignore_existing=False, nprocess=1, uploaded_entries=None):
682 Upload a testing payload storage to the conditions database.
685 filename (str): filename of the testing payload storage file that should be uploaded
686 global_tage (str): name of the globaltag to which the data should be uploaded
687 normalize (Union[bool, str]): if True the payload root files will be normalized to have the same checksum for the
688 same content, if normalize is a string in addition the file name in the root file metadata will be set to it
689 ignore_existing (bool): if True do not upload payloads that already exist
690 nprocess (int): maximal number of parallel uploads
691 uploaded_entries (list): the list of successfully uploaded entries
694 True if the upload was successful
699 B2INFO(f
"Reading payload list from {filename}")
700 entries = parse_testing_payloads_file(filename)
702 B2ERROR(f
"Problems with testing payload storage file {filename}, exiting")
706 B2INFO(f
"No payloads found in {filename}, exiting")
709 B2INFO(f
"Found {len(entries)} iovs to upload")
715 tagId = tagId[
"globalTagId"]
719 entries = sorted(set(reversed(entries)))
722 name = normalize
if normalize
is not True else None
724 e.normalize(name=name)
728 payloads = defaultdict(list)
730 payloads[(e.module, e.checksum)].append(e)
732 existing_payloads = {}
735 def upload_payload(item):
736 """Upload a payload file if necessary but first check list of existing payloads"""
738 if key
in existing_payloads:
739 B2INFO(f
"{key[0]} (md5:{key[1]}) already existing in database, skipping.")
740 payload_id = existing_payloads[key]
743 payload_id = self.
create_payloadcreate_payload(entry.module, entry.filename, entry.checksum)
744 if payload_id
is None:
747 B2INFO(f
"Created new payload {payload_id} for {entry.module} (md5:{entry.checksum})")
749 for entry
in entries:
750 entry.payload = payload_id
755 """Create an iov if necessary but first check the list of existing iovs"""
756 if entry.payload
is None:
759 iov_key = (entry.payload,) + entry.iov_tuple
760 if iov_key
in existing_iovs:
761 entry.iov = existing_iovs[iov_key]
762 B2INFO(f
"IoV {entry.iov_tuple} for {entry.module} (md5:{entry.checksum}) already existing in database, skipping.")
764 entry.payloadIovId = self.
create_iovcreate_iov(tagId, entry.payload, *entry.iov_tuple)
765 if entry.payloadIovId
is None:
768 B2INFO(f
"Created IoV {entry.iov_tuple} for {entry.module} (md5:{entry.checksum})")
773 with ThreadPoolExecutor(max_workers=nprocess)
as pool:
776 if not ignore_existing:
777 B2INFO(
"Downloading information about existing payloads and iovs...")
780 existing_payloads = {}
782 def create_future(iter, func, callback=None):
783 fn = pool.submit(iter, func)
784 if callback
is not None:
785 fn.add_done_callback(callback)
788 def update_iovs(iovs):
789 existing_iovs.update(iovs.result())
790 B2INFO(f
"Found {len(existing_iovs)} existing iovs in {global_tag}")
792 def update_payloads(payloads):
793 existing_payloads.update(payloads.result())
794 B2INFO(f
"Found {len(existing_payloads)} existing payloads")
796 create_future(self.
get_iovsget_iovs, global_tag, update_iovs)
798 for chunk
in chunks(payloads.keys(), 1000):
799 create_future(self.
check_payloadscheck_payloads, chunk, update_payloads)
801 futures_wait(futures)
804 failed_payloads = sum(0
if result
else 1
for result
in pool.map(upload_payload, payloads.items()))
805 if failed_payloads > 0:
806 B2ERROR(f
"{failed_payloads} payloads could not be uploaded")
810 for entry
in pool.map(create_iov, entries):
812 if uploaded_entries
is not None:
813 uploaded_entries.append(entry)
817 B2ERROR(f
"{failed_iovs} IoVs could not be created")
820 if uploaded_entries
is not None:
823 return failed_payloads + failed_iovs == 0
827 Upload a testing payload storage to a staging globaltag and create or update a jira issue
830 filename (str): filename of the testing payload storage file that should be uploaded
831 normalize (Union[bool, str]): if True the payload root files will be
832 normalized to have the same checksum for the same content, if
833 normalize is a string in addition the file name in the root file
834 metadata will be set to it
835 data (dict): a dictionary with the information provided by the user:
837 * task: category of globaltag, either main, online, prompt, data, mc, or analysis
838 * tag: the globaltag name
839 * request: type of request, either Update, New, or Modification. The latter two imply task == main because
840 if new payload classes are introduced or payload classes are modified then they will first be included in
841 the main globaltag. Here a synchronization of code and payload changes has to be managed.
842 If new or modified payload classes should be included in other globaltags they must already be in a release.
843 * pull-request: number of the pull request containing new or modified payload classes,
844 only for request == New or Modified
845 * backward-compatibility: description of what happens if the old payload is encountered by the updated code,
846 only for request == Modified
847 * forward-compatibility: description of what happens if a new payload is encountered by the existing code,
848 only for request == Modified
849 * release: the required release version
850 * reason: the reason for the request
851 * description: a detailed description for the globaltag manager
852 * issue: identifier of an existing jira issue (optional)
853 * user: name of the user
854 * time: time stamp of the request
856 password: the password for access to jira or the access token and secret for oauth access
859 True if the upload and jira issue creation/upload was successful
863 data[
'tag'] = upload_global_tag(data[
'task'])
864 if data[
'tag']
is None:
865 data[
'tag'] = f
"temp_{data['task']}_{data['user']}_{data['time']}"
869 if not self.
create_globalTagcreate_globalTag(data[
'tag'], data[
'reason'], data[
'user']):
873 B2INFO(f
"Uploading testing database {filename} to globaltag {data['tag']}")
875 if not self.
uploadupload(filename, data[
'tag'], normalize, uploaded_entries=entries):
880 issue = data[
'issue']
882 issue = jira_global_tag_v2(data[
'task'])
884 issue = {
"components": [{
"name":
"globaltag"}]}
887 if type(issue)
is tuple:
888 description = issue[1].format(**data)
892 |*Upload globaltag* | {data['tag']} |
893 |*Request reason* | {data['reason']} |
894 |*Required release* | {data['release']} |
895 |*Type of request* | {data['request']} |
897 if 'pull-request' in data.keys():
898 description += f
"|*Pull request* | \\#{data['pull-request']} |\n"
899 if 'backward-compatibility' in data.keys():
900 description += f
"|*Backward compatibility* | \\#{data['backward-compatibility']} |\n"
901 if 'forward-compatibility' in data.keys():
902 description += f
"|*Forward compatibility* | \\#{data['forward-compatibility']} |\n"
903 description +=
'|*Details* |' +
''.join(data[
'details']) +
' |\n'
904 if data[
'task'] ==
'online':
905 description +=
'|*Impact on data taking*|' +
''.join(data[
'data_taking']) +
' |\n'
908 description +=
'\nPayloads\n||Name||Revision||IoV||\n'
909 for entry
in entries:
910 description += f
"|{entry.module} | {entry.revision} | ({entry.iov_str()}) |\n"
913 if type(issue)
is dict:
914 issue[
"description"] = description
915 if "summary" in issue.keys():
916 issue[
"summary"] = issue[
"summary"].format(**data)
918 issue[
"summary"] = f
"Globaltag request for {data['task']} by {data['user']} at {data['time']}"
919 if "project" not in issue.keys():
920 issue[
"project"] = {
"key":
"BII"}
921 if "issuetype" not in issue.keys():
922 issue[
"issuetype"] = {
"name":
"Task"}
923 if data[
"task"] ==
"main":
924 issue[
"labels"] = [
"TUPPR"]
926 B2INFO(f
"Creating jira issue for {data['task']} globaltag request")
927 if isinstance(password, str):
928 response = requests.post(
'https://agira.desy.de/rest/api/latest/issue', auth=(data[
'user'], password),
929 json={
'fields': issue})
931 fields = {
'issue': json.dumps(issue)}
932 if 'user' in data.keys():
933 fields[
'user'] = data[
'user']
935 fields[
'token'] = password[0]
936 fields[
'secret'] = password[1]
937 response = requests.post(
'https://b2-master.belle2.org/cgi-bin/jira_issue.py', data=fields)
938 if response.status_code
in range(200, 210):
939 B2INFO(f
"Issue successfully created: https://agira.desy.de/browse/{response.json()['key']}")
941 B2ERROR(
'The creation of the issue failed: ' + requests.status_codes._codes[response.status_code][0])
948 new_issue_config = jira_global_tag_v2(data[
'task'])
949 if isinstance(new_issue_config, dict)
and "assignee" in new_issue_config:
950 user = new_issue_config[
'assignee'].get(
'name',
None)
951 if user
is not None and isinstance(password, str):
952 response = requests.post(f
'https://agira.desy.de/rest/api/latest/issue/{issue}/watchers',
953 auth=(data[
'user'], password), json=user)
954 if response.status_code
in range(200, 210):
955 B2INFO(f
"Added {user} as watcher to {issue}")
957 B2WARNING(f
"Could not add {user} as watcher to {issue}: {response.status_code}")
959 B2INFO(f
"Commenting on jira issue {issue} for {data['task']} globaltag request")
960 if isinstance(password, str):
961 response = requests.post(
'https://agira.desy.de/rest/api/latest/issue/%s/comment' % issue,
962 auth=(data[
'user'], password), json={
'body': description})
964 fields = {
'id': issue,
'user': user,
'comment': description}
966 fields[
'token'] = password[0]
967 fields[
'secret'] = password[1]
968 response = requests.post(
'https://b2-master.belle2.org/cgi-bin/jira_issue.py', data=fields)
969 if response.status_code
in range(200, 210):
970 B2INFO(f
"Issue successfully updated: https://agira.desy.de/browse/{issue}")
972 B2ERROR(
'The commenting of the issue failed: ' + requests.status_codes._codes[response.status_code][0])
978 def require_database_for_test(timeout=60, base_url=None):
979 """Make sure that the database is available and skip the test if not.
981 This function should be called in test scripts if they are expected to fail
982 if the database is down. It either returns when the database is ok or it
983 will signal test_basf2 that the test should be skipped and exit
986 if os.environ.get(
"BELLE2_CONDB_GLOBALTAG",
None) ==
"":
987 raise Exception(
"Access to the Database is disabled")
988 base_url_list = ConditionsDB.get_base_urls(base_url)
989 for url
in base_url_list:
991 req = requests.request(
"HEAD", url.rstrip(
'/') +
"/v2/globalTags")
992 req.raise_for_status()
993 except requests.RequestException
as e:
994 B2WARNING(f
"Problem connecting to {url}:\n {e}\n Trying next server ...")
998 print(
"TEST SKIPPED: No working database servers configured, giving up", file=sys.stderr)
1002 def enable_debugging():
1003 """Enable verbose output of python-requests to be able to debug http connections"""
1007 import http.client
as http_client
1009 http_client.HTTPConnection.debuglevel = 1
1011 logging.basicConfig()
1012 logging.getLogger().setLevel(logging.DEBUG)
1013 requests_log = logging.getLogger(
"requests.packages.urllib3")
1014 requests_log.setLevel(logging.DEBUG)
1015 requests_log.propagate =
True
_authtoken
Authorization header to send with each request.
def __init__(self, token)
def get_payloads(self, global_tag=None)
def get_iovs(self, globalTagName, payloadName=None)
def upload(self, filename, global_tag, normalize=False, ignore_existing=False, nprocess=1, uploaded_entries=None)
def set_authentication_token(self, token)
def get_globalTagType(self, name)
def check_payloads(self, payloads, information="payloadId")
def has_globalTag(self, name)
def create_iov(self, globalTagId, payloadId, firstExp, firstRun, finalExp, finalRun)
def request(self, method, url, message=None, *args, **argk)
def __init__(self, base_url=None, max_connections=10, retries=3)
def get_revisions(self, entries)
def create_globalTag(self, name, description, user)
def get_globalTagInfo(self, name)
_session
session object to get keep-alive support and connection pooling
def get_all_iovs(self, globalTag, exp=None, run=None, message=None)
def get_base_urls(given_url)
def staging_request(self, filename, normalize, data, password)
def create_payload(self, module, filename, checksum=None)
_base_url
base url to be prepended to all requests