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
36 def encode_name(name):
37 """Escape name to be used in an url"""
38 return urllib.parse.quote(name, safe=
"")
41 def file_checksum(filename):
42 """Calculate md5 hash of file"""
43 md5hash = hashlib.md5()
44 with open(filename,
"rb")
as data:
45 md5hash.update(data.read())
46 return md5hash.hexdigest()
49 def chunks(container, chunk_size):
50 """Cut a container in chunks of max. chunk_size"""
53 chunk = tuple(itertools.islice(it, chunk_size))
59 def get_cdb_authentication_token(path=None):
61 Helper function for correctly retrieving the CDB authentication token (either via file or via issuing server).
63 :param path: Path to a file containing a CDB authentication token; if None, the function will use
64 a default path (``${HOME}/b2cdb_${BELLE2_USER}.token`` or ``/tmp/b2cdb_${BELLE2_USER}.token``) to look for a token.
70 path_to_token = os.path.join(os.getenv(
'HOME', tempfile.gettempdir()), f
'b2cdb_{os.getenv("BELLE2_USER", None)}.token')
73 if os.path.isfile(path_to_token):
74 with open(path_to_token)
as token_file:
75 response = requests.get(
'https://token.belle2.org/check', verify=
False, params={
'token': token_file.read().strip()})
76 if response.status_code == 400:
77 B2INFO(f
'The file {path_to_token} contains an invalid token, getting a new token...')
78 os.unlink(path_to_token)
79 elif response.status_code > 400:
80 B2WARNING(
"The validity of the existing token could not be checked. Trying to connect to CDB anyway.")
83 if not os.path.isfile(path_to_token):
84 username = os.environ[
'BELLE2_USER']
85 othername = input(f
'\nConfirm your DESY user name by pressing enter or type the correct one [{username}]: ')
88 password = getpass.getpass(
"Please type your DESY password to request a CDB token: ")
89 response = requests.get(
'https://token.belle2.org', verify=
False, auth=(username, password))
90 print(response.content)
91 if response.status_code == requests.codes.ok:
92 with open(path_to_token,
'wb')
as token_file:
93 os.chmod(path_to_token, stat.S_IRUSR | stat.S_IWUSR)
94 token_file.write(response.content)
96 B2ERROR(
'Failed to get token')
100 with open(path_to_token)
as token_file:
101 return token_file.read().strip()
104 def set_cdb_authentication_token(cdb_instance, auth_token=None):
106 Helper function for setting the CDB authentication token.
108 :param cdb_instance: An instance of the ``ConditionsDB`` class.
109 :param auth_token: A CDB authentication token: if ``None``, it is automatically retrieved from the issuing server
112 if auth_token
is not None:
113 cdb_instance.set_authentication_token(auth_token)
116 token = get_cdb_authentication_token(os.getenv(
'BELLE2_CDB_AUTH_TOKEN', default=
None))
117 cdb_instance.set_authentication_token(token)
121 """Simple class to present bearer token instead of username/password"""
124 """Construct from a token"""
129 """Update headers to include token"""
130 r.headers[
"X-Authorization"] = self.
_authtoken_authtoken
135 """Small container class to help compare payload information for efficient
136 comparison between globaltags"""
140 """Set all internal members from the json information of the payload and the iov.
143 payload (dict): json information of the payload as returned by REST api
144 iov (dict): json information of the iov as returned by REST api
147 iov = {
"payloadIovId":
None,
"expStart":
None,
"runStart":
None,
"expEnd":
None,
"runEnd":
None}
150 payload[
'payloadId'],
151 payload[
'basf2Module'][
'name'],
154 payload[
'payloadUrl'],
157 (iov[
"expStart"], iov[
"runStart"], iov[
"expEnd"], iov[
"runEnd"]),
160 def __init__(self, payload_id, name, revision, checksum, payload_url, base_url, iov_id=None, iov=None):
162 Create a new object from the given information
183 """Return the full url to the payload on the server"""
187 """Make object hashable"""
191 """Check if two payloads are equal"""
192 return (self.
namename, self.
checksumchecksum, self.
ioviov) == (other.name, other.checksum, other.iov)
195 """Sort payloads by name, iov, revision"""
196 return (self.
namename.lower(), self.
ioviov, self.
revisionrevision) < (other.name.lower(), other.iov, other.revision)
199 """return a human readable name for the IoV"""
200 if self.
ioviov
is None:
203 if self.
ioviov == (0, 0, -1, -1):
206 e1, r1, e2, r2 = self.
ioviov
208 if r1 == 0
and r2 == -1:
211 return f
"exp {e1}, runs {r1}+"
213 return f
"exp {e1}, run {r1}"
215 return f
"exp {e1}, runs {r1} - {r2}"
217 if e2 == -1
and r1 == 0:
218 return f
"exp {e1} - forever"
220 return f
"exp {e1}, run {r1} - forever"
221 elif r1 == 0
and r2 == -1:
222 return f
"exp {e1}-{e2}, all runs"
224 return f
"exp {e1}, run {r1} - exp {e2}, all runs"
226 return f
"exp {e1}, run {r1} - exp {e2}, run {r2}"
230 """Class to interface conditions db REST interface"""
233 BASE_URLS = [conditions.default_metadata_provider_url]
236 """Class to be thrown by request() if there is any error"""
241 """Resolve the list of server urls. If a url is given just return it.
242 Otherwise return servers listed in BELLE2_CONDB_SERVERLIST or the
246 given_url (str): Explicit base_url. If this is not None it will be
247 returned as is in a list of length 1
250 a list of urls to try for database connectivity
253 base_url_list = ConditionsDB.BASE_URLS[:]
254 base_url_env = os.environ.get(
"BELLE2_CONDB_SERVERLIST",
None)
255 if given_url
is not None:
256 base_url_list = [given_url]
257 elif base_url_env
is not None:
258 base_url_list = base_url_env.split()
259 B2INFO(
"Getting Conditions Database servers from Environment:")
260 for i, url
in enumerate(base_url_list, 1):
261 B2INFO(f
" {i}. {url}")
264 for url
in base_url_list:
265 if url.startswith(
"http://"):
266 full_list.append(
"https" + url[4:])
268 full_list.append(url)
271 def __init__(self, base_url=None, max_connections=10, retries=3):
273 Create a new instance of the interface
276 base_url (string): base url of the rest interface
277 max_connections (int): number of connections to keep open, mostly useful for threaded applications
278 retries (int): number of retries in case of connection problems
284 adapter = requests.adapters.HTTPAdapter(
285 pool_connections=max_connections, pool_maxsize=max_connections,
286 max_retries=retries, pool_block=
True
288 self.
_session_session.mount(
"http://", adapter)
289 self.
_session_session.mount(
"https://", adapter)
291 if "BELLE2_CONDB_PROXY" in os.environ:
293 "http": os.environ.get(
"BELLE2_CONDB_PROXY"),
294 "https": os.environ.get(
"BELLE2_CONDB_PROXY"),
297 base_url_list = ConditionsDB.get_base_urls(base_url)
299 for url
in base_url_list:
304 req.raise_for_status()
305 except requests.RequestException
as e:
306 B2WARNING(f
"Problem connecting to {url}:\n {e}\n Trying next server ...")
310 B2FATAL(
"No working database servers configured, giving up")
316 self.
_session_session.headers.update({
"Accept":
"application/json",
"Cache-Control":
"no-cache"})
320 Set authentication token when talking to the database
323 token (str): JWT to hand to the database. Will not be checked
328 def request(self, method, url, message=None, *args, **argk):
330 Request function, similar to requests.request but adding the base_url
333 method (str): GET, POST, etc.
334 url (str): url for the request, base_url will be prepended
335 message (str): message to show when starting the request and if it fails
337 All other arguments will be forwarded to requests.request.
339 if message
is not None:
344 except requests.exceptions.ConnectionError
as e:
345 B2FATAL(
"Could not access '" + self.
_base_url_base_url + url.lstrip(
"/") +
"': " + str(e))
347 if req.status_code >= 300:
351 response = req.json()
352 message = response.get(
"message",
"")
353 colon =
": " if message.strip()
else ""
354 error =
"Request {method} {url} returned {code} {reason}{colon}{message}".format(
355 method=method, url=url,
356 code=response[
"code"],
357 reason=response[
"reason"],
361 except json.JSONDecodeError:
363 error =
"Request {method} {url} returned non JSON response {code}: {content}".format(
364 method=method, url=url,
365 code=req.status_code,
369 if message
is not None:
374 if method !=
"HEAD" and req.status_code != requests.codes.no_content:
377 except json.JSONDecodeError
as e:
378 B2INFO(f
"Invalid response: {req.content}")
380 .format(e, method=method, url=url))
384 """Get a list of all globaltags. Returns a dictionary with the globaltag
385 names and the corresponding ids in the database"""
388 req = self.
requestrequest(
"GET",
"/globalTags")
390 B2ERROR(f
"Could not get the list of globaltags: {e}")
394 for tag
in req.json():
395 result[tag[
"name"]] = tag
400 """Check whether the globaltag with the given name exists."""
403 self.
requestrequest(
"GET",
"/globalTag/{globalTagName}".format(globalTagName=encode_name(name)))
410 """Get the id of the globaltag with the given name. Returns either the
411 id or None if the tag was not found"""
414 req = self.
requestrequest(
"GET",
"/globalTag/{globalTagName}".format(globalTagName=encode_name(name)))
416 B2ERROR(f
"Cannot find globaltag '{name}': {e}")
423 Get the dictionary describing the given globaltag type (currently
424 one of DEV or RELEASE). Returns None if tag type was not found.
427 req = self.
requestrequest(
"GET",
"/globalTagType")
429 B2ERROR(f
"Could not get list of valid globaltag types: {e}")
432 types = {e[
"name"]: e
for e
in req.json()}
437 B2ERROR(
"Unknown globaltag type: '{}', please use one of {}".format(name,
", ".join(types)))
442 Create a new globaltag
444 info = {
"name": name,
"description": description,
"modifiedBy": user,
"isDefault":
False}
446 req = self.
requestrequest(
"POST",
"/globalTag/DEV", f
"Creating globaltag {name}", json=info)
448 B2ERROR(f
"Could not create globaltag {name}: {e}")
455 Return list of all payloads in the given globaltag where each element is
456 a `PayloadInformation` instance
459 gobalTag (str): name of the globaltag
460 exp (int): if given limit the list of payloads to the ones valid for
461 the given exp,run combination
462 run (int): if given limit the list of payloads to the ones valid for
463 the given exp,run combination
464 message (str): additional message to show when downloading the
465 payload information. Will be directly appended to
466 "Obtaining lists of iovs for globaltag {globalTag}"
469 Both, exp and run, need to be given at the same time. Just supplying
470 an experiment or a run number will not work
472 globalTag = encode_name(globalTag)
476 msg = f
"Obtaining list of iovs for globaltag {globalTag}, exp={exp}, run={run}{message}"
477 req = self.
requestrequest(
"GET",
"/iovPayloads", msg, params={
'gtName': globalTag,
'expNumber': exp,
'runNumber': run})
479 msg = f
"Obtaining list of iovs for globaltag {globalTag}{message}"
480 req = self.
requestrequest(
"GET", f
"/globalTag/{globalTag}/globalTagPayloads", msg)
482 for item
in req.json():
483 payload = item[
"payload" if 'payload' in item
else "payloadId"]
484 if "payloadIov" in item:
485 iovs = [item[
'payloadIov']]
487 iovs = item[
'payloadIovs']
490 all_iovs.append(PayloadInformation.from_json(payload, iov))
497 Get a list of all defined payloads (for the given global_tag or by default for all).
498 Returns a dictionary which maps (module, checksum) to the payload id.
503 req = self.
requestrequest(
"GET",
"/globalTag/{global_tag}/payloads"
504 .format(global_tag=encode_name(global_tag)))
506 req = self.
requestrequest(
"GET",
"/payloads")
508 B2ERROR(f
"Cannot get list of payloads: {e}")
512 for payload
in req.json():
513 module = payload[
"basf2Module"][
"name"]
514 checksum = payload[
"checksum"]
515 result[(module, checksum)] = payload[
"payloadId"]
521 Check for the existence of payloads in the database.
524 payloads (list((str,str))): A list of payloads to check for. Each
525 payload needs to be a tuple of the name of the payload and the
526 md5 checksum of the payload file.
527 information (str): The information to be extracted from the
531 A dictionary with the payload identifiers (name, checksum) as keys
532 and the requested information as values for all payloads which are already
533 present in the database.
536 search_query = [{
"name": e[0],
"checksum": e[1]}
for e
in payloads]
538 req = self.
requestrequest(
"POST",
"/checkPayloads", json=search_query)
540 B2ERROR(f
"Cannot check for existing payloads: {e}")
544 for payload
in req.json():
545 module = payload[
"basf2Module"][
"name"]
546 checksum = payload[
"checksum"]
547 result[(module, checksum)] = payload[information]
553 Get the revision numbers of payloads in the database.
556 entries (list): A list of payload entries.
557 Each entry must have the attributes module and checksum.
563 result = self.
check_payloadscheck_payloads([(entry.module, entry.checksum)
for entry
in entries],
"revision")
567 for entry
in entries:
568 entry.revision = result.get((entry.module, entry.checksum), 0)
577 module (str): name of the module
578 filename (str): name of the file
579 checksum (str): md5 hexdigest of the file. Will be calculated automatically if not given
582 checksum = file_checksum(filename)
588 (filename, open(filename,
"rb").read(),
"application/x-root"),
589 (
"json", json.dumps({
"checksum": checksum,
"isDefault":
False}),
"application/json"),
594 for name, contents, mimetype
in files:
595 rf = RequestField(name=name, data=contents)
596 rf.make_multipart(content_type=mimetype)
599 post_body, content_type = encode_multipart_formdata(fields)
600 content_type =
''.join((
'multipart/mixed',) + content_type.partition(
';')[1:])
601 headers = {
'Content-Type': content_type}
606 req = self.
requestrequest(
"POST",
"/package/dbstore/module/{moduleName}/payload"
607 .format(moduleName=encode_name(module)),
608 data=post_body, headers=headers)
610 B2ERROR(f
"Could not create Payload: {e}")
613 return req.json()[
"payloadId"]
615 def create_iov(self, globalTagId, payloadId, firstExp, firstRun, finalExp, finalRun):
620 globalTagId (int): id of the globaltag, obtain with get_globalTagId()
621 payloadId (int): id of the payload, obtain from create_payload() or get_payloads()
622 firstExp (int): first experiment for which this iov is valid
623 firstRun (int): first run for which this iov is valid
624 finalExp (int): final experiment for which this iov is valid
625 finalRun (int): final run for which this iov is valid
628 payloadIovId of the created iov, None if creation was not successful
633 local_variables = locals()
634 variables = {e: int(local_variables[e])
for e
in
635 [
"globalTagId",
"payloadId",
"firstExp",
"firstRun",
"finalExp",
"finalRun"]}
637 B2ERROR(
"create_iov: All parameters need to be integers")
642 req = self.
requestrequest(
"POST",
"/globalTagPayload/{globalTagId},{payloadId}"
643 "/payloadIov/{firstExp},{firstRun},{finalExp},{finalRun}".format(**variables))
645 B2ERROR(f
"Could not create IOV: {e}")
648 return req.json()[
"payloadIovId"]
650 def get_iovs(self, globalTagName, payloadName=None):
651 """Return existing iovs for a given tag name. It returns a dictionary
652 which maps (payloadId, first runId, final runId) to iovId
655 globalTagName(str): Global tag name.
656 payloadName(str): Payload name (if None, selection by name is
661 req = self.
requestrequest(
"GET",
"/globalTag/{globalTagName}/globalTagPayloads"
662 .format(globalTagName=encode_name(globalTagName)))
668 for payload
in req.json():
669 payloadId = payload[
"payloadId"][
"payloadId"]
670 if payloadName
is not None:
671 if payload[
"payloadId"][
"basf2Module"][
"name"] != payloadName:
673 for iov
in payload[
"payloadIovs"]:
674 iovId = iov[
"payloadIovId"]
675 firstExp, firstRun = iov[
"expStart"], iov[
"runStart"]
676 finalExp, finalRun = iov[
"expEnd"], iov[
"runEnd"]
677 result[(payloadId, firstExp, firstRun, finalExp, finalRun)] = iovId
681 def upload(self, filename, global_tag, normalize=False, ignore_existing=False, nprocess=1, uploaded_entries=None):
683 Upload a testing payload storage to the conditions database.
686 filename (str): filename of the testing payload storage file that should be uploaded
687 global_tage (str): name of the globaltag to which the data should be uploaded
688 normalize (Union[bool, str]): if True the payload root files will be normalized to have the same checksum for the
689 same content, if normalize is a string in addition the file name in the root file metadata will be set to it
690 ignore_existing (bool): if True do not upload payloads that already exist
691 nprocess (int): maximal number of parallel uploads
692 uploaded_entries (list): the list of successfully uploaded entries
695 True if the upload was successful
700 B2INFO(f
"Reading payload list from {filename}")
701 entries = parse_testing_payloads_file(filename)
703 B2ERROR(f
"Problems with testing payload storage file {filename}, exiting")
707 B2INFO(f
"No payloads found in {filename}, exiting")
710 B2INFO(f
"Found {len(entries)} iovs to upload")
716 tagId = tagId[
"globalTagId"]
720 entries = sorted(set(reversed(entries)))
723 name = normalize
if normalize
is not True else None
725 e.normalize(name=name)
729 payloads = defaultdict(list)
731 payloads[(e.module, e.checksum)].append(e)
733 existing_payloads = {}
736 def upload_payload(item):
737 """Upload a payload file if necessary but first check list of existing payloads"""
739 if key
in existing_payloads:
740 B2INFO(f
"{key[0]} (md5:{key[1]}) already existing in database, skipping.")
741 payload_id = existing_payloads[key]
744 payload_id = self.
create_payloadcreate_payload(entry.module, entry.filename, entry.checksum)
745 if payload_id
is None:
748 B2INFO(f
"Created new payload {payload_id} for {entry.module} (md5:{entry.checksum})")
750 for entry
in entries:
751 entry.payload = payload_id
756 """Create an iov if necessary but first check the list of existing iovs"""
757 if entry.payload
is None:
760 iov_key = (entry.payload,) + entry.iov_tuple
761 if iov_key
in existing_iovs:
762 entry.iov = existing_iovs[iov_key]
763 B2INFO(f
"IoV {entry.iov_tuple} for {entry.module} (md5:{entry.checksum}) already existing in database, skipping.")
765 entry.payloadIovId = self.
create_iovcreate_iov(tagId, entry.payload, *entry.iov_tuple)
766 if entry.payloadIovId
is None:
769 B2INFO(f
"Created IoV {entry.iov_tuple} for {entry.module} (md5:{entry.checksum})")
774 with ThreadPoolExecutor(max_workers=nprocess)
as pool:
777 if not ignore_existing:
778 B2INFO(
"Downloading information about existing payloads and iovs...")
781 existing_payloads = {}
783 def create_future(iter, func, callback=None):
784 fn = pool.submit(iter, func)
785 if callback
is not None:
786 fn.add_done_callback(callback)
789 def update_iovs(iovs):
790 existing_iovs.update(iovs.result())
791 B2INFO(f
"Found {len(existing_iovs)} existing iovs in {global_tag}")
793 def update_payloads(payloads):
794 existing_payloads.update(payloads.result())
795 B2INFO(f
"Found {len(existing_payloads)} existing payloads")
797 create_future(self.
get_iovsget_iovs, global_tag, update_iovs)
799 for chunk
in chunks(payloads.keys(), 1000):
800 create_future(self.
check_payloadscheck_payloads, chunk, update_payloads)
802 futures_wait(futures)
805 failed_payloads = sum(0
if result
else 1
for result
in pool.map(upload_payload, payloads.items()))
806 if failed_payloads > 0:
807 B2ERROR(f
"{failed_payloads} payloads could not be uploaded")
811 for entry
in pool.map(create_iov, entries):
813 if uploaded_entries
is not None:
814 uploaded_entries.append(entry)
818 B2ERROR(f
"{failed_iovs} IoVs could not be created")
821 if uploaded_entries
is not None:
824 return failed_payloads + failed_iovs == 0
828 Upload a testing payload storage to a staging globaltag and create or update a jira issue
831 filename (str): filename of the testing payload storage file that should be uploaded
832 normalize (Union[bool, str]): if True the payload root files will be
833 normalized to have the same checksum for the same content, if
834 normalize is a string in addition the file name in the root file
835 metadata will be set to it
836 data (dict): a dictionary with the information provided by the user:
838 * task: category of globaltag, either main, online, prompt, data, mc, or analysis
839 * tag: the globaltag name
840 * request: type of request, either Update, New, or Modification. The latter two imply task == main because
841 if new payload classes are introduced or payload classes are modified then they will first be included in
842 the main globaltag. Here a synchronization of code and payload changes has to be managed.
843 If new or modified payload classes should be included in other globaltags they must already be in a release.
844 * pull-request: number of the pull request containing new or modified payload classes,
845 only for request == New or Modified
846 * backward-compatibility: description of what happens if the old payload is encountered by the updated code,
847 only for request == Modified
848 * forward-compatibility: description of what happens if a new payload is encountered by the existing code,
849 only for request == Modified
850 * release: the required release version
851 * reason: the reason for the request
852 * description: a detailed description for the globaltag manager
853 * issue: identifier of an existing jira issue (optional)
854 * user: name of the user
855 * time: time stamp of the request
857 password: the password for access to jira or the access token and secret for oauth access
860 True if the upload and jira issue creation/upload was successful
864 data[
'tag'] = upload_global_tag(data[
'task'])
865 if data[
'tag']
is None:
866 data[
'tag'] = f
"temp_{data['task']}_{data['user']}_{data['time']}"
870 if not self.
create_globalTagcreate_globalTag(data[
'tag'], data[
'reason'], data[
'user']):
874 B2INFO(f
"Uploading testing database {filename} to globaltag {data['tag']}")
876 if not self.
uploadupload(filename, data[
'tag'], normalize, uploaded_entries=entries):
881 issue = data[
'issue']
883 issue = jira_global_tag_v2(data[
'task'])
885 issue = {
"components": [{
"name":
"globaltag"}]}
888 if type(issue)
is tuple:
889 description = issue[1].format(**data)
893 |*Upload globaltag* | {data['tag']} |
894 |*Request reason* | {data['reason']} |
895 |*Required release* | {data['release']} |
896 |*Type of request* | {data['request']} |
898 if 'pull-request' in data.keys():
899 description += f
"|*Pull request* | \\#{data['pull-request']} |\n"
900 if 'backward-compatibility' in data.keys():
901 description += f
"|*Backward compatibility* | \\#{data['backward-compatibility']} |\n"
902 if 'forward-compatibility' in data.keys():
903 description += f
"|*Forward compatibility* | \\#{data['forward-compatibility']} |\n"
904 description +=
'|*Details* |' +
''.join(data[
'details']) +
' |\n'
905 if data[
'task'] ==
'online':
906 description +=
'|*Impact on data taking*|' +
''.join(data[
'data_taking']) +
' |\n'
909 description +=
'\nPayloads\n||Name||Revision||IoV||\n'
910 for entry
in entries:
911 description += f
"|{entry.module} | {entry.revision} | ({entry.iov_str()}) |\n"
914 if type(issue)
is dict:
915 issue[
"description"] = description
916 if "summary" in issue.keys():
917 issue[
"summary"] = issue[
"summary"].format(**data)
919 issue[
"summary"] = f
"Globaltag request for {data['task']} by {data['user']} at {data['time']}"
920 if "project" not in issue.keys():
921 issue[
"project"] = {
"key":
"BII"}
922 if "issuetype" not in issue.keys():
923 issue[
"issuetype"] = {
"name":
"Task"}
924 if data[
"task"] ==
"main":
925 issue[
"labels"] = [
"TUPPR"]
927 B2INFO(f
"Creating jira issue for {data['task']} globaltag request")
928 if isinstance(password, str):
929 response = requests.post(
'https://agira.desy.de/rest/api/latest/issue', auth=(data[
'user'], password),
930 json={
'fields': issue})
932 fields = {
'issue': json.dumps(issue)}
933 if 'user' in data.keys():
934 fields[
'user'] = data[
'user']
936 fields[
'token'] = password[0]
937 fields[
'secret'] = password[1]
938 response = requests.post(
'https://b2-master.belle2.org/cgi-bin/jira_issue.py', data=fields)
939 if response.status_code
in range(200, 210):
940 B2INFO(f
"Issue successfully created: https://agira.desy.de/browse/{response.json()['key']}")
942 B2ERROR(
'The creation of the issue failed: ' + requests.status_codes._codes[response.status_code][0])
949 new_issue_config = jira_global_tag_v2(data[
'task'])
950 if isinstance(new_issue_config, dict)
and "assignee" in new_issue_config:
951 user = new_issue_config[
'assignee'].get(
'name',
None)
952 if user
is not None and isinstance(password, str):
953 response = requests.post(f
'https://agira.desy.de/rest/api/latest/issue/{issue}/watchers',
954 auth=(data[
'user'], password), json=user)
955 if response.status_code
in range(200, 210):
956 B2INFO(f
"Added {user} as watcher to {issue}")
958 B2WARNING(f
"Could not add {user} as watcher to {issue}: {response.status_code}")
960 B2INFO(f
"Commenting on jira issue {issue} for {data['task']} globaltag request")
961 if isinstance(password, str):
962 response = requests.post(
'https://agira.desy.de/rest/api/latest/issue/%s/comment' % issue,
963 auth=(data[
'user'], password), json={
'body': description})
965 fields = {
'id': issue,
'user': user,
'comment': description}
967 fields[
'token'] = password[0]
968 fields[
'secret'] = password[1]
969 response = requests.post(
'https://b2-master.belle2.org/cgi-bin/jira_issue.py', data=fields)
970 if response.status_code
in range(200, 210):
971 B2INFO(f
"Issue successfully updated: https://agira.desy.de/browse/{issue}")
973 B2ERROR(
'The commenting of the issue failed: ' + requests.status_codes._codes[response.status_code][0])
979 def require_database_for_test(timeout=60, base_url=None):
980 """Make sure that the database is available and skip the test if not.
982 This function should be called in test scripts if they are expected to fail
983 if the database is down. It either returns when the database is ok or it
984 will signal test_basf2 that the test should be skipped and exit
987 if os.environ.get(
"BELLE2_CONDB_GLOBALTAG",
None) ==
"":
988 raise Exception(
"Access to the Database is disabled")
989 base_url_list = ConditionsDB.get_base_urls(base_url)
990 for url
in base_url_list:
992 req = requests.request(
"HEAD", url.rstrip(
'/') +
"/v2/globalTags")
993 req.raise_for_status()
994 except requests.RequestException
as e:
995 B2WARNING(f
"Problem connecting to {url}:\n {e}\n Trying next server ...")
999 print(
"TEST SKIPPED: No working database servers configured, giving up", file=sys.stderr)
1003 def enable_debugging():
1004 """Enable verbose output of python-requests to be able to debug http connections"""
1008 import http.client
as http_client
1010 http_client.HTTPConnection.debuglevel = 1
1012 logging.basicConfig()
1013 logging.getLogger().setLevel(logging.DEBUG)
1014 requests_log = logging.getLogger(
"requests.packages.urllib3")
1015 requests_log.setLevel(logging.DEBUG)
1016 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