Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vertex-ai: add :UPLOAD support for model resource #12075

Open
wants to merge 14 commits into
base: vertex-ai-model-resource
Choose a base branch
from
111 changes: 19 additions & 92 deletions mmv1/products/vertexai/Model.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ id_format: '{{name}}'
update_verb: 'PATCH'
exclude_import: true
async:
actions: ['create', 'delete']
actions: ['create', 'delete', 'update']
type: 'OpAsync'
operation:
base_url: '{{op_id}}'
Expand All @@ -49,6 +49,12 @@ custom_code:
pre_create: templates/terraform/pre_create/vertex_ai_models.go.tmpl
post_create: templates/terraform/post_create/vertex_ai_models.go.tmpl
examples:
- name: 'vertex_ai_model_upload_basic'
exclude_import_test: true
primary_resource_id: 'model'
vars:
project_name: 'PROJECT_NAME'
model_name: 'MODEL_NAME'
- name: 'vertex_ai_model_source_basic_1'
exclude_import_test: true
primary_resource_id: 'model'
Expand Down Expand Up @@ -103,6 +109,7 @@ properties:
The description of this version
- type: String
name: 'metadataSchemaUri'
ignore_read: true
immutable: true
description: |
Points to a YAML file stored on Google Cloud Storage describing additional information about the Model, that is specific to it.
Expand Down Expand Up @@ -297,8 +304,10 @@ properties:
description: |
The timestamp of when the MetadataStore was last updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.
- type: String
name: 'displayName'
default_from_api: true
name: 'modelName'
at_least_one_of:
- modelName
- sourceModel
description:
The display name of the Model. The name can be up to 128 characters long
and can consist of any UTF-8 characters.
Expand All @@ -309,9 +318,9 @@ properties:
ignore_read: true
- type: Array
name: 'versionAliases'
Copy link
Collaborator Author

@BBBmau BBBmau Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently trying to resolve the diff we get on versionAliases where a user inputs their field [v2beta2] but get returned a diff on a second apply due to default_from_api returning [default, v2beta2]

although we could remove default_from_api this is necessary for copy to work since we don't input versionAlias yet we still expect a value from the model we want to copy.

In this case we can input the value but we should expect it to have default already as part of the config. I haven't been able to figure out a proper solution. A DiffSuppress was the first idea but because this is an array we are unable to use it (unless there's a hacky way that I don't know about. Leaving this for suggestions @SarahFrench

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some further investigation we may want to consider removing this for the time being, there is currently no way to include suppot of StateFunc which would help with supporting this (more can be found here). A Diff_suppress_func can't be applied due to lack of support on Lists.

I was almost convinced to leave it with ignore_read: true though this leaves us with a case where we don't receive the version_aliases when copying (e.g the target model contains v1beta1 but instead is set as null when attempting to copy)

Because of the reasons above it may be best to remove this field. Any thoughts on this would be appreciated.

