From ece0d7c4fa9257dedf689a93dde4c0a654b8d7ac Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Tue, 20 Sep 2022 16:53:20 +0000 Subject: [PATCH 1/9] Add the vertex endpoint resource. --- mmv1/products/vertexai/api.yaml | 209 ++++++++++++++++++ mmv1/products/vertexai/terraform.yaml | 19 ++ .../vertex_ai_endpoint_network.tf.erb | 41 ++++ 3 files changed, 269 insertions(+) create mode 100644 mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index c1ca03cad874..436d966a4668 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -103,6 +103,215 @@ objects: input: true description: | Points to a YAML file stored on Google Cloud Storage describing additional information about the Dataset. The schema is defined as an OpenAPI 3.0.2 Schema Object. The schema files that can be used here are found in gs://google-cloud-aiplatform/schema/dataset/metadata/. + +# Vertex AI Endpoints + - !ruby/object:Api::Resource + name: Endpoint + base_url: projects/{{project}}/locations/{{location}}/endpoints + create_url: projects/{{project}}/locations/{{location}}/endpoints + self_link: 'projects/{{project}}/locations/{{location}}/endpoints/{{name}}' + update_verb: :PATCH + update_mask: true + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Official Documentation': + 'https://cloud.google.com/vertex-ai/docs' + api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.endpoints' + async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + path: 'name' + base_url: '{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::OpAsync::Result + path: 'response' + resource_inside_response: true + status: !ruby/object:Api::OpAsync::Status + path: 'done' + complete: True + allowed: + - True + - False + error: !ruby/object:Api::OpAsync::Error + path: 'error' + message: 'message' + description: "Models are deployed into it, and afterwards Endpoint is called to obtain predictions and explanations." + parameters: + - !ruby/object:Api::Type::String + name: location + description: The location for the resource + url_param_only: true + required: true + input: true + properties: + - !ruby/object:Api::Type::String + name: name + description: Output only. The resource name of the Endpoint. + output: true + - !ruby/object:Api::Type::String + name: displayName + description: Required. The display name of the Endpoint. The name can be up to 128 characters long and can consist of any UTF-8 characters. + required: true + - !ruby/object:Api::Type::String + name: description + description: The description of the Endpoint. + - !ruby/object:Api::Type::Array + name: deployedModels + description: Output only. The models deployed in this Endpoint. To add or remove DeployedModels use EndpointService.DeployModel and EndpointService.UndeployModel respectively. + output: true + item_type: !ruby/object:Api::Type::NestedObject + name: deployedModels + description: Output only. The models deployed in this Endpoint. To add or remove DeployedModels use EndpointService.DeployModel and EndpointService.UndeployModel respectively. + properties: + - !ruby/object:Api::Type::NestedObject + name: dedicatedResources + description: A description of resources that are dedicated to the DeployedModel, and that need a higher degree of manual configuration. + output: true + properties: + - !ruby/object:Api::Type::NestedObject + name: machineSpec + description: The specification of a single machine used by the prediction. + output: true + properties: + - !ruby/object:Api::Type::String + name: machineType + description: 'The type of the machine. See the [list of machine types supported for prediction](https://cloud.google.com/vertex-ai/docs/predictions/configure-compute#machine-types) See the [list of machine types supported for custom training](https://cloud.google.com/vertex-ai/docs/training/configure-compute#machine-types). For DeployedModel this field is optional, and the default value is `n1-standard-2`. For BatchPredictionJob or as part of WorkerPoolSpec this field is required. TODO(rsurowka): Try to better unify the required vs optional.' + output: true + - !ruby/object:Api::Type::String + name: acceleratorType + description: The type of accelerator(s) that may be attached to the machine as per accelerator_count. See possible values [here](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/MachineSpec#AcceleratorType). + output: true + - !ruby/object:Api::Type::Integer + name: acceleratorCount + description: The number of accelerators to attach to the machine. + output: true + - !ruby/object:Api::Type::Integer + name: minReplicaCount + description: The minimum number of machine replicas this DeployedModel will be always deployed on. This value must be greater than or equal to 1. If traffic against the DeployedModel increases, it may dynamically be deployed onto more replicas, and as traffic decreases, some of these extra replicas may be freed. + output: true + - !ruby/object:Api::Type::Integer + name: maxReplicaCount + description: The maximum number of replicas this DeployedModel may be deployed on when the traffic against it increases. If the requested value is too large, the deployment will error, but if deployment succeeds then the ability to scale the model to that many replicas is guaranteed (barring service outages). If traffic against the DeployedModel increases beyond what its replicas at maximum may handle, a portion of the traffic will be dropped. If this value is not provided, will use min_replica_count as the default value. The value of this field impacts the charge against Vertex CPU and GPU quotas. Specifically, you will be charged for max_replica_count * number of cores in the selected machine type) and (max_replica_count * number of GPUs per replica in the selected machine type). + output: true + - !ruby/object:Api::Type::Array + name: autoscalingMetricSpecs + description: The metric specifications that overrides a resource utilization metric (CPU utilization, accelerator's duty cycle, and so on) target value (default to 60 if not set). At most one entry is allowed per metric. If machine_spec.accelerator_count is above 0, the autoscaling will be based on both CPU utilization and accelerator's duty cycle metrics and scale up when either metrics exceeds its target value while scale down if both metrics are under their target value. The default target value is 60 for both metrics. If machine_spec.accelerator_count is 0, the autoscaling will be based on CPU utilization metric only with default target value 60 if not explicitly set. For example, in the case of Online Prediction, if you want to override target CPU utilization to 80, you should set autoscaling_metric_specs.metric_name to `aiplatform.googleapis.com/prediction/online/cpu/utilization` and autoscaling_metric_specs.target to `80`. + output: true + item_type: !ruby/object:Api::Type::NestedObject + name: autoscalingMetricSpecs + description: The metric specifications that overrides a resource utilization metric (CPU utilization, accelerator's duty cycle, and so on) target value (default to 60 if not set). At most one entry is allowed per metric. If machine_spec.accelerator_count is above 0, the autoscaling will be based on both CPU utilization and accelerator's duty cycle metrics and scale up when either metrics exceeds its target value while scale down if both metrics are under their target value. The default target value is 60 for both metrics. If machine_spec.accelerator_count is 0, the autoscaling will be based on CPU utilization metric only with default target value 60 if not explicitly set. For example, in the case of Online Prediction, if you want to override target CPU utilization to 80, you should set autoscaling_metric_specs.metric_name to `aiplatform.googleapis.com/prediction/online/cpu/utilization` and autoscaling_metric_specs.target to `80`. + properties: + - !ruby/object:Api::Type::String + name: metricName + description: 'The resource metric name. Supported metrics: * For Online Prediction: * `aiplatform.googleapis.com/prediction/online/accelerator/duty_cycle` * `aiplatform.googleapis.com/prediction/online/cpu/utilization`' + output: true + - !ruby/object:Api::Type::Integer + name: target + description: The target resource utilization in percentage (1% - 100%) for the given metric; once the real usage deviates from the target by a certain percentage, the machine replicas change. The default value is 60 (representing 60%) if not provided. + output: true + - !ruby/object:Api::Type::NestedObject + name: automaticResources + description: A description of resources that to large degree are decided by Vertex AI, and require only a modest additional configuration. + output: true + properties: + - !ruby/object:Api::Type::Integer + name: minReplicaCount + description: The minimum number of replicas this DeployedModel will be always deployed on. If traffic against it increases, it may dynamically be deployed onto more replicas up to max_replica_count, and as traffic decreases, some of these extra replicas may be freed. If the requested value is too large, the deployment will error. + output: true + - !ruby/object:Api::Type::Integer + name: maxReplicaCount + description: The maximum number of replicas this DeployedModel may be deployed on when the traffic against it increases. If the requested value is too large, the deployment will error, but if deployment succeeds then the ability to scale the model to that many replicas is guaranteed (barring service outages). If traffic against the DeployedModel increases beyond what its replicas at maximum may handle, a portion of the traffic will be dropped. If this value is not provided, a no upper bound for scaling under heavy traffic will be assume, though Vertex AI may be unable to scale beyond certain replica number. + output: true + - !ruby/object:Api::Type::String + name: id + description: The ID of the DeployedModel. If not provided upon deployment, Vertex AI will generate a value for this ID. This value should be 1-10 characters, and valid characters are /[0-9]/. + output: true + - !ruby/object:Api::Type::String + name: model + description: The name of the Model that this is the deployment of. Note that the Model may be in a different location than the DeployedModel's Endpoint. + output: true + - !ruby/object:Api::Type::String + name: modelVersionId + description: Output only. The version ID of the model that is deployed. + output: true + - !ruby/object:Api::Type::String + name: displayName + description: The display name of the DeployedModel. If not provided upon creation, the Model's display_name is used. + output: true + - !ruby/object:Api::Type::String + name: createTime + description: Output only. Timestamp when the DeployedModel was created. + output: true + - !ruby/object:Api::Type::String + name: serviceAccount + description: The service account that the DeployedModel's container runs as. Specify the email address of the service account. If this service account is not specified, the container runs as a service account that doesn't have access to the resource project. Users deploying the Model must have the `iam.serviceAccounts.actAs` permission on this service account. + output: true + - !ruby/object:Api::Type::Boolean + name: enableAccessLogging + description: These logs are like standard server access logs, containing information like timestamp and latency for each prediction request. Note that Stackdriver logs may incur a cost, especially if your project receives prediction requests at a high queries per second rate (QPS). Estimate your costs before enabling this option. + output: true + - !ruby/object:Api::Type::NestedObject + name: privateEndpoints + description: Output only. Provide paths for users to send predict/explain/health requests directly to the deployed model services running on Cloud via private services access. This field is populated if network is configured. + output: true + properties: + - !ruby/object:Api::Type::String + name: predictHttpUri + description: Output only. Http(s) path to send prediction requests. + output: true + - !ruby/object:Api::Type::String + name: explainHttpUri + description: Output only. Http(s) path to send explain requests. + output: true + - !ruby/object:Api::Type::String + name: healthHttpUri + description: Output only. Http(s) path to send health check requests. + output: true + - !ruby/object:Api::Type::String + name: serviceAttachment + description: Output only. The name of the service attachment resource. Populated if private service connect is enabled. + output: true + - !ruby/object:Api::Type::String + name: sharedResources + description: 'The resource name of the shared DeploymentResourcePool to deploy on. Format: projects/{project}/locations/{location}/deploymentResourcePools/{deployment_resource_pool}' + output: true + - !ruby/object:Api::Type::Boolean + name: enableContainerLogging + description: If true, the container of the DeployedModel instances will send `stderr` and `stdout` streams to Stackdriver Logging. Only supported for custom-trained Models and AutoML Tabular Models. + output: true + - !ruby/object:Api::Type::String + name: etag + description: Used to perform consistent read-modify-write updates. If not set, a blind "overwrite" update happens. + output: true + - !ruby/object:Api::Type::KeyValuePairs + name: labels + description: The labels with user-defined metadata to organize your Endpoints. Label keys and values can be no longer than 64 characters (Unicode codepoints), can only contain lowercase letters, numeric characters, underscores and dashes. International characters are allowed. See https://goo.gl/xmQnxf for more information and examples of labels. + - !ruby/object:Api::Type::String + name: createTime + description: Output only. Timestamp when this Endpoint was created. + output: true + - !ruby/object:Api::Type::String + name: updateTime + description: Output only. Timestamp when this Endpoint was last updated. + output: true + - !ruby/object:Api::Type::NestedObject + name: encryptionSpec + description: Customer-managed encryption key spec for an Endpoint. If set, this Endpoint and all sub-resources of this Endpoint will be secured by this key. + input: true + properties: + - !ruby/object:Api::Type::String + name: kmsKeyName + description: 'Required. The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. Has the form: `projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key`. The key needs to be in the same region as where the compute resource is created.' + required: true + input: true + - !ruby/object:Api::Type::String + name: network + description: 'The full name of the Google Compute Engine [network](https://cloud.google.com//compute/docs/networks-and-firewalls#networks) to which the Endpoint should be peered. Private services access must already be configured for the network. If left unspecified, the Endpoint is not peered with any network. Only one of the fields, network or enable_private_service_connect, can be set. [Format](https://cloud.google.com/compute/docs/reference/rest/v1/networks/insert): `projects/{project}/global/networks/{network}`. Where `{project}` is a project number, as in `12345`, and `{network}` is network name.' + input: true + - !ruby/object:Api::Type::String + name: modelDeploymentMonitoringJob + description: 'Output only. Resource name of the Model Monitoring job associated with this Endpoint if monitoring is enabled by CreateModelDeploymentMonitoringJob. Format: `projects/{project}/locations/{location}/modelDeploymentMonitoringJobs/{model_deployment_monitoring_job}`' + output: true + # Vertex AI Featurestores - !ruby/object:Api::Resource name: Featurestore diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 2fb8639cada6..d7d2181a2e48 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -30,6 +30,25 @@ overrides: !ruby/object:Overrides::ResourceOverrides default_from_api: true region: !ruby/object:Overrides::Terraform::PropertyOverride default_from_api: true + Endpoint: !ruby/object:Overrides::Terraform::ResourceOverride + examples: + - !ruby/object:Provider::Terraform::Examples + name: "vertex_ai_endpoint_network" + primary_resource_id: "endpoint" + vars: + name: "vertex_ai_endpoint" + project: "vertex-ai" + address_name: "address-name" + kms_key_name: "kms-name" + network_name: "network-name" + test_vars_overrides: + kms_key_name: 'BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name' + network_name: 'BootstrapSharedTestNetwork(t, "vertex")' + properties: + etag: !ruby/object:Overrides::Terraform::PropertyOverride + ignore_read: true + name: !ruby/object:Overrides::Terraform::PropertyOverride + custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb Featurestore: !ruby/object:Overrides::Terraform::ResourceOverride autogen_async: false skip_sweeper: true diff --git a/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb new file mode 100644 index 000000000000..4de7864561db --- /dev/null +++ b/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb @@ -0,0 +1,41 @@ +resource "google_vertex_ai_endpoint" "<%= ctx[:primary_resource_id] %>" { + display_name = "sample-endpoint" + description = "A sample vertex endpoint" + location = "us-central1" + labels = { + label-one = "value-one" + } + network = "projects/${data.google_project.project.number}/global/networks/${data.google_compute_network.vertex_network.name}" + encryption_spec { + kms_key_name = "<%= ctx[:vars]['kms_key_name'] %>" + } + depends_on = [ + google_service_networking_connection.vertex_vpc_connection + ] +} + +resource "google_service_networking_connection" "vertex_vpc_connection" { + network = data.google_compute_network.vertex_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.vertex_range.name] +} + +resource "google_compute_global_address" "vertex_range" { + name = "<%= ctx[:vars]['address_name'] %>" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 24 + network = data.google_compute_network.vertex_network.id +} + +data "google_compute_network" "vertex_network" { + name = "<%= ctx[:vars]['network_name'] %>" +} + +resource "google_kms_crypto_key_iam_member" "crypto_key" { + crypto_key_id = "<%= ctx[:vars]['kms_key_name'] %>" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-aiplatform.iam.gserviceaccount.com" +} + +data "google_project" "project" {} From eb9df4feff96f62b2ba459b01b70bfd3b53c1b43 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Thu, 6 Oct 2022 13:01:06 -0700 Subject: [PATCH 2/9] Add actions block excluding update for vertex resources that do not use operations for update. --- mmv1/products/vertexai/api.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 436d966a4668..4ceae9cc20ef 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -37,6 +37,9 @@ objects: 'https://cloud.google.com/vertex-ai/docs' api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.datasets' async: !ruby/object:Api::OpAsync + actions: + - create + - delete operation: !ruby/object:Api::OpAsync::Operation path: 'name' base_url: '{{op_id}}' @@ -118,6 +121,9 @@ objects: 'https://cloud.google.com/vertex-ai/docs' api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.endpoints' async: !ruby/object:Api::OpAsync + actions: + - create + - delete operation: !ruby/object:Api::OpAsync::Operation path: 'name' base_url: '{{op_id}}' @@ -410,6 +416,9 @@ objects: 'https://cloud.google.com/vertex-ai/docs' api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores.entityTypes' async: !ruby/object:Api::OpAsync + actions: + - create + - delete operation: !ruby/object:Api::OpAsync::Operation path: 'name' base_url: '{{op_id}}' @@ -501,6 +510,9 @@ objects: 'https://cloud.google.com/vertex-ai/docs' api: 'https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores.entityTypes.features' async: !ruby/object:Api::OpAsync + actions: + - create + - delete operation: !ruby/object:Api::OpAsync::Operation path: 'name' base_url: '{{op_id}}' From 4e0ef2f18409ec1e958b9dc26289b2b0af0d54f5 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Thu, 6 Oct 2022 14:04:12 -0700 Subject: [PATCH 3/9] Fix declared but not used compile error --- mmv1/templates/terraform/resource.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mmv1/templates/terraform/resource.erb b/mmv1/templates/terraform/resource.erb index 6743adcdf6a1..e3d6ba4addcd 100644 --- a/mmv1/templates/terraform/resource.erb +++ b/mmv1/templates/terraform/resource.erb @@ -160,7 +160,7 @@ func resource<%= resource_name -%><%= prop.name.camelize(:upper) -%>SetStyleDiff <% end -%> func resource<%= resource_name -%>Create(d *schema.ResourceData, meta interface{}) error { -<% if object.async&.is_a?(Api::OpAsync) && object.async.include_project -%> +<% if object.async&.is_a?(Api::OpAsync) && object.async.include_project && object.async&.allow?('create') -%> var project string <% end -%> config := meta.(*Config) @@ -577,7 +577,7 @@ func resource<%= resource_name -%>Read(d *schema.ResourceData, meta interface{}) <% if updatable?(object, object.root_properties) -%> func resource<%= resource_name -%>Update(d *schema.ResourceData, meta interface{}) error { -<% if object.async&.is_a?(Api::OpAsync) && object.async.include_project -%> +<% if object.async&.is_a?(Api::OpAsync) && object.async.include_project && object.async&.allow?('update') -%> var project string <% end -%> config := meta.(*Config) @@ -839,7 +839,7 @@ if <%= props.map { |prop| "d.HasChange(\"#{prop.name.underscore}\")" }.join ' || <% end # if updatable? -%> func resource<%= resource_name -%>Delete(d *schema.ResourceData, meta interface{}) error { -<% if object.async&.is_a?(Api::OpAsync) && object.async.include_project -%> +<% if object.async&.is_a?(Api::OpAsync) && object.async.include_project && object.async&.allow?('delete') -%> var project string <% end -%> <% if object.skip_delete -%> From 425a76874a1db83ab734b1da7f23cb7fb4b04e38 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Thu, 6 Oct 2022 15:45:20 -0700 Subject: [PATCH 4/9] Remove pre_update from vertex entity type and feature. --- mmv1/products/vertexai/terraform.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index d7d2181a2e48..4407acd9c372 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -118,7 +118,6 @@ overrides: !ruby/object:Overrides::ResourceOverrides custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb custom_code: !ruby/object:Provider::Terraform::CustomCode pre_create: templates/terraform/constants/vertex_ai_featurestore_entitytype.go.erb - pre_update: templates/terraform/constants/vertex_ai_featurestore_entitytype.go.erb pre_delete: templates/terraform/constants/vertex_ai_featurestore_entitytype.go.erb FeaturestoreEntitytypeFeature: !ruby/object:Overrides::Terraform::ResourceOverride import_format: ["{{%entitytype}}/features/{{name}}"] @@ -146,7 +145,6 @@ overrides: !ruby/object:Overrides::ResourceOverrides custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb custom_code: !ruby/object:Provider::Terraform::CustomCode pre_create: templates/terraform/constants/vertex_ai_featurestore_entitytype_feature.go.erb - pre_update: templates/terraform/constants/vertex_ai_featurestore_entitytype_feature.go.erb pre_delete: templates/terraform/constants/vertex_ai_featurestore_entitytype_feature.go.erb MetadataStore: !ruby/object:Overrides::Terraform::ResourceOverride autogen_async: false From 0b04587fb24392c7b3f71ceb9c1137463496ce07 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Mon, 10 Oct 2022 22:08:57 +0000 Subject: [PATCH 5/9] Add a handwritten test that includes an update. --- mmv1/products/vertexai/terraform.yaml | 2 + .../tests/resource_vertex_ai_endpoint_test.go | 185 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 4407acd9c372..7a0618e513c3 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -34,6 +34,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides examples: - !ruby/object:Provider::Terraform::Examples name: "vertex_ai_endpoint_network" + # Test is covered by handwritten test to include an update. + skip_test: true primary_resource_id: "endpoint" vars: name: "vertex_ai_endpoint" diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go new file mode 100644 index 000000000000..58d47dec143f --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go @@ -0,0 +1,185 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVertexAIEndpoint_vertexAiEndpointNetwork(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name, + "network_name": BootstrapSharedTestNetwork(t, "vertex"), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVertexAIEndpointDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccVertexAIEndpoint_vertexAiEndpointNetwork(context), + }, + { + ResourceName: "google_vertex_ai_endpoint.endpoint", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag", "location"}, + }, + { + Config: testAccVertexAIEndpoint_vertexAiEndpointNetworkUpdate(context), + }, + { + ResourceName: "google_vertex_ai_endpoint.endpoint", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag", "location"}, + }, + }, + }) +} + +func testAccVertexAIEndpoint_vertexAiEndpointNetwork(context map[string]interface{}) string { + return Nprintf(` +resource "google_vertex_ai_endpoint" "endpoint" { + display_name = "sample-endpoint" + description = "A sample vertex endpoint" + location = "us-central1" + labels = { + label-one = "value-one" + } + network = "projects/${data.google_project.project.number}/global/networks/${data.google_compute_network.vertex_network.name}" + encryption_spec { + kms_key_name = "%{kms_key_name}" + } + depends_on = [ + google_service_networking_connection.vertex_vpc_connection + ] +} + +resource "google_service_networking_connection" "vertex_vpc_connection" { + network = data.google_compute_network.vertex_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.vertex_range.name] +} + +resource "google_compute_global_address" "vertex_range" { + name = "tf-test-address-name%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 24 + network = data.google_compute_network.vertex_network.id +} + +data "google_compute_network" "vertex_network" { + name = "%{network_name}" +} + +resource "google_kms_crypto_key_iam_member" "crypto_key" { + crypto_key_id = "%{kms_key_name}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-aiplatform.iam.gserviceaccount.com" +} + +data "google_project" "project" {} +`, context) +} + +func testAccVertexAIEndpoint_vertexAiEndpointNetworkUpdate(context map[string]interface{}) string { + return Nprintf(` +resource "google_vertex_ai_endpoint" "endpoint" { + display_name = "new-sample-endpoint" + description = "An updated sample vertex endpoint" + location = "us-central1" + labels = { + label-two = "value-two" + } + network = "projects/${data.google_project.project.number}/global/networks/${data.google_compute_network.vertex_network.name}" + encryption_spec { + kms_key_name = "%{kms_key_name}" + } + depends_on = [ + google_service_networking_connection.vertex_vpc_connection + ] +} + +resource "google_service_networking_connection" "vertex_vpc_connection" { + network = data.google_compute_network.vertex_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.vertex_range.name] +} + +resource "google_compute_global_address" "vertex_range" { + name = "tf-test-address-name%{random_suffix}" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 24 + network = data.google_compute_network.vertex_network.id +} + +data "google_compute_network" "vertex_network" { + name = "%{network_name}" +} + +resource "google_kms_crypto_key_iam_member" "crypto_key" { + crypto_key_id = "%{kms_key_name}" + role = "roles/cloudkms.cryptoKeyEncrypterDecrypter" + member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-aiplatform.iam.gserviceaccount.com" +} + +data "google_project" "project" {} +`, context) +} + +func testAccCheckVertexAIEndpointDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_vertex_ai_endpoint" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{VertexAIBasePath}}projects/{{project}}/locations/{{location}}/endpoints/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("VertexAIEndpoint still exists at %s", url) + } + } + + return nil + } +} From a07c33faff7dfb3b633bd9b0882d055bd5de5935 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Thu, 20 Oct 2022 22:02:21 +0000 Subject: [PATCH 6/9] Make endpoint name user-specified. --- mmv1/products/vertexai/api.yaml | 5 ++--- mmv1/products/vertexai/terraform.yaml | 1 + .../terraform/examples/vertex_ai_endpoint_network.tf.erb | 1 + .../terraform/tests/resource_vertex_ai_endpoint_test.go | 3 +++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 4ceae9cc20ef..9fc9f4727c1a 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -111,7 +111,7 @@ objects: - !ruby/object:Api::Resource name: Endpoint base_url: projects/{{project}}/locations/{{location}}/endpoints - create_url: projects/{{project}}/locations/{{location}}/endpoints + create_url: projects/{{project}}/locations/{{location}}/endpoints?endpointId={{name}} self_link: 'projects/{{project}}/locations/{{location}}/endpoints/{{name}}' update_verb: :PATCH update_mask: true @@ -151,8 +151,7 @@ objects: properties: - !ruby/object:Api::Type::String name: name - description: Output only. The resource name of the Endpoint. - output: true + description: The resource name of the Endpoint. - !ruby/object:Api::Type::String name: displayName description: Required. The display name of the Endpoint. The name can be up to 128 characters long and can consist of any UTF-8 characters. diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 7a0618e513c3..d17f17157b5f 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -40,6 +40,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides vars: name: "vertex_ai_endpoint" project: "vertex-ai" + endpoint_name: "endpoint-name" address_name: "address-name" kms_key_name: "kms-name" network_name: "network-name" diff --git a/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb index 4de7864561db..20d511947136 100644 --- a/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb +++ b/mmv1/templates/terraform/examples/vertex_ai_endpoint_network.tf.erb @@ -1,4 +1,5 @@ resource "google_vertex_ai_endpoint" "<%= ctx[:primary_resource_id] %>" { + name = "<%= ctx[:vars]['endpoint_name'] %>" display_name = "sample-endpoint" description = "A sample vertex endpoint" location = "us-central1" diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go index 58d47dec143f..eda5b4d3efad 100644 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go @@ -27,6 +27,7 @@ func TestAccVertexAIEndpoint_vertexAiEndpointNetwork(t *testing.T) { t.Parallel() context := map[string]interface{}{ + "endpoint_name": fmt.Sprint(randInt(t) % 9999999999), "kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name, "network_name": BootstrapSharedTestNetwork(t, "vertex"), "random_suffix": randString(t, 10), @@ -62,6 +63,7 @@ func TestAccVertexAIEndpoint_vertexAiEndpointNetwork(t *testing.T) { func testAccVertexAIEndpoint_vertexAiEndpointNetwork(context map[string]interface{}) string { return Nprintf(` resource "google_vertex_ai_endpoint" "endpoint" { + name = "%{endpoint_name}" display_name = "sample-endpoint" description = "A sample vertex endpoint" location = "us-central1" @@ -108,6 +110,7 @@ data "google_project" "project" {} func testAccVertexAIEndpoint_vertexAiEndpointNetworkUpdate(context map[string]interface{}) string { return Nprintf(` resource "google_vertex_ai_endpoint" "endpoint" { + name = "%{endpoint_name}" display_name = "new-sample-endpoint" description = "An updated sample vertex endpoint" location = "us-central1" From 03000b171488d411dd29c36872da979aa1d2b788 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Thu, 20 Oct 2022 22:03:32 +0000 Subject: [PATCH 7/9] Skip the vertex endpoint test. --- .../terraform/tests/resource_vertex_ai_endpoint_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go index eda5b4d3efad..493b07d5aafd 100644 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go @@ -24,6 +24,8 @@ import ( ) func TestAccVertexAIEndpoint_vertexAiEndpointNetwork(t *testing.T) { + t.Skip("Skip this test until b/254073292 is resolved") + t.Parallel() context := map[string]interface{}{ From 541d262238dc6f3821efad65455764bf6eb9d978 Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Thu, 20 Oct 2022 23:39:58 +0000 Subject: [PATCH 8/9] Make vertex endpoint name field required, url param only, and immutable. --- mmv1/products/vertexai/api.yaml | 3 +++ mmv1/products/vertexai/terraform.yaml | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 9fc9f4727c1a..c209aa4d8eb8 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -152,6 +152,9 @@ objects: - !ruby/object:Api::Type::String name: name description: The resource name of the Endpoint. + url_param_only: true + required: true + input: true - !ruby/object:Api::Type::String name: displayName description: Required. The display name of the Endpoint. The name can be up to 128 characters long and can consist of any UTF-8 characters. diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index d17f17157b5f..c56298656a29 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -50,8 +50,6 @@ overrides: !ruby/object:Overrides::ResourceOverrides properties: etag: !ruby/object:Overrides::Terraform::PropertyOverride ignore_read: true - name: !ruby/object:Overrides::Terraform::PropertyOverride - custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb Featurestore: !ruby/object:Overrides::Terraform::ResourceOverride autogen_async: false skip_sweeper: true From 205c1a383c92c7626aaf66cedebbd04c907bbcfc Mon Sep 17 00:00:00 2001 From: Thomas Rodgers Date: Fri, 21 Oct 2022 20:06:53 +0000 Subject: [PATCH 9/9] Unskip test, add link to cloud console to deployedModels description, and describe format of name field. --- mmv1/products/vertexai/api.yaml | 6 +++--- .../terraform/tests/resource_vertex_ai_endpoint_test.go | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index c209aa4d8eb8..1a2a65098adc 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -151,7 +151,7 @@ objects: properties: - !ruby/object:Api::Type::String name: name - description: The resource name of the Endpoint. + description: The resource name of the Endpoint. The name must be numeric with no leading zeros and can be at most 10 digits. url_param_only: true required: true input: true @@ -164,11 +164,11 @@ objects: description: The description of the Endpoint. - !ruby/object:Api::Type::Array name: deployedModels - description: Output only. The models deployed in this Endpoint. To add or remove DeployedModels use EndpointService.DeployModel and EndpointService.UndeployModel respectively. + description: Output only. The models deployed in this Endpoint. To add or remove DeployedModels use EndpointService.DeployModel and EndpointService.UndeployModel respectively. Models can also be deployed and undeployed using the [Cloud Console](https://console.cloud.google.com/vertex-ai/). output: true item_type: !ruby/object:Api::Type::NestedObject name: deployedModels - description: Output only. The models deployed in this Endpoint. To add or remove DeployedModels use EndpointService.DeployModel and EndpointService.UndeployModel respectively. + description: Output only. The models deployed in this Endpoint. To add or remove DeployedModels use EndpointService.DeployModel and EndpointService.UndeployModel respectively. Models can also be deployed and undeployed using the [Cloud Console](https://console.cloud.google.com/vertex-ai/). properties: - !ruby/object:Api::Type::NestedObject name: dedicatedResources diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go index 493b07d5aafd..eda5b4d3efad 100644 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_endpoint_test.go @@ -24,8 +24,6 @@ import ( ) func TestAccVertexAIEndpoint_vertexAiEndpointNetwork(t *testing.T) { - t.Skip("Skip this test until b/254073292 is resolved") - t.Parallel() context := map[string]interface{}{