Skip to content

Commit 611ee39

Browse files
Compute: add support for instance reservation_affinity (#4335) (#3288)
Signed-off-by: Modular Magician <[email protected]>
1 parent 566b982 commit 611ee39

7 files changed

+454
-1
lines changed

.changelog/4335.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
compute: added `reservation_affinity` to `google_compute_instance` and `google_compute_instance_template`
3+
```

google-beta/compute_instance_helpers.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,3 +443,54 @@ func hasNodeAffinitiesChanged(oScheduling, newScheduling map[string]interface{})
443443

444444
return false
445445
}
446+
447+
func expandReservationAffinity(d *schema.ResourceData) (*computeBeta.ReservationAffinity, error) {
448+
_, ok := d.GetOk("reservation_affinity")
449+
if !ok {
450+
return nil, nil
451+
}
452+
453+
prefix := "reservation_affinity.0"
454+
reservationAffinityType := d.Get(prefix + ".type").(string)
455+
456+
affinity := computeBeta.ReservationAffinity{
457+
ConsumeReservationType: reservationAffinityType,
458+
ForceSendFields: []string{"ConsumeReservationType"},
459+
}
460+
461+
_, hasSpecificReservation := d.GetOk(prefix + ".specific_reservation")
462+
if (reservationAffinityType == "SPECIFIC_RESERVATION") != hasSpecificReservation {
463+
return nil, fmt.Errorf("specific_reservation must be set when reservation_affinity is SPECIFIC_RESERVATION, and not set otherwise")
464+
}
465+
466+
prefix = prefix + ".specific_reservation.0"
467+
if hasSpecificReservation {
468+
affinity.Key = d.Get(prefix + ".key").(string)
469+
affinity.ForceSendFields = append(affinity.ForceSendFields, "Key", "Values")
470+
471+
for _, v := range d.Get(prefix + ".values").([]interface{}) {
472+
affinity.Values = append(affinity.Values, v.(string))
473+
}
474+
}
475+
476+
return &affinity, nil
477+
}
478+
479+
func flattenReservationAffinity(affinity *computeBeta.ReservationAffinity) []map[string]interface{} {
480+
if affinity == nil {
481+
return nil
482+
}
483+
484+
flattened := map[string]interface{}{
485+
"type": affinity.ConsumeReservationType,
486+
}
487+
488+
if affinity.ConsumeReservationType == "SPECIFIC_RESERVATION" {
489+
flattened["specific_reservation"] = []map[string]interface{}{{
490+
"key": affinity.Key,
491+
"values": affinity.Values,
492+
}}
493+
}
494+
495+
return []map[string]interface{}{flattened}
496+
}

google-beta/resource_compute_instance.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,51 @@ func resourceComputeInstance() *schema.Resource {
737737
MaxItems: 1,
738738
Description: `A list of short names or self_links of resource policies to attach to the instance. Modifying this list will cause the instance to recreate. Currently a max of 1 resource policy is supported.`,
739739
},
740+
741+
"reservation_affinity": {
742+
Type: schema.TypeList,
743+
MaxItems: 1,
744+
Optional: true,
745+
ForceNew: true,
746+
Description: `Specifies the reservations that this instance can consume from.`,
747+
Elem: &schema.Resource{
748+
Schema: map[string]*schema.Schema{
749+
"type": {
750+
Type: schema.TypeString,
751+
Required: true,
752+
ForceNew: true,
753+
ValidateFunc: validation.StringInSlice([]string{"ANY_RESERVATION", "SPECIFIC_RESERVATION", "NO_RESERVATION"}, false),
754+
Description: `The type of reservation from which this instance can consume resources.`,
755+
},
756+
757+
"specific_reservation": {
758+
Type: schema.TypeList,
759+
MaxItems: 1,
760+
Optional: true,
761+
ForceNew: true,
762+
Description: `Specifies the label selector for the reservation to use.`,
763+
764+
Elem: &schema.Resource{
765+
Schema: map[string]*schema.Schema{
766+
"key": {
767+
Type: schema.TypeString,
768+
Required: true,
769+
ForceNew: true,
770+
Description: `Corresponds to 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 the only value.`,
771+
},
772+
"values": {
773+
Type: schema.TypeList,
774+
Elem: &schema.Schema{Type: schema.TypeString},
775+
Required: true,
776+
ForceNew: true,
777+
Description: `Corresponds to the label values of a reservation resource.`,
778+
},
779+
},
780+
},
781+
},
782+
},
783+
},
784+
},
740785
},
741786
CustomizeDiff: customdiff.All(
742787
customdiff.If(
@@ -855,6 +900,11 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *Confi
855900
return nil, fmt.Errorf("Error creating guest accelerators: %s", err)
856901
}
857902

903+
reservationAffinity, err := expandReservationAffinity(d)
904+
if err != nil {
905+
return nil, fmt.Errorf("Error creating reservation affinity: %s", err)
906+
}
907+
858908
// Create the instance information
859909
return &computeBeta.Instance{
860910
CanIpForward: d.Get("can_ip_forward").(bool),
@@ -877,6 +927,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *Confi
877927
ShieldedInstanceConfig: expandShieldedVmConfigs(d),
878928
DisplayDevice: expandDisplayDevice(d),
879929
ResourcePolicies: convertStringArr(d.Get("resource_policies").([]interface{})),
930+
ReservationAffinity: reservationAffinity,
880931
}, nil
881932
}
882933

@@ -1235,6 +1286,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
12351286
return fmt.Errorf("Error setting desired_status: %s", err)
12361287
}
12371288
}
1289+
if err := d.Set("reservation_affinity", flattenReservationAffinity(instance.ReservationAffinity)); err != nil {
1290+
return fmt.Errorf("Error setting reservation_affinity: %s", err)
1291+
}
12381292

12391293
d.SetId(fmt.Sprintf("projects/%s/zones/%s/instances/%s", project, zone, instance.Name))
12401294

google-beta/resource_compute_instance_template.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,51 @@ func resourceComputeInstanceTemplate() *schema.Resource {
614614
Set: schema.HashString,
615615
Description: `A set of key/value label pairs to assign to instances created from this template,`,
616616
},
617+
618+
"reservation_affinity": {
619+
Type: schema.TypeList,
620+
MaxItems: 1,
621+
Optional: true,
622+
ForceNew: true,
623+
Description: `Specifies the reservations that this instance can consume from.`,
624+
Elem: &schema.Resource{
625+
Schema: map[string]*schema.Schema{
626+
"type": {
627+
Type: schema.TypeString,
628+
Required: true,
629+
ForceNew: true,
630+
ValidateFunc: validation.StringInSlice([]string{"ANY_RESERVATION", "SPECIFIC_RESERVATION", "NO_RESERVATION"}, false),
631+
Description: `The type of reservation from which this instance can consume resources.`,
632+
},
633+
634+
"specific_reservation": {
635+
Type: schema.TypeList,
636+
MaxItems: 1,
637+
Optional: true,
638+
ForceNew: true,
639+
Description: `Specifies the label selector for the reservation to use.`,
640+
641+
Elem: &schema.Resource{
642+
Schema: map[string]*schema.Schema{
643+
"key": {
644+
Type: schema.TypeString,
645+
Required: true,
646+
ForceNew: true,
647+
Description: `Corresponds to 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 the only value.`,
648+
},
649+
"values": {
650+
Type: schema.TypeList,
651+
Elem: &schema.Schema{Type: schema.TypeString},
652+
Required: true,
653+
ForceNew: true,
654+
Description: `Corresponds to the label values of a reservation resource.`,
655+
},
656+
},
657+
},
658+
},
659+
},
660+
},
661+
},
617662
},
618663
UseJSONNumber: true,
619664
}
@@ -871,6 +916,11 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac
871916
return err
872917
}
873918

919+
reservationAffinity, err := expandReservationAffinity(d)
920+
if err != nil {
921+
return err
922+
}
923+
874924
instanceProperties := &computeBeta.InstanceProperties{
875925
CanIpForward: d.Get("can_ip_forward").(bool),
876926
Description: d.Get("instance_description").(string),
@@ -886,6 +936,7 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac
886936
ConfidentialInstanceConfig: expandConfidentialInstanceConfig(d),
887937
ShieldedInstanceConfig: expandShieldedVmConfigs(d),
888938
DisplayDevice: expandDisplayDevice(d),
939+
ReservationAffinity: reservationAffinity,
889940
}
890941

891942
if _, ok := d.GetOk("labels"); ok {
@@ -1280,6 +1331,13 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{
12801331
return fmt.Errorf("Error setting enable_display: %s", err)
12811332
}
12821333
}
1334+
1335+
if reservationAffinity := instanceTemplate.Properties.ReservationAffinity; reservationAffinity != nil {
1336+
if err = d.Set("reservation_affinity", flattenReservationAffinity(reservationAffinity)); err != nil {
1337+
return fmt.Errorf("Error setting reservation_affinity: %s", err)
1338+
}
1339+
}
1340+
12831341
return nil
12841342
}
12851343

google-beta/resource_compute_instance_template_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,57 @@ func TestAccComputeInstanceTemplate_soleTenantNodeAffinities(t *testing.T) {
734734
})
735735
}
736736

737+
func TestAccComputeInstanceTemplate_reservationAffinities(t *testing.T) {
738+
t.Parallel()
739+
740+
var template computeBeta.InstanceTemplate
741+
var templateName = randString(t, 10)
742+
743+
vcrTest(t, resource.TestCase{
744+
PreCheck: func() { testAccPreCheck(t) },
745+
Providers: testAccProviders,
746+
CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t),
747+
Steps: []resource.TestStep{
748+
{
749+
Config: testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate_nonSpecificReservation(templateName, "NO_RESERVATION"),
750+
Check: resource.ComposeTestCheckFunc(
751+
testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template),
752+
testAccCheckComputeInstanceTemplateHasReservationAffinity(&template, "NO_RESERVATION"),
753+
),
754+
},
755+
{
756+
ResourceName: "google_compute_instance_template.foobar",
757+
ImportState: true,
758+
ImportStateVerify: true,
759+
},
760+
{
761+
Config: testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate_nonSpecificReservation(templateName, "ANY_RESERVATION"),
762+
Check: resource.ComposeTestCheckFunc(
763+
testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template),
764+
testAccCheckComputeInstanceTemplateHasReservationAffinity(&template, "ANY_RESERVATION"),
765+
),
766+
},
767+
{
768+
ResourceName: "google_compute_instance_template.foobar",
769+
ImportState: true,
770+
ImportStateVerify: true,
771+
},
772+
{
773+
Config: testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate_specificReservation(templateName),
774+
Check: resource.ComposeTestCheckFunc(
775+
testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template),
776+
testAccCheckComputeInstanceTemplateHasReservationAffinity(&template, "SPECIFIC_RESERVATION", templateName),
777+
),
778+
},
779+
{
780+
ResourceName: "google_compute_instance_template.foobar",
781+
ImportState: true,
782+
ImportStateVerify: true,
783+
},
784+
},
785+
})
786+
}
787+
737788
func TestAccComputeInstanceTemplate_shieldedVmConfig1(t *testing.T) {
738789
t.Parallel()
739790

@@ -1264,6 +1315,36 @@ func testAccCheckComputeInstanceTemplateHasMinCpuPlatform(instanceTemplate *comp
12641315
}
12651316
}
12661317

1318+
func testAccCheckComputeInstanceTemplateHasReservationAffinity(instanceTemplate *computeBeta.InstanceTemplate, consumeReservationType string, specificReservationNames ...string) resource.TestCheckFunc {
1319+
if len(specificReservationNames) > 1 {
1320+
panic("too many specificReservationNames in test")
1321+
}
1322+
1323+
return func(*terraform.State) error {
1324+
if instanceTemplate.Properties.ReservationAffinity == nil {
1325+
return fmt.Errorf("expected template to have reservation affinity, but it was nil")
1326+
}
1327+
1328+
if actualReservationType := instanceTemplate.Properties.ReservationAffinity.ConsumeReservationType; actualReservationType != consumeReservationType {
1329+
return fmt.Errorf("Wrong reservationAffinity consumeReservationType: expected %s, got, %s", consumeReservationType, actualReservationType)
1330+
}
1331+
1332+
if len(specificReservationNames) > 0 {
1333+
const reservationNameKey = "compute.googleapis.com/reservation-name"
1334+
if actualKey := instanceTemplate.Properties.ReservationAffinity.Key; actualKey != reservationNameKey {
1335+
return fmt.Errorf("Wrong reservationAffinity key: expected %s, got, %s", reservationNameKey, actualKey)
1336+
}
1337+
1338+
reservationAffinityValues := instanceTemplate.Properties.ReservationAffinity.Values
1339+
if len(reservationAffinityValues) != 1 || reservationAffinityValues[0] != specificReservationNames[0] {
1340+
return fmt.Errorf("Wrong reservationAffinity values: expected %s, got, %s", specificReservationNames, reservationAffinityValues)
1341+
}
1342+
}
1343+
1344+
return nil
1345+
}
1346+
}
1347+
12671348
func testAccCheckComputeInstanceTemplateHasShieldedVmConfig(instanceTemplate *computeBeta.InstanceTemplate, enableSecureBoot bool, enableVtpm bool, enableIntegrityMonitoring bool) resource.TestCheckFunc {
12681349

12691350
return func(s *terraform.State) error {
@@ -2139,6 +2220,69 @@ resource "google_compute_instance_template" "foobar" {
21392220
`, suffix)
21402221
}
21412222