default_from_api: true
description:
user provided version aliases so that a model version can be referenced via alias.
ignore_read: true
item_type:
type: String
- type: NestedObject
Expand All @@ -326,105 +335,23 @@ properties:
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 resource is created.
immutable: true
- type: NestedObject
name: 'metadata'
description: An additional information about the Index
properties:
- type: NestedObject
name: 'config'
immutable: true
description: The configuration of the Matching Engine Index.
properties:
- type: Integer
name: 'dimensions'
description: The number of dimensions of the input vectors.
required: true
- type: Integer
name: 'approximateNeighborsCount'
description: |-
The default number of neighbors to find via approximate search before exact reordering is
performed. Exact reordering is a procedure where results returned by an
approximate search algorithm are reordered via a more expensive distance computation.
Required if tree-AH algorithm is used.
- type: String
name: 'shardSize'
description: |-
Index data is split into equal parts to be processed. These are called "shards".
The shard size must be specified when creating an index. The value must be one of the followings:
* SHARD_SIZE_SMALL: Small (2GB)
* SHARD_SIZE_MEDIUM: Medium (20GB)
* SHARD_SIZE_LARGE: Large (50GB)
immutable: true
default_from_api: true
- type: String
name: 'distanceMeasureType'
description: |-
The distance measure used in nearest neighbor search. The value must be one of the followings:
* SQUARED_L2_DISTANCE: Euclidean (L_2) Distance
* L1_DISTANCE: Manhattan (L_1) Distance
* COSINE_DISTANCE: Cosine Distance. Defined as 1 - cosine similarity.
* DOT_PRODUCT_DISTANCE: Dot Product Distance. Defined as a negative of the dot product
default_value: 'DOT_PRODUCT_DISTANCE'
- type: String
name: 'featureNormType'
description: |-
Type of normalization to be carried out on each vector. The value must be one of the followings:
* UNIT_L2_NORM: Unit L2 normalization type
* NONE: No normalization type is specified.
default_value: 'NONE'
ignore_read: true
- type: NestedObject
name: 'algorithmConfig'
description:
The configuration with regard to the algorithms used for efficient
search.
properties:
- type: NestedObject
name: 'treeAhConfig'
exactly_one_of:
- treeAhConfig
- bruteForceConfig
description: |-
Configuration options for using the tree-AH algorithm (Shallow tree + Asymmetric Hashing).
Please refer to this paper for more details: https://arxiv.org/abs/1908.10396
properties:
- type: Integer
name: 'leafNodeEmbeddingCount'
description:
Number of embeddings on each leaf node. The default value
is 1000 if not set.
default_value: 1000
- type: Integer
name: 'leafNodesToSearchPercent'
description: |-
The default percentage of leaf nodes that any query may be searched. Must be in
range 1-100, inclusive. The default value is 10 (means 10%) if not set.
default_value: 10
- type: NestedObject
name: 'bruteForceConfig'
allow_empty_object: true
send_empty_value: true
properties: []
exactly_one_of:
- treeAhConfig
- bruteForceConfig
description: |-
Configuration options for using brute force search, which simply implements the
standard linear search in the database for each query.
- name: 'metadata'
type: KeyValuePairs
description: An additional information about the Model; the schema of the metadata can be found in metadataSchemaUri. Unset if the Model does not have any additional information.
- type: String
name: 'sourceModel'
ignore_read: true
immutable: true
default_from_api: true
at_least_one_of:
- modelName
- sourceModel
description: |
The resource name of the Model to copy. That Model must be in the same Project
- type: String
name: 'modelId'
immutable: true
ignore_read: true
default_from_api: true
# modelId is never returned from the API as a field, but we set its value from API-returned data when it's not
# provided by the user as an input
default_from_api: true
description: |
Copy sourceModel into a new Model with this ID. The ID will become the final component of the model resource name.
4 changes: 4 additions & 0 deletions mmv1/templates/terraform/constants/vertex_ai_model.go.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
func vertexAiModelDiffSuppress(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error {
// we require diffSuppress on COPY method and not UPLOAD
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is included so that we can fill out the description field on create when wanting to upload.

As a reminder this diffsuppress was created because of copy not supporting description field on create. More info can be found in this thread. #12074 (comment)

if _, ok := diff.GetOkExists("model_name"); ok {
return nil
}
// If the resource has no id then it hasn't been created yet
// Check to see if any fields are set in the config at create time that shouldn't be there
if diff.Id() == "" {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
func flatten{{$.GetPrefix}}{{$.TitlelizeProperty}}(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
if d.Get("name").(string) == "" {
return v.(string)
}

return d.Get("name")
}
9 changes: 9 additions & 0 deletions mmv1/templates/terraform/decoders/vertex_ai_models.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,14 @@ if modelName, ok := res["model"].(string); ok && modelName != "" {
res["name"] = modelName
}

if _, ok := d.GetOk("source_model"); ok {
res["sourceModel"] = d.Get("source_model").(string)
}

if _, ok := d.GetOk("model_id"); ok {
res["modelId"] = d.Get("model_id").(string)
}

res["modelName"] = res["displayName"]

return res, nil
18 changes: 17 additions & 1 deletion mmv1/templates/terraform/encoders/vertex_ai_models.go.tmpl
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
return obj, nil
// Copy encoder
if obj["sourceModel"] != nil {
delete(obj, "labels")
delete(obj, "metadata")
return obj, nil
}

// Upload encoder
obj["displayName"] = obj["modelName"]
delete(obj, "modelName")

newObj := make(map[string]interface{})
newObj["model"] = obj
newObj["modelId"] = obj["modelId"]
delete(obj, "modelId")

return newObj, nil
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
resource "google_vertex_ai_model" "{{$.PrimaryResourceId}}" {
model_name = "{{index $.Vars "model_name"}}"
description = "basic upload model"

version_aliases = ["v2beta1"]
model_id = "id_upload_test"

metadata_schema_uri = "gs://google-cloud-aiplatform/schema/model/metadata/custom_1.0.0.yaml"

region = "us-central1"
}
3 changes: 3 additions & 0 deletions mmv1/templates/terraform/pre_create/vertex_ai_models.go.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
var postRequestType string
if _, ok := d.GetOk("model_name"); ok {
postRequestType = ":upload"
}

if _, ok := d.GetOk("source_model"); ok {
postRequestType = ":copy"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ package vertexai_test

import (
"fmt"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"

"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)

func TestAccVertexAIModel_postCreationUpdates(t *testing.T) {
Expand Down Expand Up @@ -130,42 +126,3 @@ resource "google_vertex_ai_model" "model" {
}
`, context)
}

func testAccCheckVertexAIModelDestroyProducer(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_model" {
continue
}
if strings.HasPrefix(name, "data.") {
continue
}

config := acctest.GoogleProviderConfig(t)

url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{VertexAIBasePath}}projects/{{project}}/locations/{{region}}/models/{{model_id}}")
if err != nil {
return err
}

billingProject := ""

if config.BillingProject != "" {
billingProject = config.BillingProject
}

_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
Project: billingProject,
RawURL: url,
UserAgent: config.UserAgent,
})
if err == nil {
return fmt.Errorf("VertexAIModel still exists at %s", url)
}
}

return nil
}
}
Loading