Пример #1
0
class ModelLocalContainerExecutionContext:
    """
    Context manager for building and testing models
    Hides build and deploy process and provides model client
    """

    def __init__(self, model_image):
        """
        Create context

        :param model_image: docker image id
        :type model_image: str
        """
        self._image_id = model_image
        self._docker_client = legion.containers.docker.build_docker_client(None)

        self._image = self._docker_client.images.get(self._image_id)

        self._model_id = self._image.labels[legion.containers.headers.DOMAIN_MODEL_ID]
        self._model_version = self._image.labels[legion.containers.headers.DOMAIN_MODEL_VERSION]

        self.container = None
        self.container_id = None
        self.model_port = None
        self.client = None

    def __enter__(self):
        """
        Enter into context

        :return: self
        """
        try:
            LOGGER.info('Deploying model {} v {} from image {}'.format(self._model_id,
                                                                       self._model_version,
                                                                       self._image_id))

            additional_environment = {
                **build_environ_for_test_environments()
            }

            container_labels = legion.containers.docker.generate_docker_labels_for_container(self._image)

            ports = {'{}/tcp'.format(os.getenv(*legion.config.LEGION_PORT)): 0}

            self.container = self._docker_client.containers.run(self._image_id,
                                                                stdout=True,
                                                                stderr=True,
                                                                detach=True,  # in the background
                                                                remove=True,  # remove automatically after kill
                                                                environment=additional_environment,
                                                                labels=container_labels,
                                                                ports=ports)
            self.container_id = self.container.id

            LOGGER.info('Model image has been deployed')

            wait = 3
            LOGGER.info('Waiting {} sec'.format(wait))
            time.sleep(wait)

            self.container = self._docker_client.containers.get(self.container_id)
            if self.container.status != 'running':
                print_docker_container_logs(self.container)
                raise Exception('Invalid container state: {}'.format(self.container.status))

            LOGGER.info('OK')

            LOGGER.info('Detecting bound ports')
            ports_information = [item for sublist in self.container.attrs['NetworkSettings']['Ports'].values()
                                 for item in sublist]
            ports_information = [int(x['HostPort']) for x in ports_information]
            LOGGER.info('Detected ports: {}'.format(', '.join(str(port) for port in ports_information)))

            if len(ports_information) != 1:
                raise Exception('Should be only one bound port')
            self.model_port = ports_information[0]
            LOGGER.info('Model port: {}'.format(self.model_port))

            LOGGER.info('Building client')
            url = 'http://{}:{}'.format('localhost', self.model_port)
            LOGGER.info('Target URI is {}'.format(url))
            self.client = ModelClient(self._model_id, self._model_version, host=url)

            LOGGER.info('Getting model information')
            self.model_information = self.client.info()

            return self
        except Exception as build_exception:
            self.__exit__(build_exception)

    def __exit__(self, *args):
        """
        Exit from context with cleaning fs temp directory, temporary container and image

        :param args: list of arguements
        :return: None
        """
        if self.container:
            try:
                LOGGER.info('Finding container')
                container = self._docker_client.containers.get(self.container.id)
                print_docker_container_logs(container)

                try:
                    LOGGER.info('Killing container')
                    container.kill()
                except Exception as container_kill_exception:
                    LOGGER.info('Cannot kill container: {}'.format(container_kill_exception))
            except Exception as removing_exception:
                LOGGER.exception('Cannot remove container: {}'.format(removing_exception),
                                 exc_info=removing_exception)

        if args[0]:
            raise args[0]
