Ejemplo n.º 1
0
    def get(self, bento: str) -> "Bento":
        """
        Args:
            bento (`str`):
                A BentoService identifier in the format of ``NAME:VERSION``

        Returns:
            :class:`~bentoml.BentoService` metadata from Yatai RPC server.

        Raises:
            BentoMLException: ``bento`` is missing or have invalid format.

        Example::

            from bentoml.yatai.client import get_yatai_client
            yatai_client = get_yatai_client()
            bento_info = yatai_client.repository.get('my_service:version')
        """
        if ':' not in bento:
            raise BentoMLException(
                'BentoService name or version is missing. Please provide in the '
                'format of name:version')
        name, version = bento.split(':')
        result = self.yatai_service.GetBento(
            GetBentoRequest(bento_name=name, bento_version=version))
        if result.status.status_code != yatai_proto.status_pb2.Status.OK:
            error_code, error_message = status_pb_to_error_code_and_message(
                result.status)
            raise BentoMLException(
                f'BentoService {name}:{version} not found - '
                f'{error_code}:{error_message}')
        return result.bento
Ejemplo n.º 2
0
    def add(self, deployment_pb):
        try:
            deployment_spec = deployment_pb.spec
            sagemaker_config = deployment_spec.sagemaker_operator_config
            sagemaker_config.region = (sagemaker_config.region
                                       or get_default_aws_region())
            if not sagemaker_config.region:
                raise InvalidArgument('AWS region is missing')

            ensure_docker_available_or_raise()
            if sagemaker_config is None:
                raise YataiDeploymentException(
                    'Sagemaker configuration is missing.')

            bento_pb = self.yatai_service.GetBento(
                GetBentoRequest(
                    bento_name=deployment_spec.bento_name,
                    bento_version=deployment_spec.bento_version,
                ))
            if bento_pb.bento.uri.type not in (BentoUri.LOCAL, BentoUri.S3):
                raise BentoMLException(
                    'BentoML currently not support {} repository'.format(
                        BentoUri.StorageType.Name(bento_pb.bento.uri.type)))
            return self._add(deployment_pb, bento_pb, bento_pb.bento.uri.uri)

        except BentoMLException as error:
            deployment_pb.state.state = DeploymentState.ERROR
            deployment_pb.state.error_message = (
                f'Error creating SageMaker deployment: {str(error)}')
            return ApplyDeploymentResponse(status=error.status_proto,
                                           deployment=deployment_pb)
Ejemplo n.º 3
0
    def get(self, bento):
        """
        Get a BentoService info

        Args:
            bento: a BentoService identifier in the format of NAME:VERSION

        Returns:
            bentoml.yatai.proto.repository_pb2.Bento

        Example:

        >>> yatai_client = get_yatai_client()
        >>> bento_info = yatai_client.repository.get('my_service:version')
        """
        track('py-api-get')
        if ':' not in bento:
            raise BentoMLException(
                'BentoService name or version is missing. Please provide in the '
                'format of name:version'
            )
        name, version = bento.split(':')
        result = self.yatai_service.GetBento(
            GetBentoRequest(bento_name=name, bento_version=version)
        )
        if result.status.status_code != yatai_proto.status_pb2.Status.OK:
            error_code, error_message = status_pb_to_error_code_and_message(
                result.status
            )
            raise BentoMLException(
                f'BentoService {name}:{version} not found - '
                f'{error_code}:{error_message}'
            )
        return result.bento
Ejemplo n.º 4
0
    def update(self, deployment_pb, previous_deployment):
        try:
            deployment_spec = deployment_pb.spec
            bento_pb = self.yatai_service.GetBento(
                GetBentoRequest(
                    bento_name=deployment_spec.bento_name,
                    bento_version=deployment_spec.bento_version,
                )
            )
            if bento_pb.bento.uri.type not in (BentoUri.LOCAL, BentoUri.S3):
                raise BentoMLException(
                    'BentoML currently not support {} repository'.format(
                        BentoUri.StorageType.Name(bento_pb.bento.uri.type)
                    )
                )

            return self._update(
                deployment_pb, previous_deployment, bento_pb, bento_pb.bento.uri.uri
            )
        except BentoMLException as error:
            deployment_pb.state.state = DeploymentState.ERROR
            deployment_pb.state.error_message = f'Error: {str(error)}'
            return ApplyDeploymentResponse(
                status=error.status_code, deployment_pb=deployment_pb
            )
