def test_cluster_unsafe_init(): cluster = Cluster(http_address=config.http_cluster.url, grpc_address=config.grpc_cluster.url, grpc_credentials=ssl_channel_credentials(), check_connection=False) assert cluster.http_address == config.http_cluster.url assert cluster.channel is not None
def get_cluster_connection(path: str = CONFIG_PATH) -> Cluster: current_cluster = read_current_cluster(path) if current_cluster is None: raise ClickException( "Can't establish connection to Hydrosphere cluster: cluster config is missing. Use `hs cluster` commands." ) return Cluster(http_address=current_cluster.cluster.server)
def create(cluster: Cluster, name: str, modelversion_id: int, config: MetricSpecConfig) -> 'MetricSpec': """ Create MetricSpec and returns corresponding instance. :param cluster: active cluster :param name: name of the metric :param modelversion_id: ModelVersion for which to create a MetricSpec :param config: MetricSpecConfig, describing MetricSpec :return: metricSpec """ metric_spec_json = { 'name': name, 'modelVersionId': modelversion_id, 'config': { 'modelVersionId': config.modelversion_id, 'threshold': config.threshold, 'thresholdCmpOperator': { 'kind': config.threshold_op } } } resp = cluster.request("POST", MetricSpec._BASE_URL, json=metric_spec_json) handle_request_error( resp, f"Failed to create a MetricSpec for name={name}, modelversion_id={modelversion_id}. {resp.status_code} {resp.text}" ) return MetricSpec._from_json(cluster, resp.json())
def create( cluster: Cluster, model_name: str, version: int, metadata: Optional[Dict[str, str]] = None, deployment_configuration: Optional[DeploymentConfiguration] = None ) -> 'Servable': """ Deploy an instance of uploaded model version at your cluster. :param deployment_configuration: k8s configurations used to run this servable :param cluster: Cluster connected to Hydrosphere :param model_name: Name of uploaded model :param version: Version of uploaded model :param metadata: Information which you can attach to your servable in a form of Dict[str, str] :raises ServableException: :return: servable """ msg = { "modelName": model_name, "version": version, } if metadata: msg['metadata'] = metadata if deployment_configuration: msg['deploymentConfigName'] = deployment_configuration.name resp = cluster.request('POST', Servable._BASE_URL, json=msg) handle_request_error( resp, f"Failed to create a servable. {resp.status_code} {resp.text}") return Servable._from_json(cluster, resp.json())
def build(self, cluster: Cluster) -> Application: """ Create an Application in your Hydrosphere cluster. :return: Application object """ if not self.stages: raise ValueError("No execution stages were provided") execution_graph = ExecutionGraph(stages=self.stages) application_json = { "name": self.name, "kafkaStreaming": [sp.to_dict() for sp in self.streaming_parameters], "executionGraph": execution_graph.to_dict(), "metadata": self.metadata } resp = cluster.request("POST", Application._BASE_URL, json=application_json) handle_request_error( resp, f"Failed to create an application {self.name}. {resp.status_code} {resp.text}" ) return Application._from_json(cluster, resp.json())
def test_pipeline_apply(mock_app_build, mock_app_delete, mock_app_find, mock_mv_find, singular_yaml_path): conn = Cluster("http://") mock_mv_find.return_value = ModelVersion(cluster=conn, id=1, model_id=1, name="claims-model", version=1, signature=ModelSignature(), status="Released", image=DockerImage(name="aaa", tag="aaa"), runtime=DockerImage(name="aaa", tag="aaa"), is_external=False) mock_app_find.return_value = HS_APP(cluster=conn, id=1, name="app", execution_graph=ExecutionGraph([]), status="Ready", signature=ModelSignature(), kafka_streaming=[]) mock_app_build.return_value = HS_APP(cluster=conn, id=1, name="app", execution_graph=ExecutionGraph([]), status="Ready", signature=ModelSignature(), kafka_streaming=[]) app: Application = Application.parse_file(singular_yaml_path) app.apply(conn, os.path.dirname(singular_yaml_path))
def test_training_data_upload(cluster: Cluster, model_version_builder: ModelVersionBuilder, training_data: str): mv: ModelVersion = model_version_builder.build(cluster) mv.training_data = training_data data_upload_response = mv.upload_training_data() data_upload_response.wait(sleep=5) resp = cluster.request("GET", f"/monitoring/profiles/batch/{mv.id}/status") assert "Success" == resp.json()["kind"]
def test_depconf_apply(mock_request, dep_conf_path): resp = Response() resp.status_code = 200 resp._content = json.dumps({"name": "test"}).encode("utf-8") mock_request.return_value = resp conn = Cluster("http://") with open(dep_conf_path, "r") as f: obj = yaml.safe_load(f) dc: DeploymentConfig = DeploymentConfig.parse_obj(obj) dc.apply(conn)
def _upload_s3_file(cluster: Cluster, modelversion_id: int, path: str) -> requests.Response: """ Internal method for submitting training data from S3 to Hydrosphere. :param cluster: Cluster instance :param url: url to which submit S3 path :param path: S3 path to training data """ url = f'/monitoring/profiles/batch/{modelversion_id}/s3' return cluster.request("POST", url, json={"path": path})
def list(cluster: Cluster) -> List['DeploymentConfiguration']: """ List all deployment configurations :param cluster: :return: """ resp = cluster.request("GET", DeploymentConfiguration._BASE_URL) handle_request_error(resp, f"Failed to get a list of Deployment Configurations - {resp.status_code} {resp.text}") return [DeploymentConfiguration.parse_obj(app_json) for app_json in resp.json()]
def delete(cluster: Cluster, name: str) -> dict: """ Delete deployment configuration from Hydrosphere cluster :param cluster: Active Cluster :param name: Deployment configuration name :return: json response from the server """ resp = cluster.request("DELETE", f"{DeploymentConfiguration._BASE_URL}/{name}") handle_request_error(resp, f"Failed to delete Deployment Configuration named {name}: {resp.status_code} {resp.text}") return resp
def find(cluster: Cluster, name: str) -> 'DeploymentConfiguration': """ Search a deployment configuration by name :param cluster: :param name: :return: """ resp = cluster.request("GET", f"{DeploymentConfiguration._BASE_URL}/{name}") handle_request_error(resp, f"Failed to find Deployment Configuration named {name} {resp.status_code} {resp.text}") return DeploymentConfiguration.parse_obj(resp.json())
def list(cluster: Cluster) -> List['MetricSpec']: """ Send request and returns list with all available metric specs. :param cluster: active cluster :return: list with all available metric specs """ resp = cluster.request("GET", MetricSpec._BASE_URL) handle_request_error( resp, f"Failed to list MetricSpecs. {resp.status_code} {resp.text}") return [MetricSpec._from_json(cluster, x) for x in resp.json()]
def delete(cluster: Cluster, id: int) -> dict: """ Delete MetricSpec. :return: result of deletion """ resp = cluster.request("DELETE", f"{MetricSpec._BASE_URL}/{id}") handle_request_error( resp, f"Failed to delete MetricSpec for id={id}. {resp.status_code} {resp.text}" ) return resp.json()
def _upload_local_file(cluster: Cluster, modelversion_id: int, path: str, chunk_size=1024) -> requests.Response: """ Internal method for uploading local training data to Hydrosphere. :param cluster: active cluster :param modelversion_id: modelversion_id for which to upload training data :param path: path to a local file :param chunk_size: chunk size to use for streaming """ gen = read_in_chunks(path, chunk_size) url = f'/monitoring/profiles/batch/{modelversion_id}' return cluster.request("POST", url, data=gen, stream=True)
def find(cluster: Cluster, name: str, version: int) -> 'ModelVersion': """ Find a ModelVersion on the cluster by model name and a version. :param cluster: active cluster :param name: name of the model :param version: version of the model :return: ModelVersion object """ resp = cluster.request("GET", f"{ModelVersion._BASE_URL}/version/{name}/{version}") handle_request_error( resp, f"Failed to find model_version for name={name}, version={version}. {resp.status_code} {resp.text}") return ModelVersion._from_json(cluster, resp.json())
def list(cluster: Cluster) -> List['ModelVersion']: """ List all model versions on the cluster. :param cluster: active cluster :return: list of ModelVersions """ resp = cluster.request("GET", f"{ModelVersion._BASE_URL}/version") handle_request_error( resp, f"Failed to list model versions. {resp.status_code} {resp.text}") return [ModelVersion._from_json(cluster, modelversion_json) for modelversion_json in resp.json()]
def list(cluster: Cluster) -> List['Servable']: """ Retrieve a list of all servables available at your cluster :param cluster: Hydrosphere cluster :return: List of all Servables available at your cluster """ resp = cluster.request("GET", Servable._BASE_URL) handle_request_error( resp, f"Failed to list servables. {resp.status_code} {resp.text}") return [ Servable._from_json(cluster, servable_json) for servable_json in resp.json() ]
def find_by_id(cluster: Cluster, id: int) -> 'MetricSpec': """ Find MetricSpec by id. :param cluster: active cluster :param id: :return: MetricSpec """ resp = cluster.request("GET", f"{MetricSpec._BASE_URL}/{id}") handle_request_error( resp, f"Failed to retrieve MetricSpec for id={id}. {resp.status_code} {resp.text}" ) return MetricSpec._from_json(cluster, resp.json())
def find(cluster: Cluster, application_name: str) -> 'Application': """ Search for an application by name. :param cluster: active cluster :param application_name: application name :return: deserialized application object """ resp = cluster.request("GET", f"{Application._BASE_URL}/{application_name}") handle_request_error( resp, f"Failed to find an application by name={application_name}. {resp.status_code} {resp.text}" ) return Application._from_json(cluster, resp.json())
def delete(cluster: Cluster, application_name: str) -> dict: """ Delete an application by name. :param cluster: active cluster :param application_name: application name :return: response from the cluster """ resp = cluster.request("DELETE", f"{Application._BASE_URL}/{application_name}") handle_request_error( resp, f"Failed to delete application for name={application_name}. {resp.status_code} {resp.text}" ) return resp.json()
def find_by_id(cluster: Cluster, id: int) -> 'ModelVersion': """ Find a ModelVersion on the cluster by id. :param cluster: active cluster :param id: model version id :return: ModelVersion object """ resp = cluster.request("GET", f"{ModelVersion._BASE_URL}/version") handle_request_error( resp, f"Failed to retrieve model_version by id={id}. {resp.status_code} {resp.text}") for modelversion_json in resp.json(): if modelversion_json['id'] == id: return ModelVersion._from_json(cluster, modelversion_json) raise BadRequestException(f"Failed to retrieve model_version by id={id}.")
def list(cluster: Cluster) -> List['Application']: """ List all available applications from the cluster. :param cluster: active cluster :return: deserialized list of application objects """ resp = cluster.request("GET", Application._BASE_URL) handle_request_error( resp, f"Failed to list all applications. {resp.status_code} {resp.text}") applications = [ Application._from_json(cluster, app_json) for app_json in resp.json() ] return applications
def find_by_name(cluster: Cluster, servable_name: str) -> 'Servable': """ Finds a serving servable in a cluster :param cluster: active cluster :param servable_name: a name of the servable :raises ServableException: :return: Servable """ resp = cluster.request("GET", f"{Servable._BASE_URL}/{servable_name}") handle_request_error( resp, f"Failed to find servable for name={servable_name}. {resp.status_code} {resp.text}" ) return Servable._from_json(cluster, resp.json())
def build(self, cluster: Cluster) -> DeploymentConfiguration: """ Create the Deployment Configuration in your Hydrosphere cluster. :return: Deployment Configuration object from the Hydrosphere cluster """ config = DeploymentConfiguration( name=self.name, hpa=self.hpa, pod=self.pod_spec, container=self.container_spec, deployment=self.deployment_spec ) resp = cluster.request("POST", DeploymentConfiguration._BASE_URL, json=config.to_dict()) handle_request_error(resp, f"Failed to upload new Deployment Configuration. {resp.status_code} {resp.text}") return DeploymentConfiguration.parse_obj(resp.json())
def find_by_modelversion(cluster: Cluster, modelversion_id: int) -> List['MetricSpec']: """ Find MetricSpecs by model version. :param cluster: active cluster :param modelversion_id: ModelVersions for which to return metrics. :return: list of MetricSpec objects """ resp = cluster.request( "GET", f"{MetricSpec._BASE_URL}/modelversion/{modelversion_id}") handle_request_error( resp, f"Failed to list MetricSpecs for modelversion_id={modelversion_id}. {resp.status_code} {resp.text}" ) return [MetricSpec._from_json(cluster, x) for x in resp.json()]
def test_external_model_parse(): import json json_str = """ { "id": 138, "created": "2021-09-24T11:29:09.624Z", "finished": "2021-09-24T11:29:09.624Z", "modelVersion": 1, "modelSignature": { "signatureName": "predict", "inputs": [{ "name": "in", "dtype": "DT_DOUBLE", "shape": { "dims": [] }, "profile": "NONE" }], "outputs": [{ "name": "out", "dtype": "DT_DOUBLE", "shape": { "dims": [] }, "profile": "NONE" }] }, "model": { "id": 18, "name": "external-model" }, "status": "Released", "metadata": {}, "applications": [], "image": null, "runtime": null, "monitoringConfiguration": { "batchSize": 100 }, "isExternal": true }""" json_dict = json.loads(json_str) cl = Cluster("asdasd:9091", "asdasd:9090", check_connection=False) mv = ModelVersion._from_json(cl, json_dict) print(mv) assert False
def test_model_analyze_without_grpc(): cl = Cluster("asdasd:9091", check_connection=False) model = ModelVersion(cluster=cl, id=1, model_id=2, name="test", version=3, signature=ModelSignature(), status=None, image="", runtime="", is_external=True) req_id = "asdsad" request = PredictRequest() error = "error" with pytest.raises(HydrosphereException): model.analyze(request_id=req_id, request=request, error=error)
def delete(cluster: Cluster, servable_name: str) -> dict: """ Shut down and delete servable instance. :param cluster: active cluster :param servable_name: name of the servable :return: json response from serve .. warnings also:: Use with caution. Predictors previously associated with this servable will not be able to connect to it. """ resp = cluster.request("DELETE", f"{Servable._BASE_URL}/{servable_name}") handle_request_error( resp, f"Failed to delete the servable with name={servable_name}. {resp.status_code} {resp.text}" ) return resp.json()
def build_external(self, cluster: Cluster) -> 'ModelVersion': """ Create an external ModelVersion on the cluster. :param cluster: active cluster :return: ModelVersion object """ if self.signature is None: raise ValueError("signature is not specified for the model") model = { "name": self.name, "signature": ModelSignature_to_signature_dict(self.signature), "metadata": self.metadata } resp = cluster.request("POST", "/api/v2/externalmodel", json=model) handle_request_error( resp, f"Failed to create an external model. {resp.status_code} {resp.text}") json_dict = resp.json() json_dict['isExternal'] = True return ModelVersion._from_json(cluster, json_dict)