From 96634aa147ad4a81cc7ad62d5db4f2680eba7bfd Mon Sep 17 00:00:00 2001 From: Lu Peng Date: Fri, 25 Oct 2024 00:00:16 -0400 Subject: [PATCH 1/2] Updated ui to list private endpoints. --- ads/aqua/constants.py | 1 + ads/aqua/extension/ui_handler.py | 14 +++++++ ads/aqua/ui.py | 29 +++++++++++++ .../test_data/ui/private_endpoints_list.json | 32 ++++++++++++++ tests/unitary/with_extras/aqua/test_ui.py | 42 +++++++++++++++++++ .../with_extras/aqua/test_ui_handler.py | 11 +++++ 6 files changed, 129 insertions(+) create mode 100644 tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json diff --git a/ads/aqua/constants.py b/ads/aqua/constants.py index 958b161bd..c6a03a801 100644 --- a/ads/aqua/constants.py +++ b/ads/aqua/constants.py @@ -27,6 +27,7 @@ LIFECYCLE_DETAILS_MISSING_JOBRUN = "The associated JobRun resource has been deleted." READY_TO_DEPLOY_STATUS = "ACTIVE" READY_TO_FINE_TUNE_STATUS = "TRUE" +PRIVATE_ENDPOINT_TYPE = "MODEL_DEPLOYMENT" AQUA_GA_LIST = ["id19sfcrra6z"] AQUA_MODEL_TYPE_SERVICE = "service" AQUA_MODEL_TYPE_CUSTOM = "custom" diff --git a/ads/aqua/extension/ui_handler.py b/ads/aqua/extension/ui_handler.py index 9aff616bb..89d7ea7c3 100644 --- a/ads/aqua/extension/ui_handler.py +++ b/ads/aqua/extension/ui_handler.py @@ -10,6 +10,7 @@ from ads.aqua.common.decorator import handle_exceptions from ads.aqua.common.enums import Tags +from ads.aqua.constants import PRIVATE_ENDPOINT_TYPE from ads.aqua.extension.errors import Errors from ads.aqua.extension.base_handler import AquaAPIhandler from ads.aqua.extension.utils import validate_function_parameters @@ -72,6 +73,8 @@ def get(self, id=""): return self.list_vcn() elif paths.startswith("aqua/subnets"): return self.list_subnets() + elif paths.startswith("aqua/privateendpoints"): + return self.list_private_endpoints() elif paths.startswith("aqua/shapes/limit"): return self.get_shape_availability() elif paths.startswith("aqua/bucket/versioning"): @@ -175,6 +178,16 @@ def list_subnets(self, **kwargs): ) ) + def list_private_endpoints(self, **kwargs): + """Lists the private endpoints in the specified compartment.""" + compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID) + resource_type = self.get_argument("resource_type", default=PRIVATE_ENDPOINT_TYPE) + return self.finish( + AquaUIApp().list_private_endpoints( + compartment_id=compartment_id, resource_type=resource_type, **kwargs + ) + ) + def get_shape_availability(self, **kwargs): """For a given compartmentId, resource limit name, and scope, returns the number of available resources associated with the given limit.""" @@ -244,6 +257,7 @@ def post(self, *args, **kwargs): ("job/shapes/?([^/]*)", AquaUIHandler), ("vcn/?([^/]*)", AquaUIHandler), ("subnets/?([^/]*)", AquaUIHandler), + ("privateendpoints/?([^/]*)", AquaUIHandler), ("shapes/limit/?([^/]*)", AquaUIHandler), ("bucket/versioning/?([^/]*)", AquaUIHandler), ("containers/?([^/]*)", AquaUIHandler), diff --git a/ads/aqua/ui.py b/ads/aqua/ui.py index fd7fd91a2..5f2ae6f7e 100644 --- a/ads/aqua/ui.py +++ b/ads/aqua/ui.py @@ -18,6 +18,7 @@ from ads.aqua.common.enums import Tags from ads.aqua.common.errors import AquaResourceAccessError, AquaValueError from ads.aqua.common.utils import get_container_config, load_config, sanitize_response +from ads.aqua.constants import PRIVATE_ENDPOINT_TYPE from ads.common import oci_client as oc from ads.common.auth import default_signer from ads.common.object_storage_details import ObjectStorageDetails @@ -549,6 +550,34 @@ def list_subnets(self, **kwargs) -> list: return sanitize_response(oci_client=vcn_client, response=res) + @telemetry(entry_point="plugin=ui&action=list_private_endpoints", name="aqua") + def list_private_endpoints(self, **kwargs) -> list: + """Lists the private endpoints in the specified compartment. + Data seicne private endpoints have two types: `NOTEBOOK_SESSION` and `MODEL_DEPLOYMENT`. + This api will by default list `MODEL_DEPLOYMENT` type as needed by AQUA model deployment. + + Parameters + ---------- + **kwargs + Addtional arguments, such as `compartment_id`, + for `list_data_science_private_endpoints `_ + + Returns + ------- + json representation of `oci.data_science.models.DataSciencePrivateEndpointSummary`. + """ + compartment_id = kwargs.pop("compartment_id", COMPARTMENT_OCID) + resource_type = kwargs.pop("resource_type", PRIVATE_ENDPOINT_TYPE) + logger.info( + f"Loading private endpoints from compartment: {compartment_id}" + ) + + res = self.ds_client.list_data_science_private_endpoints( + compartment_id=compartment_id, data_science_resource_type=resource_type + ).data + + return sanitize_response(oci_client=self.ds_client, response=res) + @telemetry(entry_point="plugin=ui&action=get_shape_availability", name="aqua") def get_shape_availability(self, **kwargs): """ diff --git a/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json b/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json new file mode 100644 index 000000000..8401b5f82 --- /dev/null +++ b/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json @@ -0,0 +1,32 @@ +[ + { + "compartment_id": "ocid1.compartment.oc1..", + "defined_tags": {}, + "display_name": "datascienceprivateendpoint20241024145113", + "freeform_tags": {}, + "system_tags": {}, + "id": "ocid1.datascienceprivateendpointint.oc1.iad.", + "lifecycle_state": "ACTIVE", + "data_science_resource_type": "MODEL_DEPLOYMENT", + "created_by": "ocid1.datasciencenotebooksessionint.oc1.iad.", + "subnet_id": "ocid1.subnet.oc1.iad.", + "fqdn": ".md2-pe.modeldeployment-int.us-ashburn-1.oci.oc-test.com", + "time_created": "2024-10-24T14:51:13.883000Z", + "time_updated": "2024-10-24T14:51:13.883000Z" + }, + { + "compartment_id": "ocid1.compartment.oc1..", + "defined_tags": {}, + "display_name": "datascienceprivateendpoint20241023220645", + "freeform_tags": {}, + "system_tags": {}, + "id": "ocid1.datascienceprivateendpointint.oc1.iad.", + "lifecycle_state": "ACTIVE", + "data_science_resource_type": "MODEL_DEPLOYMENT", + "created_by": "ocid1.datasciencenotebooksessionint.oc1.iad.", + "subnet_id": "ocid1.subnet.oc1.iad.", + "fqdn": ".md2-pe.modeldeployment-int.us-ashburn-1.oci.oc-test.com", + "time_created": "2024-10-23T22:06:45.224000Z", + "time_updated": "2024-10-23T22:06:45.224000Z" + } +] \ No newline at end of file diff --git a/tests/unitary/with_extras/aqua/test_ui.py b/tests/unitary/with_extras/aqua/test_ui.py index 459b9bafd..70c8682b3 100644 --- a/tests/unitary/with_extras/aqua/test_ui.py +++ b/tests/unitary/with_extras/aqua/test_ui.py @@ -398,6 +398,48 @@ def test_list_subnets(self, mock_list_subnets): ) assert len(results) == len(subnets) + def test_list_private_endpoints(self): + """Test to list the private endpoints in the specified compartment.""" + private_endpoints_list_json = os.path.join( + self.curr_dir, f"test_data/ui/private_endpoints_list.json" + ) + with open(private_endpoints_list_json, "r") as _file: + private_endpoints = json.load(_file) + + self.app.ds_client.list_data_science_private_endpoints = MagicMock( + return_value=oci.response.Response( + status=200, + request=MagicMock(), + headers=MagicMock(), + data=[ + oci.data_science.models.DataSciencePrivateEndpointSummary(**pe) + for pe in private_endpoints + ], + ) + ) + + results = self.app.list_private_endpoints() + expected_attributes = { + "compartmentId", + "definedTags", + "displayName", + "freeformTags", + "systemTags", + "id", + "lifecycleState", + "dataScienceResourceType", + "createdBy", + "subnetId", + "fqdn", + "timeCreated", + "timeUpdated" + } + for result in results: + self.assertTrue( + expected_attributes.issuperset(set(result)), "Attributes mismatch" + ) + assert len(results) == len(private_endpoints) + @patch.object(oci.limits.limits_client.LimitsClient, "get_resource_availability") def test_get_shape_availability(self, mock_get_resource_availability): """Test whether the function returns the number of available resources associated with the given shape.""" diff --git a/tests/unitary/with_extras/aqua/test_ui_handler.py b/tests/unitary/with_extras/aqua/test_ui_handler.py index 687536936..c34e52ad4 100644 --- a/tests/unitary/with_extras/aqua/test_ui_handler.py +++ b/tests/unitary/with_extras/aqua/test_ui_handler.py @@ -21,6 +21,7 @@ class TestDataset: USER_COMPARTMENT_ID = "ocid1.compartment.oc1.." USER_PROJECT_ID = "ocid1.datascienceproject.oc1.iad." + PRIVATE_ENDPOINT_RESOURCE_TYPE = "MODEL_DEPLOYMENT" DEPLOYMENT_SHAPE_NAME = "VM.GPU.A10.1" @@ -149,6 +150,16 @@ def test_list_subnets(self, mock_list_subnets): compartment_id=TestDataset.USER_COMPARTMENT_ID, vcn_id="mock-vcn-id" ) + @patch("ads.aqua.ui.AquaUIApp.list_private_endpoints") + def test_list_private_endpoints(self, mock_list_private_endpoints): + """Test the get method to fetch list of private endpoints.""" + self.ui_handler.request.path = "aqua/privateendpoints" + self.ui_handler.get() + mock_list_private_endpoints.assert_called_with( + compartment_id=TestDataset.USER_COMPARTMENT_ID, + resource_type=TestDataset.PRIVATE_ENDPOINT_RESOURCE_TYPE + ) + @patch("ads.aqua.ui.AquaUIApp.get_shape_availability") def test_get_shape_availability(self, mock_get_shape_availability): """Test get shape availability.""" From 95e7a2a8cfa25ca49c81015d614c0a333245c44b Mon Sep 17 00:00:00 2001 From: Lu Peng Date: Fri, 25 Oct 2024 00:01:44 -0400 Subject: [PATCH 2/2] Updated pr. --- .../with_extras/aqua/test_data/ui/private_endpoints_list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json b/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json index 8401b5f82..1dd39f5b1 100644 --- a/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json +++ b/tests/unitary/with_extras/aqua/test_data/ui/private_endpoints_list.json @@ -29,4 +29,4 @@ "time_created": "2024-10-23T22:06:45.224000Z", "time_updated": "2024-10-23T22:06:45.224000Z" } -] \ No newline at end of file +]