Ejemplo n.º 5
0
    def add(self, deployment_pb):
        try:
            deployment_spec = deployment_pb.spec
            deployment_spec.aws_lambda_operator_config.region = (
                deployment_spec.aws_lambda_operator_config.region
                or get_default_aws_region()
            )
            if not deployment_spec.aws_lambda_operator_config.region:
                raise InvalidArgument('AWS region is missing')

            bento_pb = self.yatai_service.GetBento(
                GetBentoRequest(
                    bento_name=deployment_spec.bento_name,
                    bento_version=deployment_spec.bento_version,
                )
            )
            if bento_pb.bento.uri.type not in (BentoUri.LOCAL, BentoUri.S3):
                raise BentoMLException(
                    'BentoML currently not support {} repository'.format(
                        BentoUri.StorageType.Name(bento_pb.bento.uri.type)
                    )
                )

            return self._add(deployment_pb, bento_pb, bento_pb.bento.uri.uri)
        except BentoMLException as error:
            deployment_pb.state.state = DeploymentState.ERROR
            deployment_pb.state.error_message = f'Error: {str(error)}'
            return ApplyDeploymentResponse(
                status=error.status_proto, deployment=deployment_pb
            )
Ejemplo n.º 6
0
    def add(self, deployment_pb):
        try:
            deployment_spec = deployment_pb.spec
            deployment_spec.aws_ec2_operator_config.region = (
                deployment_spec.aws_ec2_operator_config.region
                or get_default_aws_region()
            )
            if not deployment_spec.aws_ec2_operator_config.region:
                raise InvalidArgument("AWS region is missing")

            ensure_sam_available_or_raise()
            ensure_docker_available_or_raise()

            bento_pb = self.yatai_service.GetBento(
                GetBentoRequest(
                    bento_name=deployment_spec.bento_name,
                    bento_version=deployment_spec.bento_version,
                )
            )

            if bento_pb.bento.uri.type not in (BentoUri.LOCAL, BentoUri.S3):
                raise BentoMLException(
                    "BentoML currently not support {} repository".format(
                        BentoUri.StorageType.Name(bento_pb.bento.uri.type)
                    )
                )
            bento_path = bento_pb.bento.uri.uri

            return self._add(deployment_pb, bento_pb, bento_path)
        except BentoMLException as error:
            deployment_pb.state.state = DeploymentState.ERROR
            deployment_pb.state.error_message = f"Error: {str(error)}"
            return ApplyDeploymentResponse(
                status=error.status_proto, deployment=deployment_pb
            )
Ejemplo n.º 7
0
 def get(self, bento):
     track('py-api-get')
     if ':' not in bento:
         raise BentoMLException(
             'BentoService name or version is missing. Please provide in the '
             'format of name:version')
     name, version = bento.split(':')
     result = self.yatai_service.GetBento(
         GetBentoRequest(bento_name=name, bento_version=version))
     if result.status.status_code != yatai_proto.status_pb2.Status.OK:
         error_code, error_message = status_pb_to_error_code_and_message(
             result.status)
         raise BentoMLException(
             f'BentoService {name}:{version} not found - '
             f'{error_code}:{error_message}')
     return result.bento
Ejemplo n.º 8
0
 def update(self, deployment_pb, previous_deployment):
     try:
         bento_repo_pb = self.yatai_service.GetBento(
             GetBentoRequest(
                 bento_name=deployment_pb.spec.bento_name,
                 bento_version=deployment_pb.spec.bento_version,
             ))
         bento_pb = bento_repo_pb.bento
         return self._update(deployment_pb, previous_deployment, bento_pb,
                             bento_pb.uri.uri)
     except BentoMLException as error:
         deployment_pb.state.state = DeploymentState.ERROR
         deployment_pb.state.error_message = (
             f'Encounter error when updating Azure Functions deployment: '
             f'{str(error)}')
         return ApplyDeploymentResponse(status=error.status_proto,
                                        deployment=deployment_pb)
