def ContainerizeBento(self, request, context=None): try: ensure_docker_available_or_raise() tag = request.tag if tag is None or len(tag) == 0: name = to_valid_docker_image_name(request.bento_name) version = to_valid_docker_image_version(request.bento_version) tag = f"{name}:{version}" if ":" not in tag: version = to_valid_docker_image_version(request.bento_version) tag = f"{tag}:{version}" import docker docker_client = docker.from_env() bento_pb = self.bento_metadata_store.get( request.bento_name, request.bento_version ) if not bento_pb: raise YataiRepositoryException( f'BentoService {request.bento_name}:{request.bento_version} ' f'does not exist' ) with TempDirectory() as temp_dir: temp_bundle_path = f'{temp_dir}/{bento_pb.name}' bento_service_bundle_path = bento_pb.uri.uri if bento_pb.uri.type == BentoUri.S3: bento_service_bundle_path = self.repo.get( bento_pb.name, bento_pb.version ) elif bento_pb.uri.type == BentoUri.GCS: bento_service_bundle_path = self.repo.get( bento_pb.name, bento_pb.version ) safe_retrieve(bento_service_bundle_path, temp_bundle_path) try: docker_client.images.build( path=temp_bundle_path, tag=tag, buildargs=dict(request.build_args), ) except (docker.errors.APIError, docker.errors.BuildError) as error: logger.error(f'Encounter container building issue: {error}') raise YataiRepositoryException(error) if request.push is True: try: docker_client.images.push( repository=request.repository, tag=tag ) except docker.errors.APIError as error: raise YataiRepositoryException(error) return ContainerizeBentoResponse(status=Status.OK(), tag=tag) except BentoMLException as e: logger.error(f"RPC ERROR ContainerizeBento: {e}") return ContainerizeBentoResponse(status=e.status_proto) except Exception as e: # pylint: disable=broad-except logger.error(f"RPC ERROR ContainerizeBento: {e}") return ContainerizeBentoResponse(status=Status.INTERNAL(e))
def dangerously_delete(self, bento_name, bento_version): with create_session(self.sess_maker) as sess: try: bento_obj = (sess.query(Bento).filter_by( name=bento_name, version=bento_version).one()) if bento_obj.deleted: raise YataiRepositoryException( "Bento {}:{} has already been deleted".format( bento_name, bento_version)) bento_obj.deleted = True except NoResultFound: raise YataiRepositoryException( "Bento %s:%s is not found in repository" % bento_name, bento_version)
def get(self, bento_name, bento_version): saved_path = os.path.join(self.base_path, bento_name, bento_version) if not os.path.exists(saved_path): raise YataiRepositoryException( "Bento {}:{} not found in target repository".format( bento_name, bento_version)) return saved_path
def update_bento_service_metadata( self, bento_name, bento_version, bento_service_metadata_pb ): with create_session(self.sess_maker) as sess: try: bento_obj = ( sess.query(Bento) .filter_by(name=bento_name, version=bento_version, deleted=False) .one() ) service_metadata = ProtoMessageToDict(bento_service_metadata_pb) bento_obj.bento_service_metadata = service_metadata if service_metadata.get('labels', None) is not None: bento = ( sess.query(Bento) .filter_by(name=bento_name, version=bento_version) .one() ) add_or_update_labels( sess, RESOURCE_TYPE.bento, bento.id, service_metadata['labels'] ) except NoResultFound: raise YataiRepositoryException( "Bento %s:%s is not found in repository" % bento_name, bento_version )
def add(self, bento_name, bento_version): # Full path containing saved BentoService bundle, it the base path with service # name and service version as prefix. e.g.: # with base_path = '/tmp/my_bento_repo/', the saved bento will resolve in # the directory: '/tmp/my_bento_repo/service_name/version/' target_dir = os.path.join(self.base_path, bento_name, bento_version) # Ensure parent directory exist Path(os.path.join(self.base_path), bento_name).mkdir( parents=True, exist_ok=True ) # Raise if target bento version already exist in storage if os.path.exists(target_dir): raise YataiRepositoryException( "Existing BentoService bundle {name}:{version} found in repository: " "{target_dir}".format( name=bento_name, version=bento_version, target_dir=target_dir ) ) # Create target directory for upload os.mkdir(target_dir) return BentoUri(type=self.uri_type, uri=target_dir)
def add(self, bento_name, bento_version): # Generate pre-signed s3 path for upload object_name = self._get_object_name(bento_name, bento_version) try: response = self.s3_client.generate_presigned_post( self.bucket, object_name, Fields=None, Conditions=None, ExpiresIn=self._expiration, ) except Exception as e: raise YataiRepositoryException( "Not able to get pre-signed URL on S3. Error: {}".format(e) ) additional_fields = response['fields'] additional_fields['url'] = response['url'] return BentoUri( type=self.uri_type, uri='s3://{}/{}'.format(self.bucket, object_name), additional_fields=json.dumps(additional_fields), )
def dangerously_delete(self, bento_name, bento_version): # Remove gcs path containing related Bento files object_name = self._get_object_name(bento_name, bento_version) try: bucket = self.gcs_client.bucket(self.bucket) blob = bucket.blob(object_name) blob.delete() except Exception as e: raise YataiRepositoryException( "Not able to delete object on GCS. Error: {}".format(e))
def update_bento_service_metadata(self, bento_name, bento_version, bento_service_metadata_pb): with create_session(self.sess_maker) as sess: try: bento_obj = (sess.query(Bento).filter_by(name=bento_name, version=bento_version, deleted=False).one()) bento_obj.bento_service_metadata = ProtoMessageToDict( bento_service_metadata_pb) except NoResultFound: raise YataiRepositoryException( "Bento %s:%s is not found in repository" % bento_name, bento_version)
def update_upload_status(sess, bento_name, bento_version, upload_status_pb): try: bento_obj = (sess.query(Bento).filter_by(name=bento_name, version=bento_version, deleted=False).one()) # TODO: # if bento_obj.upload_status and bento_obj.upload_status.updated_at > # upload_status_pb.updated_at, update should be ignored bento_obj.upload_status = ProtoMessageToDict(upload_status_pb) except NoResultFound: raise YataiRepositoryException( "Bento %s:%s is not found in repository" % bento_name, bento_version)
def dangerously_delete(self, bento_name, bento_version): # Remove s3 path containing related Bento files object_name = self._get_object_name(bento_name, bento_version) try: response = self.s3_client.delete_object(Bucket=self.bucket, Key=object_name) DELETE_MARKER = 'DeleteMarker' # whether object is successfully deleted. except Exception as e: raise YataiRepositoryException( "Not able to delete object on S3. Error: {}".format(e)) return response[DELETE_MARKER]
def __init__(self, base_url): try: from google.cloud import storage except ImportError: raise YataiRepositoryException( '"google-cloud-storage" package is required for Google Cloud ' 'Storage Repository. You can install it with pip: ' '"pip install google-cloud-storage"' ) self.uri_type = BentoUri.GCS parse_result = urlparse(base_url) self.bucket = parse_result.netloc self.base_path = parse_result.path.lstrip('/') self.gcs_client = storage.Client()
def get(self, bento_name, bento_version): # Return s3 path containing uploaded Bento files object_name = self._get_object_name(bento_name, bento_version) try: response = self.s3_client.generate_presigned_url( 'get_object', Params={'Bucket': self.bucket, 'Key': object_name}, ExpiresIn=self._expiration, ) except Exception as e: raise YataiRepositoryException( "Not able to get pre-signed URL on S3. Error: {}".format(e) ) return response
def add(self, bento_name, bento_version): object_name = self._get_object_name(bento_name, bento_version) try: bucket = self.gcs_client.bucket(self.bucket) blob = bucket.blob(object_name) response = blob.generate_signed_url( version="v4", expiration=self._expiration, method="PUT", ) except Exception as e: raise YataiRepositoryException( "Not able to get pre-signed URL on GCS. Error: {}".format(e) ) return BentoUri( type=self.uri_type, uri='gs://{}/{}'.format(self.bucket, object_name), gcs_presigned_url=response, )
def add(self, bento_name, bento_version): # Generate pre-signed s3 path for upload object_name = self._get_object_name(bento_name, bento_version) try: response = self.s3_client.generate_presigned_url( 'put_object', Params={'Bucket': self.bucket, 'Key': object_name}, ExpiresIn=self.expiration, ) except Exception as e: raise YataiRepositoryException( "Not able to get pre-signed URL on S3. Error: {}".format(e) ) return BentoUri( type=self.uri_type, uri='s3://{}/{}'.format(self.bucket, object_name), s3_presigned_url=response, )
def dangerously_delete(self, bento_name, bento_version): # Remove s3 path containing related Bento files from botocore.exceptions import ClientError object_name = self._get_object_name(bento_name, bento_version) try: response = self.s3_client.delete_object(Bucket=self.bucket, Key=object_name) DELETE_MARKER = 'DeleteMarker' # whether object is successfully deleted. # Note: as of boto3 v1.13.13. delete_object returns an incorrect format as # expected from documentation. # Expected format: # { # 'DeleteMarker': True|False, # 'VersionId': 'string', # 'RequestCharged': 'requester' # } # Current return: # { # 'ResponseMetadata': { # 'RequestId': '****************', # 'HostId': '*****/******', # 'HTTPStatusCode': 204, # 'HTTPHeaders': { # 'x-amz-id-2': '*****/xxxxx', # 'x-amz-request-id': '332EE9F7AB555D2B', # 'date': 'Tue, 19 May 2020 19:46:57 GMT', # 'server': 'AmazonS3' # }, # 'RetryAttempts': 0 # } # } # An open issue on github: https://github.com/boto/boto3/issues/759 if DELETE_MARKER in response: if response[DELETE_MARKER]: return else: logger.warning( f"BentoML has deleted service '{bento_name}:{bento_version}' " f"from YataiService records, but it failed to delete the saved " f"bundle files stored in s3://{self.bucket}/{object_name}, " f"the files may have already been deleted by the user." ) return elif 'ResponseMetadata' in response: # Note: Use head_object to 'check' is the object deleted or not. # head_object only try to retrieve the metadata without returning # the object itself. try: self.s3_client.head_object(Bucket=self.bucket, Key=object_name) logger.warning( f"BentoML has deleted service '{bento_name}:{bento_version}' " f"from YataiService records, but it failed to delete the saved " f"bundle files stored in s3://{self.bucket}/{object_name}, " f"the files may have already been deleted by the user." ) except ClientError as e: # expected ClientError with Code 404, as target object should be # deleted and 'head_object' should raise error_response = e.response.get('Error', {}) error_code = error_response.get('Code', None) if error_code == '404': # Error code 404 means target file object does not exist, as # expected after delete_object call return else: # unexpected boto3 ClientError raise e else: raise YataiRepositoryException( 'Unrecognized response format from s3 delete_object' ) except Exception as e: raise YataiRepositoryException( "Not able to delete object on S3. Error: {}".format(e) )