Esempio n. 1
0
def find_model_deployment(model_id, namespace='default'):
    """
    Find model deployment by model id

    :param model_id: model id
    :type model_id: str
    :param namespace: namespace
    :type namespace: str
    :return: :py:class:`kubernetes.client.models.extensions_v1beta1_deployment.ExtensionsV1beta1Deployment`
    """
    client = build_client()

    extension_api = kubernetes.client.ExtensionsV1beta1Api(client)
    all_deployments = extension_api.list_namespaced_deployment(namespace)

    type_label_name = normalize_name(
        legion.containers.headers.DOMAIN_CONTAINER_TYPE)
    type_label_value = 'model'

    model_id_name = normalize_name(legion.containers.headers.DOMAIN_MODEL_ID)
    model_id_value = normalize_name(model_id)

    for deployment in all_deployments.items:
        if deployment.metadata.labels.get(type_label_name) == type_label_value \
                and deployment.metadata.labels.get(model_id_name) == model_id_value:
            return deployment

    return None
Esempio n. 2
0
def init(model_id=None):
    """
    Init metrics from value or from ENV

    :param model_id: model name
    :type model_id: str or None
    :return: None
    """
    global _model_id
    global _model_initialized_from_function

    if _model_id:
        raise Exception('Model already has been initialized')

    if model_id:
        _model_initialized_from_function = True
    else:
        _model_initialized_from_function = False
        deducted_model_id = os.getenv(*legion.config.MODEL_ID)
        if not deducted_model_id:
            raise Exception('Cannot deduct model name. ENV %s is empty' %
                            legion.config.MODEL_ID[0])
        else:
            model_id = deducted_model_id

    model_id = normalize_name(model_id)

    if not model_id:
        raise Exception(
            'Model name string length should be greater that 1 (after normalization)'
        )

    _model_id = normalize_name(model_id)
    send_model_id(model_id)
Esempio n. 3
0
def find_all_models_deployments(namespace='default'):
    """
    Find all models deployments

    :param namespace: namespace
    :type namespace: str or none for all
    :return: list[:py:class:`kubernetes.client.models.extensions_v1beta1_deployment.ExtensionsV1beta1Deployment`]
    """
    client = build_client()

    extension_api = kubernetes.client.ExtensionsV1beta1Api(client)
    if namespace:
        all_deployments = extension_api.list_namespaced_deployment(namespace)
    else:
        all_deployments = extension_api.list_deployment_for_all_namespaces()

    type_label_name = normalize_name(
        legion.containers.headers.DOMAIN_CONTAINER_TYPE)
    type_label_value = 'model'

    model_deployments = [
        deployment for deployment in all_deployments.items
        if deployment.metadata.labels.get(type_label_name) == type_label_value
    ]

    return model_deployments
Esempio n. 4
0
    def __init__(self,
                 model_id,
                 host=None,
                 http_client=None,
                 use_relative_url=False):
        """
        Build client

        :param model_id: model id
        :type model_id: str
        :param host: host that server model HTTP requests (default: from ENV)
        :type host: str or None
        :param http_client: HTTP client (default: requests)
        :type http_client: python class that implements requests-like post & get methods
        :param use_relative_url: use non-full get/post requests (useful for locust)
        :type use_relative_url: bool
        """
        self._model_id = normalize_name(model_id)

        if host:
            self._host = host
        else:
            self._host = os.environ.get(*legion.config.MODEL_SERVER_URL)

        if http_client:
            self._http_client = http_client
        else:
            self._http_client = requests

        if use_relative_url:
            self._host = ''
        else:
            self._host = self._host.rstrip('/')
Esempio n. 5
0
def get_meta_from_docker_labels(labels):
    """
    Build meta fields for kubernetes from docker labels.

    :param labels: docker image labels
    :type labels: dict[str, Any]
    :return: tuple[str, dict[str, str], str, str] -- k8s_name name, labels in DNS-1123 format, model id and version
    """
    model_id = labels.get(legion.containers.headers.DOMAIN_MODEL_ID)
    model_version = labels.get(legion.containers.headers.DOMAIN_MODEL_VERSION)

    if not model_id or not model_version:
        raise legion.k8s.exceptions.IncompatibleLegionModelDockerImage(
            'Legion docker labels for model image are missed: {}'.format(
                ', '.join((
                    legion.containers.headers.DOMAIN_MODEL_ID,
                    legion.containers.headers.DOMAIN_MODEL_VERSION,
                )), ))

    kubernetes_labels = dict()
    kubernetes_labels[LEGION_COMPONENT_LABEL] = LEGION_COMPONENT_NAME_MODEL
    kubernetes_labels[LEGION_SYSTEM_LABEL] = LEGION_SYSTEM_VALUE
    kubernetes_labels[legion.containers.headers.DOMAIN_MODEL_ID] = model_id
    kubernetes_labels[
        legion.containers.headers.DOMAIN_MODEL_VERSION] = model_version

    return ModelContainerMetaInformation(k8s_name=normalize_name(
        'model-{}-{}'.format(model_id, model_version), dns_1035=True),
                                         model_id=model_id,
                                         model_version=model_version,
                                         kubernetes_labels=kubernetes_labels,
                                         kubernetes_annotations=labels)
    def test_name_normalization(self):
        examples = (
            ('Test name!', 'Test-name'),
            ('Test-)1+name!', 'Test-1-name'),
            ('abc-_ .', 'abc---.'),
        )

        for example, valid_answer in examples:
            self.assertEqual(utils.normalize_name(example), valid_answer)