Ejemplo n.º 9
0
 def add(self, deployment_pb):
     try:
         deployment_spec = deployment_pb.spec
         if not deployment_spec.azure_functions_operator_config.location:
             raise YataiDeploymentException(
                 'Azure Functions parameter "location" is missing')
         bento_repo_pb = self.yatai_service.GetBento(
             GetBentoRequest(
                 bento_name=deployment_spec.bento_name,
                 bento_version=deployment_spec.bento_version,
             ))
         return self._add(deployment_pb, bento_repo_pb.bento,
                          bento_repo_pb.bento.uri.uri)
     except BentoMLException as error:
         deployment_pb.state.state = DeploymentState.ERROR
         deployment_pb.state.error_message = f'Error: {str(error)}'
         return ApplyDeploymentResponse(status=error.status_proto,
                                        deployment=deployment_pb)
Ejemplo n.º 10
0
    def upload_from_dir(self, saved_bento_path, labels=None):
        bento_service_metadata = load_bento_service_metadata(saved_bento_path)
        if labels:
            _validate_labels(labels)
            bento_service_metadata.labels.update(labels)

        get_bento_response = self.yatai_service.GetBento(
            GetBentoRequest(
                bento_name=bento_service_metadata.name,
                bento_version=bento_service_metadata.version,
            ))
        if get_bento_response.status.status_code == status_pb2.Status.OK:
            raise BentoMLException(
                "BentoService bundle {}:{} already registered in repository. Reset "
                "BentoService version with BentoService#set_version or bypass BentoML's"
                " model registry feature with BentoService#save_to_dir".format(
                    bento_service_metadata.name,
                    bento_service_metadata.version))
        elif get_bento_response.status.status_code != status_pb2.Status.NOT_FOUND:
            raise BentoMLException(
                'Failed accessing YataiService. {error_code}:'
                '{error_message}'.format(
                    error_code=Status.Name(
                        get_bento_response.status.status_code),
                    error_message=get_bento_response.status.error_message,
                ))
        request = AddBentoRequest(
            bento_name=bento_service_metadata.name,
            bento_version=bento_service_metadata.version,
        )
        response = self.yatai_service.AddBento(request)

        if response.status.status_code != status_pb2.Status.OK:
            raise BentoMLException(
                "Error adding BentoService bundle to repository: {}:{}".format(
                    Status.Name(response.status.status_code),
                    response.status.error_message,
                ))

        if response.uri.type == BentoUri.LOCAL:
            if os.path.exists(response.uri.uri):
                # due to copytree dst must not already exist
                shutil.rmtree(response.uri.uri)
            shutil.copytree(saved_bento_path, response.uri.uri)

            self._update_bento_upload_progress(bento_service_metadata)

            logger.info(
                "BentoService bundle '%s:%s' saved to: %s",
                bento_service_metadata.name,
                bento_service_metadata.version,
                response.uri.uri,
            )
            # Return URI to saved bento in repository storage
            return response.uri.uri
        elif response.uri.type == BentoUri.S3 or response.uri.type == BentoUri.GCS:
            uri_type = 'S3' if response.uri.type == BentoUri.S3 else 'GCS'
            self._update_bento_upload_progress(bento_service_metadata,
                                               UploadStatus.UPLOADING, 0)

            fileobj = io.BytesIO()
            with tarfile.open(mode="w:gz", fileobj=fileobj) as tar:
                tar.add(saved_bento_path, arcname=bento_service_metadata.name)
            fileobj.seek(0, 0)

            if response.uri.type == BentoUri.S3:
                http_response = requests.put(response.uri.s3_presigned_url,
                                             data=fileobj)
            elif response.uri.type == BentoUri.GCS:
                http_response = requests.put(response.uri.gcs_presigned_url,
                                             data=fileobj)

            if http_response.status_code != 200:
                self._update_bento_upload_progress(bento_service_metadata,
                                                   UploadStatus.ERROR)
                raise BentoMLException(
                    f"Error saving BentoService bundle to {uri_type}."
                    f"{http_response.status_code}: {http_response.text}")

            self._update_bento_upload_progress(bento_service_metadata)

            logger.info(
                "Successfully saved BentoService bundle '%s:%s' to {uri_type}: %s",
                bento_service_metadata.name,
                bento_service_metadata.version,
                response.uri.uri,
            )

            return response.uri.uri
        else:
            raise BentoMLException(
                f"Error saving Bento to target repository, URI type {response.uri.type}"
                f" at {response.uri.uri} not supported")
