15Python interface to the ConditionsDB
20from basf2
import B2FATAL, B2ERROR, B2INFO, B2WARNING, conditions
22from requests.packages.urllib3.fields
import RequestField
23from requests.packages.urllib3.filepost
import encode_multipart_formdata
26from versioning
import upload_global_tag, jira_global_tag_v2
27from collections
import defaultdict
28from concurrent.futures
import ThreadPoolExecutor, wait
as futures_wait
31from typing
import Union
38 """Escape name to be used in an url"""
39 return urllib.parse.quote(name, safe=
"")
42def file_checksum(filename):
43 """Calculate md5 hash of file"""
44 md5hash = hashlib.md5()
45 with open(filename,
"rb")
as data:
46 md5hash.update(data.read())
47 return md5hash.hexdigest()
50def chunks(container, chunk_size):
51 """Cut a container in chunks of max. chunk_size"""
54 chunk = tuple(itertools.islice(it, chunk_size))
60def get_cdb_authentication_token(path=None):
62 Helper function for correctly retrieving the CDB authentication token (either via file
or via issuing server).
64 :param path: Path to a file containing a CDB authentication token;
if None, the function will use
65 a default path (``${HOME}/b2cdb_${BELLE2_USER}.token``
or ``$TMPFILE/b2cdb_${BELLE2_USER}.token``)
72 path_to_token = os.path.join(os.getenv(
'HOME', tempfile.gettempdir()), f
'b2cdb_{os.getenv("BELLE2_USER", None)}.token')
75 if os.path.isfile(path_to_token):
76 with open(path_to_token)
as token_file:
77 response = requests.get(
'https://token.belle2.org/check', verify=
False, params={
'token': token_file.read().strip()})
78 if response.status_code == 400:
79 B2INFO(f
'The file {path_to_token} contains an invalid token, getting a new token...')
80 os.unlink(path_to_token)
81 elif response.status_code > 400:
82 B2WARNING(
"The validity of the existing token could not be checked. Trying to connect to CDB anyway.")
85 if not os.path.isfile(path_to_token):
86 username = os.environ[
'BELLE2_USER']
87 othername = input(f
'\nConfirm your DESY user name by pressing enter or type the correct one [{username}]: ')
90 password = getpass.getpass(
"Please type your DESY password to request a CDB token: ")
91 response = requests.get(
'https://token.belle2.org', verify=
False, auth=(username, password))
92 print(response.content)
93 if response.status_code == requests.codes.ok:
94 with open(path_to_token,
'wb')
as token_file:
95 os.chmod(path_to_token, stat.S_IRUSR | stat.S_IWUSR)
96 token_file.write(response.content)
98 B2ERROR(
'Failed to get token')
102 with open(path_to_token)
as token_file:
103 return token_file.read().strip()
106def set_cdb_authentication_token(cdb_instance, auth_token=None):
108 Helper function for setting the CDB authentication token.
110 :param cdb_instance: An instance of the ``ConditionsDB``
class.
111 :param auth_token: A CDB authentication token:
if ``
None``, it
is automatically retrieved
from the issuing server
114 if auth_token
is not None:
115 cdb_instance.set_authentication_token(auth_token)
118 token = get_cdb_authentication_token(os.getenv(
'BELLE2_CDB_AUTH_TOKEN', default=
None))
119 cdb_instance.set_authentication_token(token)
123 """Simple class to present bearer token instead of username/password"""
126 """Construct from a token"""
131 """Update headers to include token"""
132 r.headers[
"X-Authorization"] = self.
_authtoken
137 """Small container class to help compare payload information for efficient
138 comparison between globaltags"""
142 """Set all internal members from the json information of the payload and the iov.
145 payload (dict): json information of the payload as returned by REST api
146 iov (dict): json information of the iov
as returned by REST api
149 iov = {
"payloadIovId":
None,
"expStart":
None,
"runStart":
None,
"expEnd":
None,
"runEnd":
None}
152 payload[
'payloadId'],
153 payload[
'basf2Module'][
'name'],
156 payload[
'payloadUrl'],
159 (iov[
"expStart"], iov[
"runStart"], iov[
"expEnd"], iov[
"runEnd"]),
162 def __init__(self, payload_id, name, revision, checksum, payload_url, base_url, iov_id=None, iov=None):
164 Create a new object from the given information
185 """Return the full url to the payload on the server"""
189 """Make object hashable"""
193 """Check if two payloads are equal"""
194 return (self.
name, self.
checksum, self.
iov) == (other.name, other.checksum, other.iov)
197 """Sort payloads by name, iov, revision"""
198 return (self.
name.lower(), self.
iov, self.
revision) < (other.name.lower(), other.iov, other.revision)
201 """return a human readable name for the IoV"""
205 if self.
iov == (0, 0, -1, -1):
208 e1, r1, e2, r2 = self.
iov
210 if r1 == 0
and r2 == -1:
213 return f
"exp {e1}, runs {r1}+"
215 return f
"exp {e1}, run {r1}"
217 return f
"exp {e1}, runs {r1} - {r2}"
219 if e2 == -1
and r1 == 0:
220 return f
"exp {e1} - forever"
222 return f
"exp {e1}, run {r1} - forever"
223 elif r1 == 0
and r2 == -1:
224 return f
"exp {e1}-{e2}, all runs"
226 return f
"exp {e1}, run {r1} - exp {e2}, all runs"
228 return f
"exp {e1}, run {r1} - exp {e2}, run {r2}"
232 """Class to interface conditions db REST interface"""
235 BASE_URLS = [conditions.default_metadata_provider_url]
238 """Class to be thrown by request() if there is any error"""
243 """Resolve the list of server urls. If a url is given just return it.
244 Otherwise return servers listed
in BELLE2_CONDB_SERVERLIST
or the
248 given_url (str): Explicit base_url. If this
is not None it will be
249 returned
as is in a list of length 1
252 a list of urls to
try for database connectivity
255 base_url_list = ConditionsDB.BASE_URLS[:]
256 base_url_env = os.environ.get("BELLE2_CONDB_SERVERLIST",
None)
257 if given_url
is not None:
258 base_url_list = [given_url]
259 elif base_url_env
is not None:
260 base_url_list = base_url_env.split()
261 B2INFO(
"Getting Conditions Database servers from Environment:")
262 for i, url
in enumerate(base_url_list, 1):
263 B2INFO(f
" {i}. {url}")
266 for url
in base_url_list:
267 if url.startswith(
"http://"):
268 full_list.append(
"https" + url[4:])
270 full_list.append(url)
273 def __init__(self, base_url=None, max_connections=10, retries=3):
275 Create a new instance of the interface
278 base_url (string): base url of the rest interface
279 max_connections (int): number of connections to keep open, mostly useful for threaded applications
280 retries (int): number of retries
in case of connection problems
286 adapter = requests.adapters.HTTPAdapter(
287 pool_connections=max_connections, pool_maxsize=max_connections,
288 max_retries=retries, pool_block=
True
290 self.
_session.mount(
"http://", adapter)
291 self.
_session.mount(
"https://", adapter)
293 if "BELLE2_CONDB_PROXY" in os.environ:
295 "http": os.environ.get(
"BELLE2_CONDB_PROXY"),
296 "https": os.environ.get(
"BELLE2_CONDB_PROXY"),
299 base_url_list = ConditionsDB.get_base_urls(base_url)
301 for url
in base_url_list:
306 req.raise_for_status()
307 except requests.RequestException
as e:
308 B2WARNING(f
"Problem connecting to {url}:\n {e}\n Trying next server ...")
312 B2FATAL(
"No working database servers configured, giving up")
318 self.
_session.headers.update({
"Accept":
"application/json",
"Cache-Control":
"no-cache"})
322 Set authentication token when talking to the database
325 token (str): JWT to hand to the database. Will not be checked
330 def request(self, method, url, message=None, *args, **argk):
332 Request function, similar to requests.request but adding the base_url
335 method (str): GET, POST, etc.
336 url (str): url for the request, base_url will be prepended
337 message (str): message to show when starting the request
and if it fails
339 All other arguments will be forwarded to requests.request.
341 if message
is not None:
345 req = self.
_session.request(method, self.
_base_url +
"v2/" + url.lstrip(
"/"), *args, **argk)
346 except requests.exceptions.ConnectionError
as e:
347 B2FATAL(
"Could not access '" + self.
_base_url + url.lstrip(
"/") +
"': " + str(e))
349 if req.status_code >= 300:
353 response = req.json()
354 message = response.get(
"message",
"")
355 colon =
": " if message.strip()
else ""
356 error = f
"Request {method} {url} returned {response['code']} {response['reason']}{colon}{message}"
357 except json.JSONDecodeError:
359 error = f
"Request {method} {url} returned non JSON response {req.status_code}: {req.content}"
361 if message
is not None:
366 if method !=
"HEAD" and req.status_code != requests.codes.no_content:
369 except json.JSONDecodeError
as e:
370 B2INFO(f
"Invalid response: {req.content}")
375 """Get a list of all globaltags. Returns a dictionary with the globaltag
376 names and the corresponding ids
in the database
"""
379 req = self.
request(
"GET",
"/globalTags")
381 B2ERROR(f
"Could not get the list of globaltags: {e}")
385 for tag
in req.json():
386 result[tag[
"name"]] = tag
391 """Check whether the globaltag with the given name exists."""
394 self.
request(
"GET", f
"/globalTag/{encode_name(name)}")
401 """Get the id of the globaltag with the given name. Returns either the
402 id or None if the tag was
not found
"""
405 req = self.
request(
"GET", f
"/globalTag/{encode_name(name)}")
407 B2ERROR(f
"Cannot find globaltag '{name}': {e}")
414 Get the dictionary describing the given globaltag type (currently
415 one of DEV or RELEASE). Returns
None if tag type was
not found.
418 req = self.
request(
"GET",
"/globalTagType")
420 B2ERROR(f
"Could not get list of valid globaltag types: {e}")
423 types = {e[
"name"]: e
for e
in req.json()}
428 B2ERROR(f
"Unknown globaltag type: '{name}', please use one of {', '.join(types)}")
433 Create a new globaltag
435 info = {"name": name,
"description": description,
"modifiedBy": user,
"isDefault":
False}
437 req = self.
request(
"POST",
"/globalTag/DEV", f
"Creating globaltag {name}", json=info)
439 B2ERROR(f
"Could not create globaltag {name}: {e}")
444 def get_all_iovs(self, globalTag, exp=None, run=None, message=None, run_range=None, fully_contained=False):
446 Return list of all payloads in the given globaltag where each element
is
447 a `PayloadInformation` instance
450 gobalTag (str): name of the globaltag
451 exp (int):
if given limit the list of payloads to the ones valid
for
452 the given exp,run combination
453 run (int):
if given limit the list of payloads to the ones valid
for
454 the given exp,run combination
455 message (str): additional message to show when downloading the
456 payload information. Will be directly appended to
457 "Obtaining lists of iovs for globaltag {globalTag}"
458 run_range (tuple):
if given limit the list of payloads to the ones
459 overlapping
with the given run range,
if
460 fully_contained (bool):
if True and the run_range
is not None it limits
461 the list of payloads to the ones fully contained
in the given run range
464 Both, exp
and run, need to be given at the same time. Just supplying
465 an experiment
or a run number will
not work
467 globalTag = encode_name(globalTag)
470 if run_range
is not None:
472 message += f
" [fully contained in {tuple(run_range)}]"
474 message += f
" [valid in {tuple(run_range)}]"
478 msg = f
"Obtaining list of iovs for globaltag {globalTag}, exp={exp}, run={run}{message}"
479 req = self.
request(
"GET",
"/iovPayloads", msg, params={
'gtName': globalTag,
'expNumber': exp,
'runNumber': run})
481 msg = f
"Obtaining list of iovs for globaltag {globalTag}{message}"
482 req = self.
request(
"GET", f
"/globalTag/{globalTag}/globalTagPayloads", msg)
484 for item
in req.json():
485 payload = item[
"payload" if 'payload' in item
else "payloadId"]
486 if "payloadIov" in item:
487 iovs = [item[
'payloadIov']]
489 iovs = item[
'payloadIovs']
492 if run_range
is not None:
493 iov_ =
IntervalOfValidity(iov[
'expStart'], iov[
'runStart'], iov[
'expEnd'], iov[
'runEnd'])
495 if not iov_ & run_range == iov_:
498 if iov_ & run_range
is None:
500 all_iovs.append(PayloadInformation.from_json(payload, iov))
507 Get a list of all defined payloads (for the given global_tag
or by default
for all).
508 Returns a dictionary which maps (module, checksum) to the payload id.
513 req = self.
request(
"GET", f
"/globalTag/{encode_name(global_tag)}/payloads")
515 req = self.
request(
"GET",
"/payloads")
517 B2ERROR(f
"Cannot get list of payloads: {e}")
521 for payload
in req.json():
522 module = payload[
"basf2Module"][
"name"]
523 checksum = payload[
"checksum"]
524 result[(module, checksum)] = payload[
"payloadId"]
530 Check for the existence of payloads
in the database.
533 payloads (list((str,str))): A list of payloads to check
for. Each
534 payload needs to be a tuple of the name of the payload
and the
535 md5 checksum of the payload file.
536 information (str): The information to be extracted
from the
540 A dictionary
with the payload identifiers (name, checksum)
as keys
541 and the requested information
as values
for all payloads which are already
542 present
in the database.
545 search_query = [{"name": e[0],
"checksum": e[1]}
for e
in payloads]
547 req = self.
request(
"POST",
"/checkPayloads", json=search_query)
549 B2ERROR(f
"Cannot check for existing payloads: {e}")
553 for payload
in req.json():
554 module = payload[
"basf2Module"][
"name"]
555 checksum = payload[
"checksum"]
556 result[(module, checksum)] = payload[information]
562 Get the revision numbers of payloads in the database.
565 entries (list): A list of payload entries.
566 Each entry must have the attributes module
and checksum.
572 result = self.check_payloads([(entry.module, entry.checksum) for entry
in entries],
"revision")
576 for entry
in entries:
577 entry.revision = result.get((entry.module, entry.checksum), 0)
586 module (str): name of the module
587 filename (str): name of the file
588 checksum (str): md5 hexdigest of the file. Will be calculated automatically if not given
591 checksum = file_checksum(filename)
597 (filename, open(filename,
"rb").read(),
"application/x-root"),
598 (
"json", json.dumps({
"checksum": checksum,
"isDefault":
False}),
"application/json"),
603 for name, contents, mimetype
in files:
604 rf = RequestField(name=name, data=contents)
605 rf.make_multipart(content_type=mimetype)
608 post_body, content_type = encode_multipart_formdata(fields)
609 content_type =
''.join((
'multipart/mixed',) + content_type.partition(
';')[1:])
610 headers = {
'Content-Type': content_type}
615 req = self.
request(
"POST", f
"/package/dbstore/module/{encode_name(module)}/payload", data=post_body, headers=headers)
617 B2ERROR(f
"Could not create Payload: {e}")
620 return req.json()[
"payloadId"]
622 def create_iov(self, globalTagId, payloadId, firstExp, firstRun, finalExp, finalRun):
627 globalTagId (int): id of the globaltag, obtain with get_globalTagId()
629 firstExp (int): first experiment
for which this iov
is valid
630 firstRun (int): first run
for which this iov
is valid
631 finalExp (int): final experiment
for which this iov
is valid
632 finalRun (int): final run
for which this iov
is valid
635 payloadIovId of the created iov,
None if creation was
not successful
640 local_variables = locals()
641 variables = {e: int(local_variables[e])
for e
in
642 [
"globalTagId",
"payloadId",
"firstExp",
"firstRun",
"finalExp",
"finalRun"]}
644 B2ERROR(
"create_iov: All parameters need to be integers")
649 req = self.
request(
"POST",
"/globalTagPayload/{globalTagId},{payloadId}"
650 "/payloadIov/{firstExp},{firstRun},{finalExp},{finalRun}".format(**variables))
652 B2ERROR(f
"Could not create IOV: {e}")
655 return req.json()[
"payloadIovId"]
661 iovId (int): id of the iov to be deleted
664 self.
request(
"DELETE", f
"/payloadIov/{iovId}")
666 B2ERROR(f
"Could not delete IOV: {e}")
668 def modify_iov(self, iovId, firstExp, firstRun, finalExp, finalRun):
669 """Modify the validity range of a given iov
672 iovId (int): id of the iov to be modified
673 firstExp (int): first experiment for which this iov
is valid
674 firstRun (int): first run
for which this iov
is valid
675 finalExp (int): final experiment
for which this iov
is valid
676 finalRun (int): final run
for which this iov
is valid
679 querystring = {
"expStart": str(firstExp),
"runStart": str(firstRun),
"expEnd": str(finalExp),
"runEnd": str(finalRun)}
680 self.
request(
"PUT", f
"/payloadIov/{iovId}", params=querystring)
682 B2ERROR(f
"Could not modify IOV: {e}")
684 def get_iovs(self, globalTagName, payloadName=None):
685 """Return existing iovs for a given tag name. It returns a dictionary
686 which maps (payloadId, first runId, final runId) to iovId
689 globalTagName(str): Global tag name.
690 payloadName(str): Payload name (if None, selection by name
is
695 req = self.
request(
"GET", f
"/globalTag/{encode_name(globalTagName)}/globalTagPayloads")
701 for payload
in req.json():
702 payloadId = payload[
"payloadId"][
"payloadId"]
703 if payloadName
is not None:
704 if payload[
"payloadId"][
"basf2Module"][
"name"] != payloadName:
706 for iov
in payload[
"payloadIovs"]:
707 iovId = iov[
"payloadIovId"]
708 firstExp, firstRun = iov[
"expStart"], iov[
"runStart"]
709 finalExp, finalRun = iov[
"expEnd"], iov[
"runEnd"]
710 result[(payloadId, firstExp, firstRun, finalExp, finalRun)] = iovId
714 def upload(self, filename, global_tag, normalize=False, ignore_existing=False, nprocess=1, uploaded_entries=None):
716 Upload a testing payload storage to the conditions database.
719 filename (str): filename of the testing payload storage file that should be uploaded
720 global_tage (str): name of the globaltag to which the data should be uploaded
721 normalize (Union[bool, str]): if True the payload root files will be normalized to have the same checksum
for the
722 same content,
if normalize
is a string
in addition the file name
in the root file metadata will be set to it
723 ignore_existing (bool):
if True do
not upload payloads that already exist
724 nprocess (int): maximal number of parallel uploads
725 uploaded_entries (list): the list of successfully uploaded entries
728 True if the upload was successful
733 B2INFO(f
"Reading payload list from {filename}")
734 entries = parse_testing_payloads_file(filename)
736 B2ERROR(f
"Problems with testing payload storage file {filename}, exiting")
740 B2INFO(f
"No payloads found in {filename}, exiting")
743 B2INFO(f
"Found {len(entries)} iovs to upload")
749 tagId = tagId[
"globalTagId"]
753 entries = sorted(set(reversed(entries)))
756 name = normalize
if normalize
is not True else None
758 e.normalize(name=name)
762 payloads = defaultdict(list)
764 payloads[(e.module, e.checksum)].append(e)
766 existing_payloads = {}
769 def upload_payload(item):
770 """Upload a payload file if necessary but first check list of existing payloads"""
772 if key
in existing_payloads:
773 B2INFO(f
"{key[0]} (md5:{key[1]}) already existing in database, skipping.")
774 payload_id = existing_payloads[key]
777 payload_id = self.
create_payload(entry.module, entry.filename, entry.checksum)
778 if payload_id
is None:
781 B2INFO(f
"Created new payload {payload_id} for {entry.module} (md5:{entry.checksum})")
783 for entry
in entries:
784 entry.payload = payload_id
789 """Create an iov if necessary but first check the list of existing iovs"""
790 if entry.payload
is None:
793 iov_key = (entry.payload,) + entry.iov_tuple
794 if iov_key
in existing_iovs:
795 entry.iov = existing_iovs[iov_key]
796 B2INFO(f
"IoV {entry.iov_tuple} for {entry.module} (md5:{entry.checksum}) already existing in database, skipping.")
798 entry.payloadIovId = self.
create_iov(tagId, entry.payload, *entry.iov_tuple)
799 if entry.payloadIovId
is None:
802 B2INFO(f
"Created IoV {entry.iov_tuple} for {entry.module} (md5:{entry.checksum})")
807 with ThreadPoolExecutor(max_workers=nprocess)
as pool:
810 if not ignore_existing:
811 B2INFO(
"Downloading information about existing payloads and iovs...")
814 existing_payloads = {}
816 def create_future(iter, func, callback=None):
817 fn = pool.submit(iter, func)
818 if callback
is not None:
819 fn.add_done_callback(callback)
822 def update_iovs(iovs):
823 existing_iovs.update(iovs.result())
824 B2INFO(f
"Found {len(existing_iovs)} existing iovs in {global_tag}")
826 def update_payloads(payloads):
827 existing_payloads.update(payloads.result())
828 B2INFO(f
"Found {len(existing_payloads)} existing payloads")
830 create_future(self.
get_iovs, global_tag, update_iovs)
832 for chunk
in chunks(payloads.keys(), 1000):
835 futures_wait(futures)
838 failed_payloads = sum(0
if result
else 1
for result
in pool.map(upload_payload, payloads.items()))
839 if failed_payloads > 0:
840 B2ERROR(f
"{failed_payloads} payloads could not be uploaded")
844 for entry
in pool.map(create_iov, entries):
846 if uploaded_entries
is not None:
847 uploaded_entries.append(entry)
851 B2ERROR(f
"{failed_iovs} IoVs could not be created")
854 if uploaded_entries
is not None:
857 return failed_payloads + failed_iovs == 0
861 Upload a testing payload storage to a staging globaltag and create
or update a jira issue
864 filename (str): filename of the testing payload storage file that should be uploaded
865 normalize (Union[bool, str]):
if True the payload root files will be
866 normalized to have the same checksum
for the same content,
if
867 normalize
is a string
in addition the file name
in the root file
868 metadata will be set to it
869 data (dict): a dictionary
with the information provided by the user:
871 * task: category of globaltag, either main, online, prompt, data, mc,
or analysis
872 * tag: the globaltag name
873 * request: type of request, either Update, New,
or Modification. The latter two imply task == main because
874 if new payload classes are introduced
or payload classes are modified then they will first be included
in
875 the main globaltag. Here a synchronization of code
and payload changes has to be managed.
876 If new
or modified payload classes should be included
in other globaltags they must already be
in a release.
877 * pull-request: number of the pull request containing new
or modified payload classes,
878 only
for request == New
or Modified
879 * backward-compatibility: description of what happens
if the old payload
is encountered by the updated code,
880 only
for request == Modified
881 * forward-compatibility: description of what happens
if a new payload
is encountered by the existing code,
882 only
for request == Modified
883 * release: the required release version
884 * reason: the reason
for the request
885 * description: a detailed description
for the globaltag manager
886 * issue: identifier of an existing jira issue (optional)
887 * user: name of the user
888 * time: time stamp of the request
890 password: the password
for access to jira
or the access token
and secret
for oauth access
893 True if the upload
and jira issue creation/upload was successful
897 data[
'tag'] = upload_global_tag(data[
'task'])
898 if data[
'tag']
is None:
899 data[
'tag'] = f
"temp_{data['task']}_{data['user']}_{data['time']}"
907 B2INFO(f
"Uploading testing database {filename} to globaltag {data['tag']}")
909 if not self.
upload(filename, data[
'tag'], normalize, uploaded_entries=entries):
914 issue = data[
'issue']
916 issue = jira_global_tag_v2(data[
'task'])
918 issue = {
"components": [{
"name":
"globaltag"}]}
921 if type(issue)
is tuple:
922 description = issue[1].format(**data)
926|*Upload globaltag* | {data['tag']} |
927|*Request reason* | {data[
'reason']} |
928|*Required release* | {data[
'release']} |
929|*Type of request* | {data[
'request']} |
931 if 'pull-request' in data.keys():
932 description += f
"|*Pull request* | \\#{data['pull-request']} |\n"
933 if 'backward-compatibility' in data.keys():
934 description += f
"|*Backward compatibility* | \\#{data['backward-compatibility']} |\n"
935 if 'forward-compatibility' in data.keys():
936 description += f
"|*Forward compatibility* | \\#{data['forward-compatibility']} |\n"
937 description +=
'|*Details* |' +
''.join(data[
'details']) +
' |\n'
938 if data[
'task'] ==
'online':
939 description +=
'|*Impact on data taking*|' +
''.join(data[
'data_taking']) +
' |\n'
942 description +=
'\nPayloads\n||Name||Revision||IoV||\n'
943 for entry
in entries:
944 description += f
"|{entry.module} | {entry.revision} | ({entry.iov_str()}) |\n"
947 if type(issue)
is dict:
948 issue[
"description"] = description
949 if "summary" in issue.keys():
950 issue[
"summary"] = issue[
"summary"].format(**data)
952 issue[
"summary"] = f
"Globaltag request for {data['task']} by {data['user']} at {data['time']}"
953 if "project" not in issue.keys():
954 issue[
"project"] = {
"key":
"BII"}
955 if "issuetype" not in issue.keys():
956 issue[
"issuetype"] = {
"name":
"Task"}
957 if data[
"task"] ==
"main":
958 issue[
"labels"] = [
"TUPPR"]
960 B2INFO(f
"Creating jira issue for {data['task']} globaltag request")
961 if isinstance(password, str):
962 response = requests.post(
'https://agira.desy.de/rest/api/latest/issue', auth=(data[
'user'], password),
963 json={
'fields': issue})
965 fields = {
'issue': json.dumps(issue)}
966 if 'user' in data.keys():
967 fields[
'user'] = data[
'user']
969 fields[
'token'] = password[0]
970 fields[
'secret'] = password[1]
971 response = requests.post(
'https://b2-master.belle2.org/cgi-bin/jira_issue.py', data=fields)
972 if response.status_code
in range(200, 210):
973 B2INFO(f
"Issue successfully created: https://agira.desy.de/browse/{response.json()['key']}")
975 B2ERROR(
'The creation of the issue failed: ' + requests.status_codes._codes[response.status_code][0])
982 new_issue_config = jira_global_tag_v2(data[
'task'])
983 if isinstance(new_issue_config, dict)
and "assignee" in new_issue_config:
984 user = new_issue_config[
'assignee'].get(
'name',
None)
985 if user
is not None and isinstance(password, str):
986 response = requests.post(f
'https://agira.desy.de/rest/api/latest/issue/{issue}/watchers',
987 auth=(data[
'user'], password), json=user)
988 if response.status_code
in range(200, 210):
989 B2INFO(f
"Added {user} as watcher to {issue}")
991 B2WARNING(f
"Could not add {user} as watcher to {issue}: {response.status_code}")
993 B2INFO(f
"Commenting on jira issue {issue} for {data['task']} globaltag request")
994 if isinstance(password, str):
995 response = requests.post(f
'https://agira.desy.de/rest/api/latest/issue/{issue}/comment',
996 auth=(data[
'user'], password), json={
'body': description})
998 fields = {
'id': issue,
'user': user,
'comment': description}
1000 fields[
'token'] = password[0]
1001 fields[
'secret'] = password[1]
1002 response = requests.post(
'https://b2-master.belle2.org/cgi-bin/jira_issue.py', data=fields)
1003 if response.status_code
in range(200, 210):
1004 B2INFO(f
"Issue successfully updated: https://agira.desy.de/browse/{issue}")
1006 B2ERROR(
'The commenting of the issue failed: ' + requests.status_codes._codes[response.status_code][0])
1012def require_database_for_test(timeout=60, base_url=None):
1013 """Make sure that the database is available and skip the test if not.
1015 This function should be called in test scripts
if they are expected to fail
1016 if the database
is down. It either returns when the database
is ok
or it
1017 will signal test_basf2 that the test should be skipped
and exit
1020 if os.environ.get(
"BELLE2_CONDB_GLOBALTAG",
None) ==
"":
1021 raise Exception(
"Access to the Database is disabled")
1022 base_url_list = ConditionsDB.get_base_urls(base_url)
1023 for url
in base_url_list:
1025 req = requests.request(
"HEAD", url.rstrip(
'/') +
"/v2/globalTags")
1026 req.raise_for_status()
1027 except requests.RequestException
as e:
1028 B2WARNING(f
"Problem connecting to {url}:\n {e}\n Trying next server ...")
1032 print(
"TEST SKIPPED: No working database servers configured, giving up", file=sys.stderr)
1036def enable_debugging():
1037 """Enable verbose output of python-requests to be able to debug http connections"""
1041 import http.client
as http_client
1043 http_client.HTTPConnection.debuglevel = 1
1045 logging.basicConfig()
1046 logging.getLogger().setLevel(logging.DEBUG)
1047 requests_log = logging.getLogger(
"requests.packages.urllib3")
1048 requests_log.setLevel(logging.DEBUG)
1049 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 delete_iov(self, iovId)
def get_all_iovs(self, globalTag, exp=None, run=None, message=None, run_range=None, fully_contained=False)
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_base_urls(given_url)
def modify_iov(self, iovId, firstExp, firstRun, finalExp, finalRun)
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