Esempio n. 7
0
def get_metric_name(metric):
    """
    Get metric name on stats server

    :param metric: instance of Metric or custom name
    :type metric: :py:class:`legion.metrics.Metric` or str
    :return: str -- metric name on stats server
    """
    name = metric.value if isinstance(metric, Metric) else str(metric)
    return normalize_name('%s.metrics.%s' % (get_model_id_for_metrics(), name))
Esempio n. 8
0
def send_model_id(model_id):
    """
    Send information about model name to stderr

    :param model_id: model name
    :type model_id: str
    :return: None
    """
    send_header_to_stderr(legion.containers.headers.MODEL_ID,
                          normalize_name(model_id))
Esempio n. 9
0
def normalize_k8s_name(model_id, model_version):
    """
    Convert model id and version to normalize k8s name

    :param model_id: model id
    :type model_id: str or None
    :param model_version: model version
    :type model_version: str or None
    :return str - normalize k8s name
    """
    return normalize_name('model-{}-{}'.format(model_id, model_version),
                          dns_1035=True)
Esempio n. 10
0
def get_metric_name(metric, model_id):
    """
    Get metric name on stats server

    :param metric: instance of Metric or custom name
    :type metric: :py:class:`legion.metrics.Metric` or str
    :param model_id: model ID
    :type model_id: str
    :return: str -- metric name on stats server
    """
    name = metric.value if isinstance(metric, Metric) else str(metric)
    return normalize_name('{}.metrics.{}'.format(model_id, name))
Esempio n. 11
0
def get_meta_from_docker_image(image):
    """
    Build meta fields for kubernetes from docker image. Docker image will be pulled automatically.

    :param image: docker image
    :type image: str
    :return: tuple[str, dict[str, str], str, str] -- deployment name, labels in DNS-1123 format, model id and version
    """
    docker_client = legion.containers.docker.build_docker_client(None)
    try:
        docker_image = docker_client.images.get(image)
    except docker.errors.ImageNotFound:
        docker_image = docker_client.images.pull(image)

    required_headers = [
        legion.containers.headers.DOMAIN_MODEL_ID,
        legion.containers.headers.DOMAIN_MODEL_VERSION,
        legion.containers.headers.DOMAIN_CONTAINER_TYPE
    ]

    model_id = docker_image.labels[legion.containers.headers.DOMAIN_MODEL_ID]
    model_version = docker_image.labels[
        legion.containers.headers.DOMAIN_MODEL_VERSION]

    if any(header not in docker_image.labels for header in required_headers):
        raise Exception('Missed on of %s labels. Available labels: %s' %
                        (', '.join(required_headers), ', '.join(
                            tuple(docker_image.labels.keys()))))

    deployment_name = "model.%s.%s.deployment" % (
        normalize_name(model_id), normalize_name(model_version))

    compatible_labels = {
        normalize_name(k): normalize_name(v)
        for k, v in docker_image.labels.items()
    }

    return deployment_name, compatible_labels, model_id, model_version
Esempio n. 12
0
    def __init__(self,
                 model_id,
                 model_version,
                 token=None,
                 host=None,
                 http_client=None,
                 use_relative_url=False,
                 timeout=None):
        """
        Build client

        :param model_id: model id
        :type model_id: str
        :param model_version: model version
        :type model_version: str
        :param token: API token value to use (default: None)
        :type token: str
        :param host: host that server model HTTP requests (default: from ENV)
        :type host: str or None
        :param http_client: HTTP client (default: requests)
        :type http_client: python class that implements requests-like post & get methods
        :param use_relative_url: use non-full get/post requests (useful for locust)
        :type use_relative_url: bool
        :param timeout: timeout for connections
        :type timeout: int
        """
        self._model_id = normalize_name(model_id)
        self._model_version = model_version
        self._token = token

        if host:
            self._host = host
        else:
            self._host = os.environ.get(*legion.config.MODEL_SERVER_URL)

        if http_client:
            self._http_client = http_client
        else:
            self._http_client = requests

        self._use_relative_url = use_relative_url
        if self._use_relative_url:
            self._host = ''
        else:
            self._host = self._host.rstrip('/')

        self._timeout = timeout
Esempio n. 13
0
def init(model_id, model_version, model_type=legion.pymodel.model.Model.NAME):
    """
    Initialize new model context

    :param model_id: model name
    :type model_id: str
    :param model_version: model version
    :type model_version: str
    :param model_type: type of model, one of MODEL_TYPES names
    :type model_type: str
    :return: object -- instance of Model class
    """
    global _model

    if _model:
        raise Exception('Context already has been defined')

    model_id = normalize_name(model_id)
    if not model_id:
        raise Exception(
            'Model name string length should be greater that 1 (after normalization)'
        )

    builder = [m_type for m_type in MODEL_TYPES if m_type.NAME == model_type]

    if not builder:
        raise Exception(
            'Cannot find model builder for type {}'.format(model_type))

    if len(builder) > 1:
        raise Exception(
            'More then 1 builder have been found for type {}'.format(
                model_type))

    _model = builder[0](model_id, model_version)
    set_properties(_model.properties)

    return _model
Esempio n. 14
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