Ejemplo n.º 11
0
    def describe(self, deployment_pb):
        try:
            deployment_spec = deployment_pb.spec
            lambda_deployment_config = deployment_spec.aws_lambda_operator_config
            lambda_deployment_config.region = (lambda_deployment_config.region
                                               or get_default_aws_region())
            if not lambda_deployment_config.region:
                raise InvalidArgument('AWS region is missing')

            bento_pb = self.yatai_service.GetBento(
                GetBentoRequest(
                    bento_name=deployment_spec.bento_name,
                    bento_version=deployment_spec.bento_version,
                ))
            bento_service_metadata = bento_pb.bento.bento_service_metadata
            api_names = ([lambda_deployment_config.api_name]
                         if lambda_deployment_config.api_name else
                         [api.name for api in bento_service_metadata.apis])

            try:
                cf_client = boto3.client('cloudformation',
                                         lambda_deployment_config.region)
                stack_name = generate_aws_compatible_string(
                    '{ns}-{name}'.format(ns=deployment_pb.namespace,
                                         name=deployment_pb.name))
                cloud_formation_stack_result = cf_client.describe_stacks(
                    StackName=stack_name)
                stack_result = cloud_formation_stack_result.get('Stacks')[0]
                # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/\
                # using-cfn-describing-stacks.html
                success_status = ['CREATE_COMPLETE', 'UPDATE_COMPLETE']
                if stack_result['StackStatus'] in success_status:
                    if stack_result.get('Outputs'):
                        outputs = stack_result['Outputs']
                    else:
                        return DescribeDeploymentResponse(
                            status=Status.ABORTED(
                                '"Outputs" field is not present'),
                            state=DeploymentState(
                                state=DeploymentState.ERROR,
                                error_message='"Outputs" field is not present',
                            ),
                        )
                elif stack_result[
                        'StackStatus'] in FAILED_CLOUDFORMATION_STACK_STATUS:
                    state = DeploymentState(state=DeploymentState.FAILED)
                    state.timestamp.GetCurrentTime()
                    return DescribeDeploymentResponse(status=Status.OK(),
                                                      state=state)
                else:
                    state = DeploymentState(state=DeploymentState.PENDING)
                    state.timestamp.GetCurrentTime()
                    return DescribeDeploymentResponse(status=Status.OK(),
                                                      state=state)
            except Exception as error:  # pylint: disable=broad-except
                state = DeploymentState(state=DeploymentState.ERROR,
                                        error_message=str(error))
                state.timestamp.GetCurrentTime()
                return DescribeDeploymentResponse(status=Status.INTERNAL(
                    str(error)),
                                                  state=state)
            outputs = {o['OutputKey']: o['OutputValue'] for o in outputs}
            info_json = {}

            if 'EndpointUrl' in outputs:
                info_json['endpoints'] = [
                    outputs['EndpointUrl'] + '/' + api_name
                    for api_name in api_names
                ]
            if 'S3Bucket' in outputs:
                info_json['s3_bucket'] = outputs['S3Bucket']

            state = DeploymentState(state=DeploymentState.RUNNING,
                                    info_json=json.dumps(info_json))
            state.timestamp.GetCurrentTime()
            return DescribeDeploymentResponse(status=Status.OK(), state=state)
        except BentoMLException as error:
            return DescribeDeploymentResponse(status=error.status_proto)
