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
def pull(self, bento): """ Pull a BentoService from a remote yatai service. The BentoService will be saved and registered with local yatai service. Args: bento: a BentoService identifier in the form of NAME:VERSION Returns: BentoService saved path Example: >>> client = get_yatai_client('127.0.0.1:50051') >>> saved_path = client.repository.pull('MyService:') """ track('py-api-pull') bento_pb = self.get(bento) with TempDirectory() as tmpdir: # Create a non-exist directory for safe_retrieve target_bundle_path = os.path.join(tmpdir, 'bundle') self.download_to_directory(bento_pb, target_bundle_path) from bentoml.yatai.client import get_yatai_client labels = (dict(bento_pb.bento_service_metadata.labels) if bento_pb.bento_service_metadata.labels else None) local_yc = get_yatai_client() return local_yc.repository.upload_from_dir(target_bundle_path, labels=labels)
def track_deployment_delete(deployment_operator, created_at, force_delete=False): operator_name = DeploymentSpec.DeploymentOperator.Name(deployment_operator) up_time = int((datetime.utcnow() - created_at.ToDatetime()).total_seconds()) track( f'deployment-{operator_name}-stop', {'up_time': up_time, 'force_delete': force_delete}, )
def containerize(self, bento, tag=None, build_args=None, push=False): """ Create a container image from a BentoService. Args: bento: string tag: string build_args: dict push: boolean Returns: Image tag: String """ track('py-api-containerize') if ':' not in bento: raise BentoMLException( 'BentoService name or version is missing. Please provide in the ' 'format of name:version') name, version = bento.split(':') containerize_request = ContainerizeBentoRequest( bento_name=name, bento_version=version, tag=tag, build_args=build_args, push=push, ) result = self.yatai_service.ContainerizeBento(containerize_request) 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'Failed to containerize {bento} - {error_code}:{error_message}' ) return result.tag
def load(self, bento): """ Load bento service from bento tag or from a bento bundle path. Args: bento: string, Returns: BentoService instance Example: >>> yatai_client = get_yatai_client() >>> # Load BentoService bases on bento tag. >>> bento = yatai_client.repository.load('Service_name:version') >>> # Load BentoService from bento bundle path >>> bento = yatai_client.repository.load('/path/to/bento/bundle') >>> # Load BentoService from s3 storage >>> bento = yatai_client.repository.load('s3://bucket/path/bundle.tar.gz') """ track('py-api-load') if os.path.isdir(bento) or is_s3_url(bento) or is_gcs_url(bento): saved_bundle_path = bento else: bento_pb = self.get(bento) saved_bundle_path = resolve_bento_bundle_uri(bento_pb) svc = load_from_dir(saved_bundle_path) return svc
def push(self, bento, labels=None): """ Push a local BentoService to a remote yatai server. Args: bento: a BentoService identifier in the format of NAME:VERSION labels: optional. List of labels for the BentoService. Returns: BentoService saved path Example: >>> svc = MyBentoService() >>> svc.save() >>> >>> remote_yatai_client = get_yatai_client('http://remote.yatai.service:50050') >>> bento = f'{svc.name}:{svc.version}' >>> remote_saved_path= remote_yatai_client.repository.push(bento) """ track('py-api-push') from bentoml.yatai.client import get_yatai_client local_yc = get_yatai_client() local_bento_pb = local_yc.repository.get(bento) if local_bento_pb.uri.s3_presigned_url: bento_bundle_path = local_bento_pb.uri.s3_presigned_url elif local_bento_pb.uri.gcs_presigned_url: bento_bundle_path = local_bento_pb.uri.gcs_presigned_url else: bento_bundle_path = local_bento_pb.uri.uri return self.upload_from_dir(bento_bundle_path, labels=labels)
def pull(self, bento): """ Pull a BentoService from a remote yatai service. The BentoService will be saved and registered with local yatai service. Args: bento: a BentoService identifier in the form of NAME:VERSION Returns: BentoService saved path Example: >>> remote_yatai_client = get_yatai_client('remote_yatai_service_address') >>> saved_path = remote_yatai_client.repository.pull('MyService:version') """ track('py-api-pull') if isinstance(self.yatai_service, YataiService): raise BentoMLException('need set yatai_service_url') bento_pb = self.get(bento) with TempDirectory() as tmpdir: # Create a non-exist directory for safe_retrieve target_bundle_path = os.path.join(tmpdir, 'bundle') self.download_to_directory(bento_pb, target_bundle_path) from bentoml.yatai.client import get_yatai_client local_yc = get_yatai_client() return local_yc.repository.upload_from_dir(target_bundle_path)
def prune(self, bento_name=None, labels=None): """ Delete all BentoServices that matches the specified criteria Args: bento_name: optional labels: optional Example: >>> yatai_client = get_yatai_client() >>> # Delete all bento services >>> yatai_client.repository.prune() >>> # Delete bento services that matches with the label `ci=failed` >>> yatai_client.repository.prune(labels='ci=failed') """ track('py-api-prune') list_bentos_result = self.list(bento_name=bento_name, labels=labels,) if list_bentos_result.status.status_code != yatai_proto.status_pb2.Status.OK: error_code, error_message = status_pb_to_error_code_and_message( list_bentos_result.status ) raise BentoMLException(f'{error_code}:{error_message}') for bento in list_bentos_result.bentos: bento_tag = f'{bento.name}:{bento.version}' try: self.delete(bento_tag) except BentoMLException as e: logger.error(f'Failed to delete Bento {bento_tag}: {e}')
def delete(self, bento): """ Delete bento Args: bento: a BentoService identifier in the format of NAME:VERSION Example: >>> >>> yatai_client = get_yatai_client() >>> yatai_client.repository.delete('my_service:version') """ track('py-api-delete') 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.DangerouslyDeleteBento( DangerouslyDeleteBentoRequest(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'Failed to delete Bento {bento} {error_code}:{error_message}' )
def push(self, bento, labels=None): """ Push a local BentoService to a remote yatai server. Args: bento: a BentoService identifier in the format of NAME:VERSION labels: optional. List of labels for the BentoService. Returns: BentoService saved path """ track('py-api-push') if isinstance(self.yatai_service, YataiService): raise BentoMLException('need set yatai_service_url') from bentoml.yatai.client import get_yatai_client local_yc = get_yatai_client() local_bento_pb = local_yc.repository.get(bento) if local_bento_pb.uri.s3_presigned_url: bento_bundle_path = local_bento_pb.uri.s3_presigned_url elif local_bento_pb.uri.gcs_presigned_url: bento_bundle_path = local_bento_pb.uri.gcs_presigned_url else: bento_bundle_path = local_bento_pb.uri.uri return self.upload_from_dir(bento_bundle_path, labels=labels)
def wrapper(do_not_track: bool, *args, **kwargs): if do_not_track: os.environ["BENTOML_DO_NOT_TRACK"] = str(True) logger.info("Executing '%s' command without usage tracking.", command_name) track_properties = { 'command_group': cmd_group.name, 'command': command_name, } start_time = time.time() try: return_value = func(*args, **kwargs) track_properties['duration'] = time.time() - start_time track_properties['return_code'] = 0 track(TRACK_CLI_EVENT_NAME, track_properties) return return_value except BaseException as e: track_properties['duration'] = time.time() - start_time track_properties['error_type'] = type(e).__name__ track_properties['error_message'] = str(e) track_properties['return_code'] = 1 if type(e) == KeyboardInterrupt: track_properties['return_code'] = 2 track(TRACK_CLI_EVENT_NAME, track_properties) raise
def prune(self, bento_name=None, labels=None): track('py-api-prune') list_bentos_result = self.list(bento_name=bento_name, labels=labels,) if list_bentos_result.status.status_code != yatai_proto.status_pb2.Status.OK: error_code, error_message = status_pb_to_error_code_and_message( list_bentos_result.status ) raise BentoMLException(f'{error_code}:{error_message}') for bento in list_bentos_result.bentos: bento_tag = f'{bento.name}:{bento.version}' try: self.delete(bento_tag) except BentoMLException as e: logger.error(f'Failed to delete Bento {bento_tag}: {e}')
def delete(self, bento): track('py-api-delete') 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.DangerouslyDeleteBento( DangerouslyDeleteBentoRequest(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'Failed to delete Bento {bento} {error_code}:{error_message}')
def list( self, bento_name=None, offset=None, limit=None, labels=None, order_by=None, ascending_order=None, ): """ List BentoServices that satisfy the specified criteria. Args: bento_name: optional. BentoService name limit: optional. maximum number of returned results labels: optional. offset: optional. offset of results order_by: optional. order by results ascending_order: optional. direction of results order Returns: [bentoml.yatai.proto.repository_pb2.Bento] Example: >>> yatai_client = get_yatai_client() >>> bentos_info_list = yatai_client.repository.list( >>> labels='key=value,key2=value' >>> ) """ track('py-api-list') list_bento_request = ListBentoRequest( bento_name=bento_name, offset=offset, limit=limit, order_by=order_by, ascending_order=ascending_order, ) if labels is not None: generate_gprc_labels_selector(list_bento_request.label_selectors, labels) result = self.yatai_service.ListBento(list_bento_request) 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'{error_code}:{error_message}') return result.bentos
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
def new_behaviour(request_or_iterator, servicer_context): start = default_timer() try: return behaviour(request_or_iterator, servicer_context) finally: duration = max(default_timer() - start, 0) GRPC_SERVER_HANDLED_HISTOGRAM.labels( grpc_type='UNARY', grpc_service=grpc_service_name, grpc_method=grpc_method_name, ).observe(duration) track( YATAI_GRPC_USAGE_EVENT_NAME, { "method": grpc_method_name, "duration": duration }, )
def wrapper(*args, **kwargs): track_properties = { 'command_group': cmd_group.name, 'command': command_name, } start_time = time.time() try: return_value = func(*args, **kwargs) track_properties['duration'] = time.time() - start_time track_properties['return_code'] = 0 track(TRACK_CLI_EVENT_NAME, track_properties) return return_value except BaseException as e: track_properties['duration'] = time.time() - start_time track_properties['error_type'] = type(e).__name__ track_properties['return_code'] = 1 if type(e) == KeyboardInterrupt: track_properties['return_code'] = 2 track(TRACK_CLI_EVENT_NAME, track_properties) raise
def deploy_bentoml(clipper_conn, bundle_path, api_name, model_name=None, labels=None, build_envs=None): """Deploy bentoml bundle to clipper cluster Args: clipper_conn(clipper_admin.ClipperConnection): Clipper connection instance bundle_path(str): Path to the saved BentomlService bundle. api_name(str): name of the api that will be used as prediction function for clipper cluster model_name(str): Model's name for clipper cluster labels(:obj:`list(str)`, optional): labels for clipper model Returns: tuple: Model name and model version that deployed to clipper """ track("clipper-deploy", {'bento_service_bundle_path': bundle_path}) build_envs = {} if build_envs is None else build_envs # docker is required to build clipper model image ensure_docker_available_or_raise() if not clipper_conn.connected: raise BentoMLException( "No connection to Clipper cluster. CallClipperConnection.connect to " "connect to an existing cluster or ClipperConnection.start_clipper to " "create a new one") bento_service_metadata = load_bento_service_metadata(bundle_path) try: api_metadata = next((api for api in bento_service_metadata.apis if api.name == api_name)) except StopIteration: raise BentoMLException( "Can't find API '{}' in BentoService bundle {}".format( api_name, bento_service_metadata.name)) if api_metadata.input_type not in ADAPTER_TYPE_TO_INPUT_TYPE: raise BentoMLException( "Only BentoService APIs using ClipperInput can be deployed to Clipper" ) input_type = ADAPTER_TYPE_TO_INPUT_TYPE[api_metadata.input_type] model_name = model_name or get_clipper_compatible_string( bento_service_metadata.name + "-" + api_metadata.name) model_version = get_clipper_compatible_string( bento_service_metadata.version) with TempDirectory() as tempdir: entry_py_content = CLIPPER_ENTRY.format(api_name=api_name) build_path = os.path.join(tempdir, "bento") shutil.copytree(bundle_path, build_path) with open(os.path.join(build_path, "clipper_entry.py"), "w") as f: f.write(entry_py_content) if bento_service_metadata.env.python_version.startswith("3.6"): base_image = "clipper/python36-closure-container:0.4.1" elif bento_service_metadata.env.python_version.startswith("2.7"): base_image = "clipper/python-closure-container:0.4.1" else: raise BentoMLException( "Python version {} is not supported in Clipper".format( bento_service_metadata.env.python_version)) docker_content = CLIPPER_DOCKERFILE.format( model_name=model_name, model_version=model_version, base_image=base_image, pip_index_url=build_envs.get("PIP_INDEX_URL", ""), pip_trusted_url=build_envs.get("PIP_TRUSTED_HOST", ""), ) with open(os.path.join(build_path, "Dockerfile-clipper"), "w") as f: f.write(docker_content) docker_api = docker.APIClient() clipper_model_docker_image_tag = "clipper-model-{}:{}".format( bento_service_metadata.name.lower(), bento_service_metadata.version) for line in docker_api.build( path=build_path, dockerfile="Dockerfile-clipper", tag=clipper_model_docker_image_tag, ): process_docker_api_line(line) logger.info( "Successfully built docker image %s for Clipper deployment", clipper_model_docker_image_tag, ) clipper_conn.deploy_model( name=model_name, version=model_version, input_type=input_type, image=clipper_model_docker_image_tag, labels=labels, ) track("clipper-deploy-success", {'bento_service_bundle_path': bundle_path}) return model_name, model_version
def delete( self, bento_tag=None, labels=None, bento_name=None, bento_version=None, prune=False, # pylint: disable=redefined-builtin require_confirm=False, ): """ Delete bentos that matches the specified criteria Args: bento_tag: string labels: string bento_name: string bento_version: string prune: boolean, Set True to delete all BentoService require_confirm: boolean Example: >>> >>> yatai_client = get_yatai_client() >>> # Delete all bento services >>> yatai_client.repository.delete(prune=True) >>> # Delete bento service with name is `IrisClassifier` and version `0.1.0` >>> yatai_client.repository.delete( >>> bento_name='IrisClassifier', bento_version='0.1.0' >>> ) >>> # or use bento tag >>> yatai_client.repository.delete('IrisClassifier:v0.1.0') >>> # Delete all bento services with name 'MyService` >>> yatai_client.repository.delete(bento_name='MyService') >>> # Delete all bento services with labels match `ci=failed` and `cohort=20` >>> yatai_client.repository.delete(labels='ci=failed, cohort=20') """ track('py-api-delete') delete_list_limit = 50 if (bento_tag is not None and bento_name is not None and bento_version is not None): raise BentoMLException('Too much arguments') if bento_tag is not None: logger.info(f'Deleting saved Bento bundle {bento_tag}') return self._delete_bento_bundle(bento_tag, require_confirm) elif bento_name is not None and bento_tag is not None: logger.info( f'Deleting saved Bento bundle {bento_name}:{bento_version}') return self._delete_bento_bundle(f'{bento_name}:{bento_version}', require_confirm) else: # list of bentos if prune is True: logger.info('Deleting all BentoML saved bundles.') # ignore other fields bento_name = None labels = None else: log_message = 'Deleting saved Bento bundles' if bento_name is not None: log_message += f' with name: {bento_name},' if labels is not None: log_message += f' with labels match to {labels}' logger.info(log_message) offset = 0 while offset >= 0: bento_list = self.list( bento_name=bento_name, labels=labels, offset=offset, limit=delete_list_limit, ) offset += delete_list_limit # Stop the loop, when no more bentos if len(bento_list) == 0: break else: for bento in bento_list: self._delete_bento_bundle( f'{bento.name}:{bento.version}', require_confirm)