From 0263f62961da213c14fdc3a7beda460f227e1c13 Mon Sep 17 00:00:00 2001 From: xuzhang3 <57888764+xuzhang3@users.noreply.github.com> Date: Wed, 29 May 2024 19:13:13 +0800 Subject: [PATCH] `azurerm_machine_learning_workspace` - Add support for `serverless_compute` (#25660) * add support for serverlesscompute * add support for serverlesscompute * fix test case * fix test case * fix test case * fix test case * change default value * 1. rename custom_subnet_id to custom_subnet_id 2. rename no_public_ip_enabled to public_ip_enabled 3. add test case for serverless compute 4. update document * formant --- .../machine_learning_workspace_resource.go | 73 ++++++++++++ ...achine_learning_workspace_resource_test.go | 108 ++++++++++++++++++ .../machine_learning_workspace.html.markdown | 12 ++ 3 files changed, 193 insertions(+) diff --git a/internal/services/machinelearning/machine_learning_workspace_resource.go b/internal/services/machinelearning/machine_learning_workspace_resource.go index 94da68f69cab..ea5c0afbfa53 100644 --- a/internal/services/machinelearning/machine_learning_workspace_resource.go +++ b/internal/services/machinelearning/machine_learning_workspace_resource.go @@ -230,6 +230,26 @@ func resourceMachineLearningWorkspace() *pluginsdk.Resource { Default: false, }, + "serverless_compute": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "subnet_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: commonids.ValidateSubnetID, + }, + "public_ip_enabled": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + "discovery_url": { Type: pluginsdk.TypeString, Computed: true, @@ -315,6 +335,22 @@ func resourceMachineLearningWorkspaceCreateOrUpdate(d *pluginsdk.ResourceData, m }, } + serverlessCompute := expandMachineLearningWorkspaceServerlessCompute(d.Get("serverless_compute").([]interface{})) + if serverlessCompute != nil { + if *serverlessCompute.ServerlessComputeNoPublicIP && serverlessCompute.ServerlessComputeCustomSubnet == nil && !networkAccessBehindVnetEnabled { + return fmt.Errorf("`public_ip_enabled` must be set to `true` if `subnet_id` is not set and `public_network_access_enabled` is `false`") + } + + if serverlessCompute.ServerlessComputeCustomSubnet == nil { + oldVal, newVal := d.GetChange("serverless_compute.0.public_ip_enabled") + if oldVal.(bool) && !newVal.(bool) { + return fmt.Errorf(" Not supported to update `public_ip_enabled` from `true` to `false` when `subnet_id` is null or empty") + } + } + } + + workspace.Properties.ServerlessComputeSettings = serverlessCompute + if networkAccessBehindVnetEnabled { workspace.Properties.PublicNetworkAccess = pointer.To(workspaces.PublicNetworkAccessEnabled) } @@ -422,6 +458,7 @@ func resourceMachineLearningWorkspaceRead(d *pluginsdk.ResourceData, meta interf d.Set("v1_legacy_mode_enabled", props.V1LegacyMode) d.Set("workspace_id", props.WorkspaceId) d.Set("managed_network", flattenMachineLearningWorkspaceManagedNetwork(props.ManagedNetwork)) + d.Set("serverless_compute", flattenMachineLearningWorkspaceServerlessCompute(props.ServerlessComputeSettings)) kvId, err := commonids.ParseKeyVaultIDInsensitively(*props.KeyVault) if err != nil { @@ -678,3 +715,39 @@ func flattenMachineLearningWorkspaceManagedNetwork(i *workspaces.ManagedNetworkS return &[]interface{}{out} } + +func expandMachineLearningWorkspaceServerlessCompute(i []interface{}) *workspaces.ServerlessComputeSettings { + if len(i) == 0 || i[0] == nil { + return nil + } + + v := i[0].(map[string]interface{}) + + serverlessCompute := workspaces.ServerlessComputeSettings{ + ServerlessComputeNoPublicIP: pointer.To(!v["public_ip_enabled"].(bool)), + } + + if subnetId, ok := v["subnet_id"].(string); ok && subnetId != "" { + serverlessCompute.ServerlessComputeCustomSubnet = pointer.To(subnetId) + } + + return &serverlessCompute +} + +func flattenMachineLearningWorkspaceServerlessCompute(i *workspaces.ServerlessComputeSettings) *[]interface{} { + if i == nil { + return &[]interface{}{} + } + + out := map[string]interface{}{} + + if i.ServerlessComputeCustomSubnet != nil { + out["subnet_id"] = *i.ServerlessComputeCustomSubnet + } + + if i.ServerlessComputeNoPublicIP != nil { + out["public_ip_enabled"] = !*i.ServerlessComputeNoPublicIP + } + + return &[]interface{}{out} +} diff --git a/internal/services/machinelearning/machine_learning_workspace_resource_test.go b/internal/services/machinelearning/machine_learning_workspace_resource_test.go index 0362af996d76..f6c8d940902d 100644 --- a/internal/services/machinelearning/machine_learning_workspace_resource_test.go +++ b/internal/services/machinelearning/machine_learning_workspace_resource_test.go @@ -353,6 +353,41 @@ func TestAccMachineLearningWorkspace_purgeSoftDelete(t *testing.T) { }) } +func TestAccMachineLearningWorkspace_serverlessCompute(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_workspace", "test") + r := WorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.serverlessCompute(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("serverless_compute.#").HasValue("1"), + check.That(data.ResourceName).Key("serverless_compute.0.subnet_id").Exists(), + check.That(data.ResourceName).Key("serverless_compute.0.public_ip_enabled").HasValue("false"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccMachineLearningWorkspace_serverlessCompute_withoutSubnet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_machine_learning_workspace", "test") + r := WorkspaceResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.serverlessComputeWithoutSubnet(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("serverless_compute.#").HasValue("1"), + check.That(data.ResourceName).Key("serverless_compute.0.public_ip_enabled").HasValue("true"), + ), + }, + data.ImportStep(), + }) +} + func (r WorkspaceResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { workspacesClient := client.MachineLearning.Workspaces id, err := workspaces.ParseWorkspaceID(state.ID) @@ -1166,3 +1201,76 @@ resource "azurerm_machine_learning_workspace" "test" { } `, template, data.RandomInteger) } + +func (r WorkspaceResource) serverlessCompute(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[2]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} +resource "azurerm_subnet" "test" { + name = "internal" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_machine_learning_workspace" "test" { + name = "acctest-MLW-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_insights_id = azurerm_application_insights.test.id + key_vault_id = azurerm_key_vault.test.id + storage_account_id = azurerm_storage_account.test.id + + serverless_compute { + subnet_id = azurerm_subnet.test.id + } + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger) +} + +func (r WorkspaceResource) serverlessComputeWithoutSubnet(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network" "test" { + name = "acctestvirtnet%[2]d" + address_space = ["10.0.0.0/16"] + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} +resource "azurerm_subnet" "test" { + name = "internal" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.0.2.0/24"] +} + +resource "azurerm_machine_learning_workspace" "test" { + name = "acctest-MLW-%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + application_insights_id = azurerm_application_insights.test.id + key_vault_id = azurerm_key_vault.test.id + storage_account_id = azurerm_storage_account.test.id + + serverless_compute { + public_ip_enabled = true + } + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger) +} diff --git a/website/docs/r/machine_learning_workspace.html.markdown b/website/docs/r/machine_learning_workspace.html.markdown index b71b13a3c37a..a11d26a1be3e 100644 --- a/website/docs/r/machine_learning_workspace.html.markdown +++ b/website/docs/r/machine_learning_workspace.html.markdown @@ -390,6 +390,8 @@ The following arguments are supported: * `sku_name` - (Optional) SKU/edition of the Machine Learning Workspace, possible values are `Free`, `Basic`, `Standard` and `Premium`. Defaults to `Basic`. +* `serverless_compute` - (Optional) A `serverless_compute` block as defined below. + * `tags` - (Optional) A mapping of tags to assign to the resource. --- @@ -422,6 +424,16 @@ An `managed_network` block supports the following: --- +A `serverless_compute` block supports the following: + +* `subnet_id` - (Optional) The ID of an existing Virtual Network Subnet in which the serverless compute nodes should be deployed to. + +* `public_ip_enabled` - (Optional) Should serverless compute nodes deployed in a custom Virtual Network have public IP addresses enabled for a workspace with private endpoint? Defaults to `false`. + +~> **Note:** `public_ip_enabled` cannot be updated from `true` to `false` when `subnet_id` is not set. `public_ip_enabled` must be set to `true` if `subnet_id` is not set and when `public_network_access_enabled` is `false`. + +--- + An `feature_store` block supports the following: * `computer_spark_runtime_version` - (Optional) The version of Spark runtime.