Ejemplo n.º 12
0
 def get(self, bento_name, bento_version=None):
     get_bento_request = GetBentoRequest(bento_name=bento_name,
                                         bento_version=bento_version)
     return self.yatai_service.GetBento(get_bento_request)
Ejemplo n.º 13
0
    def describe(self, deployment_pb):
        try:
            deployment_spec = deployment_pb.spec
            ec2_deployment_config = deployment_spec.aws_ec2_operator_config
            ec2_deployment_config.region = (ec2_deployment_config.region
                                            or get_default_aws_region())
            if not ec2_deployment_config.region:
                raise InvalidArgument("AWS region is missing")

            bento_pb = self.yatai_service.GetBento(
                GetBentoRequest(
                    bento_name=deployment_spec.bento_name,
                    bento_version=deployment_spec.bento_version,
                ))
            bento_service_metadata = bento_pb.bento.bento_service_metadata
            api_names = [api.name for api in bento_service_metadata.apis]

            deployment_stack_name = generate_aws_compatible_string(
                "btml-stack-{namespace}-{name}".format(
                    namespace=deployment_pb.namespace,
                    name=deployment_pb.name))
            try:
                cf_client = boto3.client("cloudformation",
                                         ec2_deployment_config.region)
                cloudformation_stack_result = cf_client.describe_stacks(
                    StackName=deployment_stack_name)
                stack_result = cloudformation_stack_result.get("Stacks")[0]

                if stack_result.get("Outputs"):
                    outputs = stack_result.get("Outputs")
                else:
                    return DescribeDeploymentResponse(
                        status=Status.ABORTED(
                            '"Outputs" field is not present'),
                        state=DeploymentState(
                            state=DeploymentState.ERROR,
                            error_message='"Outputs" field is not present',
                        ),
                    )

                if stack_result[
                        "StackStatus"] in FAILED_CLOUDFORMATION_STACK_STATUS:
                    state = DeploymentState(state=DeploymentState.FAILED)
                    return DescribeDeploymentResponse(status=Status.OK(),
                                                      state=state)

            except Exception as error:  # pylint: disable=broad-except
                state = DeploymentState(state=DeploymentState.ERROR,
                                        error_message=str(error))
                return DescribeDeploymentResponse(status=Status.INTERNAL(
                    str(error)),
                                                  state=state)

            info_json = {}
            outputs = {o["OutputKey"]: o["OutputValue"] for o in outputs}
            if "AutoScalingGroup" in outputs:
                info_json[
                    "InstanceDetails"] = get_instance_ip_from_scaling_group(
                        [outputs["AutoScalingGroup"]],
                        ec2_deployment_config.region)
                info_json["Endpoints"] = get_endpoints_from_instance_address(
                    info_json["InstanceDetails"], api_names)
            if "S3Bucket" in outputs:
                info_json["S3Bucket"] = outputs["S3Bucket"]
            if "TargetGroup" in outputs:
                info_json["TargetGroup"] = outputs["TargetGroup"]
            if "Url" in outputs:
                info_json["Url"] = outputs["Url"]

            healthy_target = get_healthy_target(outputs["TargetGroup"],
                                                ec2_deployment_config.region)
            if healthy_target:
                deployment_state = DeploymentState.RUNNING
            else:
                deployment_state = DeploymentState.PENDING
            state = DeploymentState(state=deployment_state,
                                    info_json=json.dumps(info_json))
            return DescribeDeploymentResponse(status=Status.OK(), state=state)

        except BentoMLException as error:
            return DescribeDeploymentResponse(status=error.status_proto)
