| # |
| # 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 logging |
| import os |
| import threading |
| import time |
| import unittest |
| import urllib.request |
| from unittest import TestCase |
| |
| import docker |
| |
| from liminal.build.python import PythonImageVersions |
| from liminal.build.service.python_server.python_server import PythonServerImageBuilder |
| |
| |
| class TestPythonServer(TestCase): |
| |
| def setUp(self) -> None: |
| super().setUp() |
| self.docker_client = docker.from_env() |
| self.config = self.__create_conf('my_task') |
| self.image_name = self.config['image'] |
| self.__remove_containers() |
| |
| def tearDown(self) -> None: |
| self.__remove_containers() |
| self.docker_client.close() |
| |
| def test_build_python_server(self): |
| versions = [None] + list(PythonImageVersions().supported_versions) |
| for version in versions: |
| build_out = self.__test_build_python_server(python_version=version) |
| self.assertTrue('RUN pip install -r requirements.txt' in build_out, |
| 'Incorrect pip command') |
| |
| def test_build_python_server_with_pip_conf(self): |
| build_out = self.__test_build_python_server(use_pip_conf=True) |
| |
| self.assertTrue( |
| 'RUN --mount=type=secret,id=pip_config,dst=/etc/pip.conf pip install' in build_out, |
| 'Incorrect pip command') |
| |
| def __test_build_python_server(self, use_pip_conf=False, |
| python_version=None): |
| base_path = os.path.join(os.path.dirname(__file__), '../../../liminal') |
| |
| config = self.__create_conf('my_task') |
| |
| if use_pip_conf: |
| config['pip_conf'] = os.path.join(base_path, 'pip.conf') |
| |
| if python_version: |
| config['python_version'] = python_version |
| |
| builder = PythonServerImageBuilder(config=config, |
| base_path=base_path, |
| relative_source_path='myserver', |
| tag=self.image_name) |
| |
| build_out = str(builder.build()) |
| |
| thread = threading.Thread(target=self.__run_container, args=[self.image_name]) |
| thread.daemon = True |
| thread.start() |
| |
| time.sleep(5) |
| |
| logging.info('Sending request to server') |
| |
| json_string = '{"key1": "val1", "key2": "val2"}' |
| |
| encoding = 'ascii' |
| |
| server_response = str(urllib.request.urlopen( |
| 'http://localhost:9294/myendpoint1', |
| data=json_string.encode(encoding) |
| ).read().decode(encoding)) |
| |
| logging.info(f'Response from server: {server_response}') |
| |
| self.assertEqual(f'Input was: {json.loads(json_string)}', server_response) |
| |
| return build_out |
| |
| def __remove_containers(self): |
| logging.info(f'Stopping containers with image: {self.image_name}') |
| |
| all_containers = self.docker_client.containers |
| matching_containers = all_containers.list(filters={'ancestor': self.image_name}) |
| |
| for container in matching_containers: |
| container_id = container.id |
| logging.info(f'Stopping container {container_id}') |
| self.docker_client.api.stop(container_id) |
| logging.info(f'Removing container {container_id}') |
| self.docker_client.api.remove_container(container_id) |
| |
| self.docker_client.containers.prune() |
| |
| def __run_container(self, image_name): |
| try: |
| logging.info(f'Running container for image: {image_name}') |
| self.docker_client.containers.run(image_name, ports={'80/tcp': 9294}) |
| except Exception as err: |
| logging.exception(err) |
| pass |
| |
| @staticmethod |
| def __create_conf(task_id): |
| return { |
| 'task': task_id, |
| 'cmd': 'foo bar', |
| 'image': 'liminal_server_image', |
| 'source': 'baz', |
| 'input_type': 'my_input_type', |
| 'input_path': 'my_input', |
| 'output_path': '/my_output.json', |
| 'no_cache': True, |
| 'endpoints': [ |
| { |
| 'endpoint': '/myendpoint1', |
| 'module': 'my_server', |
| 'function': 'myendpoint1func' |
| } |
| ] |
| } |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |