def test_create(template: str, params: Dict, monkeypatch): create_from_yaml_mock = Mock() load_kube_config_mock = Mock() monkeypatch.setattr(kubernetes.utils, 'create_from_yaml', create_from_yaml_mock) monkeypatch.setattr(kubernetes.config, 'load_kube_config', load_kube_config_mock) # avoid deleting the config file so that we can check it remove_mock = Mock() monkeypatch.setattr(os, 'remove', remove_mock) create(template=template, params=params) # get the path to the config file assert remove_mock.call_count == 1 path_to_config_file = remove_mock.call_args[0][0] # get the content and check that the values are present with open(path_to_config_file, 'r') as fh: content = fh.read() for v in params.values(): if isinstance(v, str): assert v in content elif isinstance(v, dict): dict_content = json.loads(content) for sub_key, sub_v in v.items(): assert dict_content['data'][sub_key] == sub_v monkeypatch.undo() os.remove(path_to_config_file)
def test_create_deployment_with_device_plugin(template, monkeypatch): params = { 'name': 'test-name', 'namespace': 'test-namespace', 'image': 'test-image', 'replicas': 1, 'command': 'test-command', 'args': 'test-args', 'port_expose': 1234, 'port_in': 1234, 'port_out': 1234, 'port_ctrl': 1234, 'pull_policy': 1234, 'device_plugins': { 'hardware-vendor.example/foo': 2, 'nvidia.com/gpu:': 3 }, } create_from_yaml_mock = Mock() load_kube_config_mock = Mock() remove_mock = Mock() monkeypatch.setattr(kubernetes.utils, 'create_from_yaml', create_from_yaml_mock) monkeypatch.setattr(kubernetes.config, 'load_kube_config', load_kube_config_mock) monkeypatch.setattr(os, 'remove', remove_mock) create(template, params) assert remove_mock.call_count == 1 path_to_config_file = remove_mock.call_args[0][0] with open(path_to_config_file, 'r') as f: config = yaml.safe_load(f) assert config['spec']['template']['spec']['containers'][0][ 'resources'] == { 'limits': { 'hardware-vendor.example/foo': 2, 'nvidia.com/gpu:': 3 } }
def test_create(template: str, values: Dict, monkeypatch): create_from_yaml_mock = Mock() monkeypatch.setattr(kubernetes.utils, 'create_from_yaml', create_from_yaml_mock) # avoid deleting the config file so that we can check it remove_mock = Mock() monkeypatch.setattr(os, 'remove', remove_mock) create(template, values) # get the path to the config file assert remove_mock.call_count == 1 path_to_config_file = remove_mock.call_args[0][0] # get the content and check that the values are present with open(path_to_config_file, 'r') as fh: content = fh.read() for v in values.values(): assert v in content monkeypatch.undo() os.remove(path_to_config_file)
def deploy_service( name: str, namespace: str, image_name: str, container_cmd: str, container_args: str, logger: JinaLogger, replicas: int, pull_policy: str, init_container: Dict = None, custom_resource_dir: Optional[str] = None, port_expose: Optional[int] = None, ) -> str: """Deploy service on Kubernetes. :param name: name of the service and deployment :param namespace: k8s namespace of the service and deployment :param image_name: image for the k8s deployment :param container_cmd: command executed on the k8s pods :param container_args: arguments used for the k8s pod :param logger: used logger :param replicas: number of replicas :param pull_policy: pull policy used for fetching the Docker images from the registry. :param init_container: additional arguments used for the init container :param custom_resource_dir: Path to a folder containing the kubernetes yml template files. Defaults to the standard location jina.resources if not specified. :param port_expose: port which will be exposed by the deployed containers :return: dns name of the created service """ # we can always assume the ports are the same for all executors since they run on different k8s pods # port expose can be defined by the user if not port_expose: port_expose = 8080 port_in = 8081 port_out = 8082 port_ctrl = 8083 logger.info( f'🔋\tCreate Service for "{name}" with exposed port "{port_expose}"') kubernetes_tools.create( 'service', { 'name': name, 'target': name, 'namespace': namespace, 'port_expose': port_expose, 'port_in': port_in, 'port_out': port_out, 'port_ctrl': port_ctrl, 'type': 'ClusterIP', }, logger=logger, custom_resource_dir=custom_resource_dir, ) logger.info( f'🐳\tCreate Deployment for "{name}" with image "{image_name}", replicas {replicas} and init_container {init_container is not None}' ) if init_container: template_name = 'deployment-init' else: template_name = 'deployment' init_container = {} kubernetes_tools.create( template_name, { 'name': name, 'namespace': namespace, 'image': image_name, 'replicas': replicas, 'command': container_cmd, 'args': container_args, 'port_expose': port_expose, 'port_in': port_in, 'port_out': port_out, 'port_ctrl': port_ctrl, 'pull_policy': pull_policy, **init_container, }, logger=logger, custom_resource_dir=custom_resource_dir, ) logger.info(f'🔑\tCreate necessary permissions"') kubernetes_tools.create( 'connection-pool-role', { 'namespace': namespace, }, ) kubernetes_tools.create( 'connection-pool-role-binding', { 'namespace': namespace, }, ) return f'{name}.{namespace}.svc'
def deploy_service( name: str, namespace: str, image_name: str, container_cmd: str, container_args: str, logger: JinaLogger, replicas: int, pull_policy: str, init_container: Optional[Dict] = None, custom_resource_dir: Optional[str] = None, port_expose: Optional[int] = None, env: Optional[Dict] = None, gpus: Optional[Union[int, str]] = None, ) -> str: """Deploy service on Kubernetes. :param name: name of the service and deployment :param namespace: k8s namespace of the service and deployment :param image_name: image for the k8s deployment :param container_cmd: command executed on the k8s pods :param container_args: arguments used for the k8s pod :param logger: used logger :param replicas: number of replicas :param pull_policy: pull policy used for fetching the Docker images from the registry. :param init_container: additional arguments used for the init container :param custom_resource_dir: Path to a folder containing the kubernetes yml template files. Defaults to the standard location jina.resources if not specified. :param port_expose: port which will be exposed by the deployed containers :param env: environment variables to be passed into configmap. :param gpus: number of gpus to use, for k8s requires you pass an int number, refers to the number of requested gpus. :return: dns name of the created service """ # we can always assume the ports are the same for all executors since they run on different k8s pods # port expose can be defined by the user if not port_expose: port_expose = 8080 port_in = 8081 port_out = 8082 port_ctrl = 8083 logger.debug( f'🔋\tCreate Service for "{name}" with exposed port "{port_expose}"') kubernetes_tools.create( 'service', { 'name': name, 'target': name, 'namespace': namespace, 'port_expose': port_expose, 'port_in': port_in, 'port_out': port_out, 'port_ctrl': port_ctrl, 'type': 'ClusterIP', }, logger=logger, custom_resource_dir=custom_resource_dir, ) logger.debug(f'📝\tCreate ConfigMap for deployment.') kubernetes_tools.create( 'configmap', { 'name': name, 'namespace': namespace, 'data': env, }, logger=logger, custom_resource_dir=None, ) logger.debug( f'🐳\tCreate Deployment for "{name}" with image "{image_name}", replicas {replicas} and init_container {init_container is not None}' ) deployment_params = { 'name': name, 'namespace': namespace, 'image': image_name, 'replicas': replicas, 'command': container_cmd, 'args': container_args, 'port_expose': port_expose, 'port_in': port_in, 'port_out': port_out, 'port_ctrl': port_ctrl, 'pull_policy': pull_policy, } if init_container: template_name = 'deployment-init' deployment_params = {**deployment_params, **init_container} else: template_name = 'deployment' if gpus: deployment_params['device_plugins'] = {'nvidia.com/gpu': gpus} kubernetes_tools.create( template_name, deployment_params, logger=logger, custom_resource_dir=custom_resource_dir, ) logger.debug(f'🔑\tCreate necessary permissions"') kubernetes_tools.create( 'connection-pool-role', { 'namespace': namespace, }, ) kubernetes_tools.create( 'connection-pool-role-binding', { 'namespace': namespace, }, ) return f'{name}.{namespace}.svc'