Ejemplo n.º 14
0
    def upload_from_dir(self,
                        saved_bento_path: str,
                        labels: Dict = None) -> "BentoUri":
        from bentoml.yatai.db.stores.label import _validate_labels

        bento_service_metadata = load_bento_service_metadata(saved_bento_path)
        if labels:
            _validate_labels(labels)
            bento_service_metadata.labels.update(labels)

        get_bento_response = self.yatai_service.GetBento(
            GetBentoRequest(
                bento_name=bento_service_metadata.name,
                bento_version=bento_service_metadata.version,
            ))
        if get_bento_response.status.status_code == status_pb2.Status.OK:
            raise BentoMLException(
                "BentoService bundle {}:{} already registered in repository. Reset "
                "BentoService version with BentoService#set_version or bypass BentoML's"
                " model registry feature with BentoService#save_to_dir".format(
                    bento_service_metadata.name,
                    bento_service_metadata.version))
        elif get_bento_response.status.status_code != status_pb2.Status.NOT_FOUND:
            raise BentoMLException(
                'Failed accessing YataiService. {error_code}:'
                '{error_message}'.format(
                    error_code=Status.Name(
                        get_bento_response.status.status_code),
                    error_message=get_bento_response.status.error_message,
                ))
        request = AddBentoRequest(
            bento_name=bento_service_metadata.name,
            bento_version=bento_service_metadata.version,
        )
        response = self.yatai_service.AddBento(request)

        if response.status.status_code != status_pb2.Status.OK:
            raise BentoMLException(
                "Error adding BentoService bundle to repository: {}:{}".format(
                    Status.Name(response.status.status_code),
                    response.status.error_message,
                ))

        if response.uri.type == BentoUri.LOCAL:
            # When using Yatai backed by File System repository,
            # if Yatai is a local instance, copy the files directly.
            # Otherwise, use UploadBento RPC to stream files to remote Yatai server
            if is_remote_yatai(self.yatai_service):
                self._upload_bento(
                    bento_service_metadata.name,
                    bento_service_metadata.version,
                    saved_bento_path,
                )
                update_bento_service = UpdateBentoRequest(
                    bento_name=bento_service_metadata.name,
                    bento_version=bento_service_metadata.version,
                    service_metadata=bento_service_metadata,
                )
                self.yatai_service.UpdateBento(update_bento_service)
            else:
                if os.path.exists(response.uri.uri):
                    raise BentoMLException(
                        f'Bento bundle directory {response.uri.uri} already exist'
                    )
                shutil.copytree(saved_bento_path, response.uri.uri)
                upload_status = UploadStatus.DONE

                self._update_bento_upload_progress(bento_service_metadata,
                                                   status=upload_status)

            logger.info(
                "BentoService bundle '%s:%s' saved to: %s",
                bento_service_metadata.name,
                bento_service_metadata.version,
                response.uri.uri,
            )
            # Return URI to saved bento in repository storage
            return response.uri.uri
        elif response.uri.type == BentoUri.S3 or response.uri.type == BentoUri.GCS:
            uri_type = 'S3' if response.uri.type == BentoUri.S3 else 'GCS'
            self._update_bento_upload_progress(bento_service_metadata,
                                               UploadStatus.UPLOADING, 0)

            fileobj = io.BytesIO()
            with tarfile.open(mode="w:gz", fileobj=fileobj) as tar:
                tar.add(saved_bento_path, arcname=bento_service_metadata.name)
            fileobj.seek(0, 0)

            if response.uri.type == BentoUri.S3:
                http_response = requests.put(response.uri.s3_presigned_url,
                                             data=fileobj)
            elif response.uri.type == BentoUri.GCS:
                http_response = requests.put(response.uri.gcs_presigned_url,
                                             data=fileobj)

            if http_response.status_code != 200:
                self._update_bento_upload_progress(bento_service_metadata,
                                                   UploadStatus.ERROR)
                raise BentoMLException(
                    f"Error saving BentoService bundle to {uri_type}."
                    f"{http_response.status_code}: {http_response.text}")

            self._update_bento_upload_progress(bento_service_metadata)

            logger.info(
                "Successfully saved BentoService bundle '%s:%s' to {uri_type}: %s",
                bento_service_metadata.name,
                bento_service_metadata.version,
                response.uri.uri,
            )

            return response.uri.uri
        else:
            raise BentoMLException(
                f"Error saving Bento to target repository, URI type {response.uri.type}"
                f" at {response.uri.uri} not supported")