blob: 09bde07adc04e99fecd949b69b30ef8c1f82afcd [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import json
import threading
from concurrent.futures import ThreadPoolExecutor
from http.server import HTTPServer
from urllib.request import urlopen
from pypaimon import CatalogFactory
from pypaimon.api.api_response import ConfigResponse
from pypaimon.api.auth import BearTokenAuthProvider
from pypaimon.tests.rest.rest_server import RESTCatalogServer
def _server(path):
return RESTCatalogServer(str(path), BearTokenAuthProvider('token'),
ConfigResponse(defaults={'prefix': 'lifecycle'}), 'warehouse')
def _catalog(server):
return CatalogFactory.create({
'metastore': 'rest', 'uri': server.get_url(), 'warehouse': 'warehouse',
'token.provider': 'bear', 'token': 'token', 'data-token.enabled': 'false'})
def test_shutdown_preserves_isolation_and_releases_threads_and_ports(tmp_path):
servers = [_server(tmp_path / str(index)) for index in range(2)]
try:
for index, server in enumerate(servers):
server.start()
catalog = _catalog(server)
catalog.create_database('database_' + str(index), False)
for index, server in enumerate(servers):
# The final response is complete and each server still has its own state.
assert _catalog(server).list_databases() == ['database_' + str(index)]
finally:
for server in servers:
server.shutdown()
for server in servers:
assert not server.server_thread.is_alive()
assert server.server.socket.fileno() == -1
with HTTPServer(('localhost', server.port), server._create_request_handler()):
pass
def test_shutdown_waits_for_inflight_response(tmp_path, monkeypatch):
server = _server(tmp_path)
request_entered = threading.Event()
release_request = threading.Event()
response_complete = threading.Event()
shutdown_entered = threading.Event()
handler = server._create_request_handler()
send_response = handler._send_response
def record_response(self, status_code, body):
send_response(self, status_code, body)
response_complete.set()
monkeypatch.setattr(handler, '_send_response', record_response)
monkeypatch.setattr(server, '_create_request_handler', lambda: handler)
route_request = server._route_request
def block_request(*args):
request_entered.set()
assert release_request.wait(10)
return route_request(*args)
monkeypatch.setattr(server, '_route_request', block_request)
server.start()
def read_config():
from urllib.request import Request
url = server.get_url() + server.resource_paths.config() + '?warehouse=warehouse'
with urlopen(Request(url, headers={'Authorization': 'Bearer token'}), timeout=10) as response:
return json.loads(response.read().decode('utf-8'))
def shutdown():
shutdown_entered.set()
server.shutdown()
return response_complete.is_set()
try:
with ThreadPoolExecutor(max_workers=2) as executor:
request = executor.submit(read_config)
try:
assert request_entered.wait(10)
stopped = executor.submit(shutdown)
assert shutdown_entered.wait(10)
assert not stopped.done()
finally:
release_request.set()
assert request.result(timeout=10)['defaults'] == {'prefix': 'lifecycle'}
assert stopped.result(timeout=10)
finally:
release_request.set()
if server.server_thread.is_alive():
server.shutdown()
assert not server.server_thread.is_alive()
assert server.server.socket.fileno() == -1