2223+
func testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate_nonSpecificReservation(templateName, consumeReservationType string) string {
2224+
return fmt.Sprintf(`
2225+
data "google_compute_image" "my_image" {
2226+
family = "debian-9"
2227+
project = "debian-cloud"
2228+
}
2229+
2230+
resource "google_compute_instance_template" "foobar" {
2231+
name = "instancet-test-%s"
2232+
machine_type = "e2-medium"
2233+
can_ip_forward = false
2234+
2235+
disk {
2236+
source_image = data.google_compute_image.my_image.self_link
2237+
auto_delete = true
2238+
boot = true
2239+
}
2240+
2241+
network_interface {
2242+
network = "default"
2243+
}
2244+
2245+
reservation_affinity {
2246+
type = "%s"
2247+
}
2248+
}
2249+
`, templateName, consumeReservationType)
2250+
}
2251+
2252+
func testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate_specificReservation(templateName string) string {
2253+
return fmt.Sprintf(`
2254+
data "google_compute_image" "my_image" {
2255+
family = "debian-9"
2256+
project = "debian-cloud"
2257+
}
2258+
2259+
resource "google_compute_instance_template" "foobar" {
2260+
name = "instancet-test-%s"
2261+
machine_type = "e2-medium"
2262+
can_ip_forward = false
2263+
2264+
disk {
2265+
source_image = data.google_compute_image.my_image.self_link
2266+
auto_delete = true
2267+
boot = true
2268+
}
2269+
2270+
network_interface {
2271+
network = "default"
2272+
}
2273+
2274+
reservation_affinity {
2275+
type = "SPECIFIC_RESERVATION"
2276+
2277+
specific_reservation {
2278+
key = "compute.googleapis.com/reservation-name"
2279+
values = ["%s"]
2280+
}
2281+
}
2282+
}
2283+
`, templateName, templateName)
2284+
}
2285+
21422286
func testAccComputeInstanceTemplate_shieldedVmConfig(suffix string, enableSecureBoot bool, enableVtpm bool, enableIntegrityMonitoring bool) string {
21432287
return fmt.Sprintf(`
21442288
data "google_compute_image" "my_image" {

0 commit comments

Comments
 (0)