Пример #2
0
class ModelTestDeployment:
    """
    Context manager for building and testing models
    Hides build and deploy process and provides model client
    """
    def __init__(self, model_id, model_version, model_builder, wheel):
        """
        Create context

        :param model_id: id of model (uses for building model and model client)
        :type model_id: str
        :param model_version: version of model (passes to model builder)
        :param model_builder: str
        :param wheel: path to actual package wheel
        :type wheel: str
        """
        self._model_id = model_id
        self._model_version = model_version
        self._model_builder = model_builder
        self._wheel = wheel
        self._docker_client = legion.containers.docker.build_docker_client(
            None)

        self._temp_directory = tempfile.mkdtemp()
        self._model_path = os.path.join(self._temp_directory, 'temp.model')

        self.image = None
        self.container = None
        self.container_id = None
        self.model_port = None
        self.client = None

    def __enter__(self):
        """
        Enter into context

        :return: self
        """
        try:
            print('Building model file {} v {}'.format(self._model_id,
                                                       self._model_version))
            legion.model.model_id.init(self._model_id)
            self._model_builder(self._model_path, self._model_version)
            print('Model file has been built')

            print('Building model image {} v {}'.format(
                self._model_id, self._model_version))
            args = Namespace(model_file=self._model_path,
                             model_id=None,
                             base_docker_image=None,
                             docker_network=None,
                             python_package=self._wheel,
                             python_package_version=None,
                             python_repository=None,
                             docker_image_tag=None,
                             serving=deploy.VALID_SERVING_WORKERS[1],
                             push_to_registry=None)
            self.image = deploy.build_model(args)
            print('Model image has been built')

            print('Deploying model {} v {}'.format(self._model_id,
                                                   self._model_version))
            additional_environment = {
                legion.config.REGISTER_ON_GRAFANA[0]: 'false',
                legion.config.REGISTER_ON_CONSUL[0]: 'false',
            }
            with patch_environ(additional_environment):
                args = Namespace(model_id=self._model_id,
                                 docker_image=None,
                                 docker_network=None,
                                 grafana_server=None,
                                 grafana_user=None,
                                 grafana_password=None,
                                 expose_model_port=0)
                self.container = deploy.deploy_model(args)
                self.container_id = self.container.id
            print('Model image has been deployed')

            wait = 3
            print('Waiting {} sec'.format(wait))
            time.sleep(wait)
            self.container = self._docker_client.containers.get(
                self.container_id)
            if self.container.status != 'running':
                try:
                    logs = self.container.logs().decode('utf-8')

                    print('--- CONTAINER LOGS ---')
                    print(logs)
                except Exception:
                    print('Cannot get logs of container')

                raise Exception('Invalid container state: {}'.format(
                    self.container.status))
            print('OK')

            print('Detecting bound ports')
            ports_information = [
                item for sublist in self.container.attrs['NetworkSettings']
                ['Ports'].values() for item in sublist
            ]
            ports_information = [int(x['HostPort']) for x in ports_information]
            print('Detected ports: {}'.format(', '.join(
                str(port) for port in ports_information)))

            if len(ports_information) != 1:
                raise Exception('Should be only one bound port')
            self.model_port = ports_information[0]
            print('Model port: {}'.format(self.model_port))

            print('Building client')
            url = 'http://{}:{}'.format('localhost', self.model_port)
            print('Target URI is {}'.format(url))
            self.client = ModelClient(self._model_id, url)

            print('Getting model information')
            self.model_information = self.client.info()

            return self
        except Exception as build_exception:
            self.__exit__(build_exception)

    def __exit__(self, *args):
        """
        Exit from context with cleaning fs temp directory, temporary container and image

        :param args: list of arguements
        :return: None
        """
        print('Removing temporary directory')
        remove_directory(self._temp_directory)

        if self.container:
            try:
                print('Finding container')
                container = self._docker_client.containers.get(
                    self.container.id)
                print('Stopping container')
                container.stop()
                print('Removing container')
                container.remove()
            except Exception as removing_exception:
                print('Cannot remove container: {}'.format(removing_exception))

        if self.image:
            try:
                print('Removing image')
                self._docker_client.images.remove(self.image.id)
            except Exception as removing_exception:
                print('Cannot remove image: {}'.format(removing_exception))

        if args[0]:
            raise args[0]
Пример #3
0
def inspect(cluster_config, cluster_secrets, namespace=None):
    """
    Get model deployments information

    :param cluster_config: cluster configuration
    :type cluster_config: dict
    :param cluster_secrets: secrets with credentials
    :type cluster_secrets: dict[str, str]
    :param namespace:
    :return: list[:py:class:`legion.containers.k8s.ModelDeploymentDescription`]
    """
    deployments = find_all_models_deployments(namespace)
    models = []

    edge_url = 'http://%s:%d' % (cluster_config['edge']['domain'],
                                 cluster_config['edge']['port'])

    for deployment in deployments:
        ready_replicas = deployment.status.ready_replicas
        if not ready_replicas:
            ready_replicas = 0
        replicas = deployment.status.replicas
        if not replicas:
            replicas = 0
        status = 'ok'

        if ready_replicas == 0:
            status = 'fail'
        elif replicas > ready_replicas > 0:
            status = 'warning'

        container_image = deployment.spec.template.spec.containers[0].image

        model_name = deployment.metadata.labels.get(
            normalize_name(legion.containers.headers.DOMAIN_MODEL_ID), '?')
        model_version = deployment.metadata.labels.get(
            normalize_name(legion.containers.headers.DOMAIN_MODEL_VERSION),
            '?')

        model_api_info = {'host': edge_url}

        try:
            model_client = ModelClient(model_name, host=edge_url)
            model_api_info['result'] = model_client.info()
            model_api_ok = True
        except Exception as model_api_exception:
            model_api_info['exception'] = str(model_api_exception)
            model_api_ok = False

        model_information = ModelDeploymentDescription(
            status=status,
            model=model_name,
            version=model_version,
            image=container_image,
            scale=replicas,
            ready_replicas=ready_replicas,
            namespace=deployment.metadata.namespace,
            deployment='deployment',
            model_api_ok=model_api_ok,
            model_api_info=model_api_info)
        models.append(model_information)

    return models