15 Script to make sure the conditions database interface is behaving as expected.
17 We do this by creating a local http server which acts as a mock database for
18 getting 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
33 from http.server
import HTTPServer, BaseHTTPRequestHandler
34 from urllib.parse
import urlparse, parse_qs
35 from b2test_utils
import clean_working_directory, safe_process, skip_test, configure_logging_for_tests
36 import multiprocessing
40 class 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 '{{ "name": "{}", "globalTagStatus": {{ "name": "{}" }} }}'.format(
126 gtname, self.globaltags[gtname]))
128 return self.send_error(404)
130 if url.path.endswith(
"/iovPayloads/"):
131 exp = params[
"expNumber"][0]
132 run = params[
"runNumber"][0]
135 self.send_error(int(exp))
141 if exp
in self.payloads:
142 baseurl =
"http://%s:%s" % self.server.socket.getsockname()
143 return self.reply(self.payloads[exp] % dict(exp=exp, run=run, baseurl=baseurl))
146 filename = os.path.basename(url.path)
148 filename = filename.replace(
"rev_3",
"rev_1")
149 basedir = basf2.find_file(
"framework/tests/conditions_testpayloads")
150 path = os.path.join(basedir, filename)
151 if os.path.isfile(path):
153 self.send_response(200)
155 with open(path,
"rb")
as f:
156 shutil.copyfileobj(f, self.wfile)
163 def run_mockdb(pipe):
164 """Startup the mock conditions db server and send the port we listen on back
165 to the parent process"""
170 httpd = HTTPServer((
"127.0.0.1", 12701), SimpleConditionsDB)
173 skip_test(
"Socket 12701 is in use, cannot continue")
176 port = httpd.socket.getsockname()[1]
180 httpd.serve_forever()
183 def run_redirect(pipe, redir_port):
184 """Startup the redirection server: any request should be transparently forwarded
185 to the other using 308 http replies"""
187 class SimpleRedirectServer(BaseHTTPRequestHandler):
189 self.send_response(308)
190 self.send_header(
"Location", f
"http://127.0.0.1:{redir_port}{self.path}")
193 def log_message(self, format, *args):
194 """Override default logging to remove timestamp"""
195 print(
"Redirect Server:", format % args)
197 def log_error(self, *args):
198 """Disable error logs"""
201 httpd = HTTPServer((
"127.0.0.1", 12702), SimpleRedirectServer)
205 skip_test(
"Socket 12702 is in use, cannot continue")
209 httpd.serve_forever()
212 def dbprocess(host, path, lastChangeCallback=lambda: None, *, globaltag=
"localtest"):
213 """Process a given path in a child process so that FATAL will not abort this
214 script but just the child and configure to use a central database at the given host"""
216 with clean_working_directory():
218 configure_logging_for_tests()
219 basf2.logging.log_level = basf2.LogLevel.DEBUG
220 basf2.logging.debug_level = 30
221 basf2.conditions.reset()
222 basf2.conditions.expert_settings(download_cache_location=
"db-cache")
223 basf2.conditions.override_globaltags([globaltag])
225 basf2.conditions.metadata_providers = [host]
226 basf2.conditions.payload_locations = []
231 def set_serverlist(serverlist):
232 """Set a list of database servers."""
233 basf2.conditions.metadata_providers = serverlist + [e
for e
in basf2.conditions.metadata_providers
if not e.startswith(
"http")]
236 os.environ.pop(
'BELLE2_CONDB_METADATA',
None)
239 basf2.conditions.expert_settings(backoff_factor=1, connection_timeout=5,
240 stalled_timeout=5, max_retries=3)
243 basf2.set_random_seed(
"something important")
245 conn = multiprocessing.Pipe(
False)
248 mock_conditionsdb = multiprocessing.Process(target=run_mockdb, args=(conn[1],))
249 mock_conditionsdb.daemon =
True
250 mock_conditionsdb.start()
252 mock_port = conn[0].recv()
254 if mock_port
is None:
258 redir_server = multiprocessing.Process(target=run_redirect, args=(conn[1], mock_port))
259 redir_server.daemon =
True
261 redir_port = conn[0].recv()
262 if redir_port
is None:
266 mock_host = f
"http://localhost:{mock_port}/"
267 redir_host = f
"http://localhost:{redir_port}/"
272 evtinfo = main.add_module(
"EventInfoSetter")
273 main.add_module(
"PrintBeamParameters")
277 for exp
in range(len(SimpleConditionsDB.payloads) + 1):
279 basf2.B2INFO(f
">>> check exp {exp}: {SimpleConditionsDB.payloads[str(exp)][0:20]}...)")
281 basf2.B2INFO(f
">>> check exp {exp}")
282 evtinfo.param({
"expList": [exp, exp, exp],
"runList": [0, 1, 2],
"evtNumList": [1, 1, 1]})
283 dbprocess(mock_host, main)
285 dbprocess(redir_host, main)
287 basf2.B2INFO(
">>> check that a invalid global tag or a misspelled global tag actually throw errors")
288 evtinfo.param({
"expList": [3],
"runList": [0],
"evtNumList": [1]})
289 for gt
in [
"newgt",
"invalidgt",
"horriblymisspelled",
290 "h͌̉e̳̞̞͆ͨ̏͋̕ ͍͚̱̰̀͡c͟o͛҉̟̰̫͔̟̪̠m̴̀ͯ̿͌ͨ̃͆e̡̦̦͖̳͉̗ͨͬ̑͌̃ͅt̰̝͈͚͍̳͇͌h̭̜̙̦̣̓̌̃̓̀̉͜!̱̞̻̈̿̒̀͢!̋̽̍̈͐ͫ͏̠̹̺̜̬͍ͅ"]:
291 dbprocess(mock_host, main, globaltag=gt)
293 basf2.B2INFO(
">>> check retry on 503 errors")
294 evtinfo.param({
"expList": [503],
"runList": [0],
"evtNumList": [1]})
295 dbprocess(mock_host, main)
296 basf2.B2INFO(
">>> check again without retries")
297 basf2.conditions.expert_settings(max_retries=0)
298 dbprocess(mock_host, main)
301 evtinfo.param({
"expList": [0],
"runList": [0],
"evtNumList": [1]})
303 basf2.B2INFO(
">>> try to open localhost on port 0, this should always be refused")
304 dbprocess(
"http://localhost:0", main)
306 basf2.B2INFO(
">>> and once more with a non existing host name to check for lookup errors")
307 dbprocess(
"http://nosuchurl/", main)
309 basf2.B2INFO(
">>> and once more with a non existing protocol")
310 dbprocess(
"nosuchproto://nosuchurl/", main)
312 basf2.B2INFO(
">>> and once more with a totally bogus url")
313 dbprocess(
"h͌̉e̳̞̞͆ͨ̏͋̕ ͍͚̱̰̀͡c͟o͛҉̟̰̫͔̟̪̠m̴̀ͯ̿͌ͨ̃͆e̡̦̦͖̳͉̗ͨͬ̑͌̃ͅt̰̝͈͚͍̳͇͌h̭̜̙̦̣̓̌̃̓̀̉͜!̱̞̻̈̿̒̀͢!̋̽̍̈͐ͫ͏̠̹̺̜̬͍ͅ", main)
315 basf2.B2INFO(
""">>> try to have a list of servers from environment variable
316 We expect that it fails over to the third server, {mock_host}, but then succeeds
318 evtinfo.param({
"expList": [3],
"runList": [0],
"evtNumList": [1]})
320 "http://localhost:0",
321 "http://h͌̉e̳̞̞͆ͨ̏͋̕c͟o͛҉̟̰̫͔̟̪̠m̴̀ͯ̿͌ͨ̃͆e̡̦̦͖̳͉̗ͨͬ̑͌̃ͅt̰̝͈͚͍̳͇͌h̭̜̙̦̣̓̌̃̓̀̉͜!̱̞̻̈̿̒̀͢",
324 os.environ[
"BELLE2_CONDB_SERVERLIST"] =
" ".join(serverlist)
328 basf2.B2INFO(
""">>> try to have a list of servers from steering file
329 We expect that it fails over to the third server, {mock_host}, but then succeeds
331 dbprocess(
"", main, lastChangeCallback=
lambda: set_serverlist(serverlist))
333 if "ssl" in sys.argv:
337 for hostname
in (
"expired",
"wrong.host",
"self-signed",
"untrusted-root"):
338 dbprocess(f
"https://{hostname}.badssl.com/", main)