15Script to make sure the conditions database interface is behaving as expected. 
   17We do this by creating a local http server which acts as a mock database for 
   18getting the payload information and payloads and then we run through different scenarios: 
   23    - corrupt payload information 
   24    - incomplete payload information 
   25    - correct payload information 
   26    - payload file missing 
   27    - payload file checksum mismatch 
   33from http.server 
import HTTPServer, BaseHTTPRequestHandler
 
   34from urllib.parse 
import urlparse, parse_qs
 
   35from b2test_utils 
import clean_working_directory, safe_process, skip_test, configure_logging_for_tests
 
   40class SimpleConditionsDB(BaseHTTPRequestHandler):
 
   41    """Simple ConditionsDB server which handles just the two things needed to 
   42    test the interface: get a list of payloads for the current run and download 
   43    a payloadfile. It will return different payloads for the experiments to 
   44    check for different error conditions""" 
   47    globaltag_states = 
"""[ 
   49      { "name": "TESTING" }, 
   50      { "name": "VALIDATED" }, 
   51      { "name": "PUBLISHED" }, 
   52      { "name": "RUNNING" }, 
   57    example_payload = 
"""[{{ 
   59            "baseUrl": "%(baseurl)s", 
   61            "checksum":"{checksum}", 
   62            "revision":{revision}, 
   63            "payloadUrl":"dbstore_BeamParameters_rev_{revision}.root", 
   64            "basf2Module": {{ "name":"BeamParameters", "basf2Package": {{ "name":"dbstore" }} }} 
   78        "1": 
'[{ "foo": { } }]',
 
   82        "3": example_payload.format(checksum=
"2447fbcf76419fbbc7c6d015ef507769", revision=
"1"),
 
   84        "4": example_payload.format(checksum=
"00[wrong checksum]", revision=
"1"),
 
   86        "5": example_payload.format(checksum=
"00[missing]", revision=
"2"),
 
   88        "6": example_payload.format(checksum=
"2447fbcf76419fbbc7c6d015ef507769", revision=
"2")[:-1] + 
"," +
 
   89             example_payload.format(checksum=
"2447fbcf76419fbbc7c6d015ef507769", revision=
"1")[1:-1] + 
"," +
 
   90             example_payload.format(checksum=
"2447fbcf76419fbbc7c6d015ef507769", revision=
"3")[1:],
 
   95        "localtest": 
"PUBLISHED",
 
   97        "invalidgt": 
"INVALID",
 
  100    def reply(self, xml):
 
  101        """Return a given xml string""" 
  102        self.send_response(200)
 
  104        self.wfile.write(xml.encode())
 
  106    def log_message(self, format, *args):
 
  107        """Override default logging to remove timestamp""" 
  108        print(
"MockConditionsDB:", format % args)
 
  110    def log_error(self, *args):
 
  111        """Disable error logs""" 
  114        """Parse a get request""" 
  115        url = urlparse(self.path)
 
  116        params = parse_qs(url.query)
 
  117        if url.path == 
"/v2/globalTagStatus":
 
  118            return self.reply(self.globaltag_states)
 
  120        elif url.path.startswith(
"/v2/globalTag"):
 
  122            gtname = url.path.split(
"/")[-1]
 
  123            if gtname 
in self.globaltags:
 
  125                    f
'{{ "name": "{gtname}", "globalTagStatus": {{ "name": "{self.globaltags[gtname]}" }} }}')
 
  127                return self.send_error(404)
 
  129        if url.path.endswith(
"/iovPayloads/"):
 
  130            exp = params[
"expNumber"][0]
 
  131            run = params[
"runNumber"][0]
 
  134                self.send_error(int(exp))
 
  140            if exp 
in self.payloads:
 
  141                baseurl = 
"http://%s:%s" % self.server.socket.getsockname()
 
  142                return self.reply(self.payloads[exp] % dict(exp=exp, run=run, baseurl=baseurl))
 
  145            filename = os.path.basename(url.path)
 
  147            filename = filename.replace(
"rev_3", 
"rev_1")
 
  148            basedir = basf2.find_file(
"framework/tests/conditions_testpayloads")
 
  149            path = os.path.join(basedir, filename)
 
  150            if os.path.isfile(path):
 
  152                self.send_response(200)
 
  154                with open(path, 
"rb") 
as f:
 
  155                    shutil.copyfileobj(f, self.wfile)
 
  163    """Startup the mock conditions db server and send the port we listen on back 
  164    to the parent process""" 
  169        httpd = HTTPServer((
"127.0.0.1", 12701), SimpleConditionsDB)
 
  172        skip_test(
"Socket 12701 is in use, cannot continue")
 
  175    port = httpd.socket.getsockname()[1]
 
  179    httpd.serve_forever()
 
  182def run_redirect(pipe, redir_port):
 
  183    """Startup the redirection server: any request should be transparently forwarded 
  184    to the other using 308 http replies""" 
  186    class SimpleRedirectServer(BaseHTTPRequestHandler):
 
  188            self.send_response(308)
 
  189            self.send_header(
"Location", f
"http://127.0.0.1:{redir_port}{self.path}")
 
  192        def log_message(self, format, *args):
 
  193            """Override default logging to remove timestamp""" 
  194            print(
"Redirect Server:", format % args)
 
  196        def log_error(self, *args):
 
  197            """Disable error logs""" 
  200        httpd = HTTPServer((
"127.0.0.1", 12702), SimpleRedirectServer)
 
  204        skip_test(
"Socket 12702 is in use, cannot continue")
 
  208    httpd.serve_forever()
 
  211def dbprocess(host, path, lastChangeCallback=lambda: None, *, globaltag=
"localtest"):
 
  212    """Process a given path in a child process so that FATAL will not abort this 
  213    script but just the child and configure to use a central database at the given host""" 
  215    with clean_working_directory():
 
  217        configure_logging_for_tests()
 
  218        basf2.logging.log_level = basf2.LogLevel.DEBUG
 
  219        basf2.logging.debug_level = 30
 
  220        basf2.conditions.reset()
 
  221        basf2.conditions.expert_settings(download_cache_location=
"db-cache")
 
  222        basf2.conditions.override_globaltags([globaltag])
 
  224            basf2.conditions.metadata_providers = [host]
 
  225        basf2.conditions.payload_locations = []
 
  230def set_serverlist(serverlist):
 
  231    """Set a list of database servers.""" 
  232    basf2.conditions.metadata_providers = serverlist + [e 
for e 
in basf2.conditions.metadata_providers 
if not e.startswith(
"http")]
 
  235os.environ.pop(
'BELLE2_CONDB_METADATA', 
None)
 
  238basf2.conditions.expert_settings(backoff_factor=1, connection_timeout=5,
 
  239                                 stalled_timeout=5, max_retries=3)
 
  242basf2.set_random_seed(
"something important")
 
  244conn = multiprocessing.Pipe(
False)
 
  247mock_conditionsdb = multiprocessing.Process(target=run_mockdb, args=(conn[1],))
 
  248mock_conditionsdb.daemon = 
True 
  249mock_conditionsdb.start()
 
  251mock_port = conn[0].recv()
 
  257redir_server = multiprocessing.Process(target=run_redirect, args=(conn[1], mock_port))
 
  258redir_server.daemon = 
True 
  260redir_port = conn[0].recv()
 
  261if redir_port 
is None:
 
  265mock_host = f
"http://localhost:{mock_port}/" 
  266redir_host = f
"http://localhost:{redir_port}/" 
  271evtinfo = main.add_module(
"EventInfoSetter")
 
  272main.add_module(
"PrintBeamParameters")
 
  276for exp 
in range(len(SimpleConditionsDB.payloads) + 1):
 
  278        basf2.B2INFO(f
">>> check exp {exp}: {SimpleConditionsDB.payloads[str(exp)][0:20]}...)")
 
  280        basf2.B2INFO(f
">>> check exp {exp}")
 
  281    evtinfo.param({
"expList": [exp, exp, exp], 
"runList": [0, 1, 2], 
"evtNumList": [1, 1, 1]})
 
  282    dbprocess(mock_host, main)
 
  284    dbprocess(redir_host, main)
 
  286basf2.B2INFO(
">>> check that a invalid global tag or a misspelled global tag actually throw errors")
 
  287evtinfo.param({
"expList": [3], 
"runList": [0], 
"evtNumList": [1]})
 
  288for gt 
in [
"newgt", 
"invalidgt", 
"horriblymisspelled",
 
  289           "h͌̉e̳̞̞͆ͨ̏͋̕ ͍͚̱̰̀͡c͟o͛҉̟̰̫͔̟̪̠m̴̀ͯ̿͌ͨ̃͆e̡̦̦͖̳͉̗ͨͬ̑͌̃ͅt̰̝͈͚͍̳͇͌h̭̜̙̦̣̓̌̃̓̀̉͜!̱̞̻̈̿̒̀͢!̋̽̍̈͐ͫ͏̠̹̺̜̬͍ͅ"]:
 
  290    dbprocess(mock_host, main, globaltag=gt)
 
  292basf2.B2INFO(
">>> check retry on 503 errors")
 
  293evtinfo.param({
"expList": [503], 
"runList": [0], 
"evtNumList": [1]})
 
  294dbprocess(mock_host, main)
 
  295basf2.B2INFO(
">>> check again without retries")
 
  296basf2.conditions.expert_settings(max_retries=0)
 
  297dbprocess(mock_host, main)
 
  300evtinfo.param({
"expList": [0], 
"runList": [0], 
"evtNumList": [1]})
 
  302basf2.B2INFO(
">>> try to open localhost on port 0, this should always be refused")
 
  303dbprocess(
"http://localhost:0", main)
 
  305basf2.B2INFO(
">>> and once more with a non existing host name to check for lookup errors")
 
  306dbprocess(
"http://nosuchurl/", main)
 
  308basf2.B2INFO(
">>> and once more with a non existing protocol")
 
  309dbprocess(
"nosuchproto://nosuchurl/", main)
 
  311basf2.B2INFO(
">>> and once more with a totally bogus url")
 
  312dbprocess(
"h͌̉e̳̞̞͆ͨ̏͋̕ ͍͚̱̰̀͡c͟o͛҉̟̰̫͔̟̪̠m̴̀ͯ̿͌ͨ̃͆e̡̦̦͖̳͉̗ͨͬ̑͌̃ͅt̰̝͈͚͍̳͇͌h̭̜̙̦̣̓̌̃̓̀̉͜!̱̞̻̈̿̒̀͢!̋̽̍̈͐ͫ͏̠̹̺̜̬͍ͅ", main)
 
  314basf2.B2INFO(
""">>> try to have a list of servers from environment variable 
  315    We expect that it fails over to the third server, {mock_host}, but then succeeds 
  317evtinfo.param({
"expList": [3], 
"runList": [0], 
"evtNumList": [1]})
 
  319    "http://localhost:0",
 
  320    "http://h͌̉e̳̞̞͆ͨ̏͋̕c͟o͛҉̟̰̫͔̟̪̠m̴̀ͯ̿͌ͨ̃͆e̡̦̦͖̳͉̗ͨͬ̑͌̃ͅt̰̝͈͚͍̳͇͌h̭̜̙̦̣̓̌̃̓̀̉͜!̱̞̻̈̿̒̀͢",
 
  323os.environ[
"BELLE2_CONDB_SERVERLIST"] = 
" ".join(serverlist)
 
  327basf2.B2INFO(
""">>> try to have a list of servers from steering file 
  328    We expect that it fails over to the third server, {mock_host}, but then succeeds 
  330dbprocess(
"", main, lastChangeCallback=
lambda: set_serverlist(serverlist))
 
  336    for hostname 
in (
"expired", 
"wrong.host", 
"self-signed", 
"untrusted-root"):
 
  337        dbprocess(f
"https://{hostname}.badssl.com/", main)