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
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)
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
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('/')
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)
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))
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))
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)
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))
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
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
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
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