From 88f997dc82279d7b8baf219970efde491f7c3bbc Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Fri, 26 Jun 2026 21:36:12 +0000 Subject: [PATCH] Add ANY_RESERVATION_THEN_FAIL reservation affinity to container module (#18094) [upstream:6469167cc4ade51af5592a1a9852d7da23f3ff74] Signed-off-by: Modular Magician --- .changelog/18094.txt | 3 + go.mod | 2 +- go.sum | 4 +- google/services/container/node_config.go | 2 +- .../resource_container_cluster_test.go | 101 ++++++++++++++++++ .../resource_container_node_pool_test.go | 81 ++++++++++++++ .../docs/r/container_cluster.html.markdown | 3 +- .../docs/r/container_node_pool.html.markdown | 3 +- 8 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 .changelog/18094.txt diff --git a/.changelog/18094.txt b/.changelog/18094.txt new file mode 100644 index 00000000000..cd147d05482 --- /dev/null +++ b/.changelog/18094.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +container: added `ANY_RESERVATION_THEN_FAIL` option to `consume_reservation_type` field in `google_container_cluster` and `google_container_node_pool` resources +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 75484a9766e..d64a7d5e325 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/net v0.56.0 golang.org/x/oauth2 v0.36.0 - google.golang.org/api v0.285.0 + google.golang.org/api v0.286.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad google.golang.org/grpc v1.81.1 google.golang.org/protobuf v1.36.11 diff --git a/go.sum b/go.sum index 6c0b656ffd2..251248a379f 100644 --- a/go.sum +++ b/go.sum @@ -429,8 +429,8 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.285.0 h1:B7eHHoKGAX/LrPkQvhQqnGwjgWxofbdGwCTQvpm8FkM= -google.golang.org/api v0.285.0/go.mod h1:NlOlUIr8MPoIhT9Bb/oUnRuHbJOLwxb6JSYJM8Yz+jQ= +google.golang.org/api v0.286.0 h1:TdTXMvzYKnWV1/lPbCdbXRqBrkDqjPto22H2xeZZ8LI= +google.golang.org/api v0.286.0/go.mod h1:NlOlUIr8MPoIhT9Bb/oUnRuHbJOLwxb6JSYJM8Yz+jQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= diff --git a/google/services/container/node_config.go b/google/services/container/node_config.go index a23be3bd01d..0bdb1b47184 100644 --- a/google/services/container/node_config.go +++ b/google/services/container/node_config.go @@ -593,7 +593,7 @@ func schemaNodeConfig() *schema.Schema { Required: true, ForceNew: true, Description: `Corresponds to the type of reservation consumption.`, - ValidateFunc: validation.StringInSlice([]string{"UNSPECIFIED", "NO_RESERVATION", "ANY_RESERVATION", "SPECIFIC_RESERVATION"}, false), + ValidateFunc: validation.StringInSlice([]string{"UNSPECIFIED", "NO_RESERVATION", "ANY_RESERVATION", "SPECIFIC_RESERVATION", "ANY_RESERVATION_THEN_FAIL"}, false), }, "key": { Type: schema.TypeString, diff --git a/google/services/container/resource_container_cluster_test.go b/google/services/container/resource_container_cluster_test.go index 5d72c72a6ce..9a3a50aca62 100644 --- a/google/services/container/resource_container_cluster_test.go +++ b/google/services/container/resource_container_cluster_test.go @@ -2931,6 +2931,38 @@ func TestAccContainerCluster_withNodeConfigReservationAffinitySpecific(t *testin }) } +func TestAccContainerCluster_withNodeConfigReservationAffinityAnyReservationThenFail(t *testing.T) { + t.Parallel() + + reservationName := fmt.Sprintf("tf-test-reservation-%s", acctest.RandString(t, 10)) + clusterName := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10)) + networkName := tpgcompute.BootstrapSharedTestNetwork(t, "gke-cluster") + subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccContainerCluster_withNodeConfigReservationAffinityAnyReservationThenFail(reservationName, clusterName, networkName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_container_cluster.with_node_config", + "node_config.0.reservation_affinity.#", "1"), + resource.TestCheckResourceAttr("google_container_cluster.with_node_config", + "node_config.0.reservation_affinity.0.consume_reservation_type", "ANY_RESERVATION_THEN_FAIL"), + ), + }, + { + ResourceName: "google_container_cluster.with_node_config", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"deletion_protection"}, + }, + }, + }) +} + func TestAccContainerCluster_withNodeConfigNodeImageConfig(t *testing.T) { t.Parallel() @@ -9721,6 +9753,75 @@ resource "google_container_cluster" "with_node_config" { `, reservation, clusterName, networkName, subnetworkName) } +func testAccContainerCluster_withNodeConfigReservationAffinityAnyReservationThenFail(reservation, clusterName, networkName, subnetworkName string) string { + return fmt.Sprintf(` + +resource "google_project_service" "compute" { + service = "compute.googleapis.com" +} + +resource "google_project_service" "container" { + service = "container.googleapis.com" + depends_on = [google_project_service.compute] +} + + +resource "google_compute_reservation" "gce_reservation" { + name = "%s" + zone = "us-central1-f" + + specific_reservation { + count = 1 + instance_properties { + machine_type = "n1-standard-1" + } + } + + specific_reservation_required = false + depends_on = [google_project_service.compute] +} + +resource "google_container_cluster" "with_node_config" { + name = "%s" + location = "us-central1-f" + initial_node_count = 1 + + node_config { + machine_type = "n1-standard-1" + disk_size_gb = 15 + disk_type = "pd-ssd" + oauth_scopes = [ + "https://www.googleapis.com/auth/monitoring", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/devstorage.read_only", + "https://www.googleapis.com/auth/logging.write", + ] + service_account = "default" + metadata = { + foo = "bar" + disable-legacy-endpoints = "true" + } + labels = { + foo = "bar" + } + tags = ["foo", "bar"] + + // Updatable fields + image_type = "COS_CONTAINERD" + + reservation_affinity { + consume_reservation_type = "ANY_RESERVATION_THEN_FAIL" + } + } + network = "%s" + subnetwork = "%s" + + deletion_protection = false + depends_on = [google_project_service.container] +} +`, reservation, clusterName, networkName, subnetworkName) +} + func testAccContainerCluster_withNodeConfigNodeImageConfig(clusterName, networkName, subnetworkName, imageName, imageProject string) string { return fmt.Sprintf(` diff --git a/google/services/container/resource_container_node_pool_test.go b/google/services/container/resource_container_node_pool_test.go index bf7c4f5b256..f01fa0c9b8a 100644 --- a/google/services/container/resource_container_node_pool_test.go +++ b/google/services/container/resource_container_node_pool_test.go @@ -791,6 +791,38 @@ func TestAccContainerNodePool_withReservationAffinitySpecific(t *testing.T) { }) } +func TestAccContainerNodePool_withReservationAffinityAnyReservationThenFail(t *testing.T) { + t.Parallel() + + cluster := fmt.Sprintf("tf-test-cluster-%s", acctest.RandString(t, 10)) + reservation := fmt.Sprintf("tf-test-reservation-%s", acctest.RandString(t, 10)) + np := fmt.Sprintf("tf-test-np-%s", acctest.RandString(t, 10)) + networkName := tpgcompute.BootstrapSharedTestNetwork(t, "gke-cluster") + subnetworkName := tpgcompute.BootstrapSubnet(t, "gke-cluster", networkName) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckContainerClusterDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccContainerNodePool_withReservationAffinityAnyReservationThenFail(cluster, reservation, np, networkName, subnetworkName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity", + "node_config.0.reservation_affinity.#", "1"), + resource.TestCheckResourceAttr("google_container_node_pool.with_reservation_affinity", + "node_config.0.reservation_affinity.0.consume_reservation_type", "ANY_RESERVATION_THEN_FAIL"), + ), + }, + { + ResourceName: "google_container_node_pool.with_reservation_affinity", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccContainerNodePool_withWorkloadIdentityConfig(t *testing.T) { t.Parallel() @@ -3838,6 +3870,55 @@ resource "google_container_node_pool" "with_reservation_affinity" { `, cluster, networkName, subnetworkName, reservation, np) } +func testAccContainerNodePool_withReservationAffinityAnyReservationThenFail(cluster, reservation, np, networkName, subnetworkName string) string { + return fmt.Sprintf(` +data "google_container_engine_versions" "central1a" { + location = "us-central1-a" +} + +resource "google_container_cluster" "cluster" { + name = "%s" + location = "us-central1-a" + initial_node_count = 1 + min_master_version = data.google_container_engine_versions.central1a.latest_master_version + deletion_protection = false + network = "%s" + subnetwork = "%s" +} + +resource "google_compute_reservation" "gce_reservation" { + name = "%s" + zone = "us-central1-a" + + specific_reservation { + count = 1 + instance_properties { + machine_type = "n1-standard-1" + } + } + + specific_reservation_required = false +} + +resource "google_container_node_pool" "with_reservation_affinity" { + name = "%s" + location = "us-central1-a" + cluster = google_container_cluster.cluster.name + initial_node_count = 1 + node_config { + machine_type = "n1-standard-1" + oauth_scopes = [ + "https://www.googleapis.com/auth/logging.write", + "https://www.googleapis.com/auth/monitoring", + ] + reservation_affinity { + consume_reservation_type = "ANY_RESERVATION_THEN_FAIL" + } + } +} +`, cluster, networkName, subnetworkName, reservation, np) +} + func testAccContainerNodePool_withWorkloadMetadataConfig(cluster, np, networkName, subnetworkName string) string { return fmt.Sprintf(` data "google_container_engine_versions" "central1a" { diff --git a/website/docs/r/container_cluster.html.markdown b/website/docs/r/container_cluster.html.markdown index 1b786cf2cb6..e2b84265bfb 100644 --- a/website/docs/r/container_cluster.html.markdown +++ b/website/docs/r/container_cluster.html.markdown @@ -1521,8 +1521,9 @@ not. * `"UNSPECIFIED"`: Default value. This should not be used. * `"NO_RESERVATION"`: Do not consume from any reserved capacity. - * `"ANY_RESERVATION"`: Consume any reservation available. + * `"ANY_RESERVATION"`: Consume any non-specific reservation available, with a fallback to on-demand capacity in case of none reservaition being claimable. * `"SPECIFIC_RESERVATION"`: Must consume from a specific reservation. Must specify key value fields for specifying the reservations. + * `"ANY_RESERVATION_THEN_FAIL"`: Consume any non-specific reservation available, without a fallback to on-demand capacity in case of none reservaition being claimable. * `key` (Optional) The label key of a reservation resource. To target a SPECIFIC_RESERVATION by name, specify "compute.googleapis.com/reservation-name" as the key and specify the name of your reservation as its value. * `values` (Optional) The list of label values of reservation resources. For example: the name of the specific reservation when using a key of "compute.googleapis.com/reservation-name" diff --git a/website/docs/r/container_node_pool.html.markdown b/website/docs/r/container_node_pool.html.markdown index ea0ccfb9879..a147492aea7 100644 --- a/website/docs/r/container_node_pool.html.markdown +++ b/website/docs/r/container_node_pool.html.markdown @@ -336,8 +336,9 @@ cluster. * `"UNSPECIFIED"`: Default value. This should not be used. * `"NO_RESERVATION"`: Do not consume from any reserved capacity. - * `"ANY_RESERVATION"`: Consume any reservation available. + * `"ANY_RESERVATION"`: Consume any non-specific reservation available, with a fallback to on-demand capacity in case of none reservaition being claimable. * `"SPECIFIC_RESERVATION"`: Must consume from a specific reservation. Must specify key value fields for specifying the reservations. + * `"ANY_RESERVATION_THEN_FAIL"`: Consume any non-specific reservation available, without a fallback to on-demand capacity in case of none reservaition being claimable. * `key` (Optional) The label key of a reservation resource. To target a SPECIFIC_RESERVATION by name, specify "compute.googleapis.com/reservation-name" as the key and specify the name of your reservation as its value. * `values` (Optional) The list of label values of reservation resources. For example: the name of the specific reservation when using a key of "compute.googleapis.com/reservation-name"