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

Compute: add support for instance reservation_affinity #7669

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions google/compute_instance_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,54 @@ func schedulingHasChange(d *schema.ResourceData) bool {

return reflect.DeepEqual(newNa, originalNa)
}

func expandReservationAffinity(d *schema.ResourceData) (*computeBeta.ReservationAffinity, error) {
_, ok := d.GetOk("reservation_affinity")
if !ok {
return nil, nil
}

prefix := "reservation_affinity.0"
reservationAffinityType := d.Get(prefix + ".type").(string)

affinity := computeBeta.ReservationAffinity{
ConsumeReservationType: reservationAffinityType,
ForceSendFields: []string{"ConsumeReservationType"},
}

_, hasSpecificReservation := d.GetOk(prefix + ".specific_reservation")
if (reservationAffinityType == "SPECIFIC_RESERVATION") != hasSpecificReservation {
return nil, fmt.Errorf("specific_reservation must be set when reservation_affinity is SPECIFIC_RESERVATION, and not set otherwise")
}

prefix = prefix + ".specific_reservation.0"
if hasSpecificReservation {
affinity.Key = d.Get(prefix + ".key").(string)
affinity.ForceSendFields = append(affinity.ForceSendFields, "Key", "Values")

for _, v := range d.Get(prefix + ".values").([]interface{}) {
affinity.Values = append(affinity.Values, v.(string))
}
}

return &affinity, nil
}

func flattenReservationAffinity(affinity *computeBeta.ReservationAffinity) []map[string]interface{} {
if affinity == nil {
return nil
}

flattened := map[string]interface{}{
"type": affinity.ConsumeReservationType,
}

if affinity.ConsumeReservationType == "SPECIFIC_RESERVATION" {
flattened["specific_reservation"] = []map[string]interface{}{{
"key": affinity.Key,
"values": affinity.Values,
}}
}

return []map[string]interface{}{flattened}
}
54 changes: 54 additions & 0 deletions google/resource_compute_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,51 @@ func resourceComputeInstance() *schema.Resource {
MaxItems: 1,
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.`,
},

"reservation_affinity": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Description: `Specifies the reservations that this instance can consume from.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"ANY_RESERVATION", "SPECIFIC_RESERVATION", "NO_RESERVATION"}, false),
Description: `The type of reservation from which this instance can consume resources.`,
},

"specific_reservation": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Description: `Specifies the label selector for the reservation to use.`,

Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
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.`,
},
"values": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Required: true,
ForceNew: true,
Description: `Corresponds to the label values of a reservation resource.`,
},
},
},
},
},
},
},
},
CustomizeDiff: customdiff.All(
customdiff.If(
Expand Down Expand Up @@ -825,6 +870,11 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *Confi
return nil, fmt.Errorf("Error creating guest accelerators: %s", err)
}

reservationAffinity, err := expandReservationAffinity(d)
if err != nil {
return nil, fmt.Errorf("Error creating reservation affinity: %s", err)
}

// Create the instance information
return &computeBeta.Instance{
CanIpForward: d.Get("can_ip_forward").(bool),
Expand All @@ -845,6 +895,7 @@ func expandComputeInstance(project string, d *schema.ResourceData, config *Confi
ForceSendFields: []string{"CanIpForward", "DeletionProtection"},
ShieldedInstanceConfig: expandShieldedVmConfigs(d),
DisplayDevice: expandDisplayDevice(d),
ReservationAffinity: reservationAffinity,
ResourcePolicies: convertStringArr(d.Get("resource_policies").([]interface{})),
}, nil
}
Expand Down Expand Up @@ -1201,6 +1252,9 @@ func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error
return fmt.Errorf("Error setting desired_status: %s", err)
}
}
if err := d.Set("reservation_affinity", flattenReservationAffinity(instance.ReservationAffinity)); err != nil {
return fmt.Errorf("Error setting reservation_affinity: %s", err)
}

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

Expand Down
58 changes: 58 additions & 0 deletions google/resource_compute_instance_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,51 @@ func resourceComputeInstanceTemplate() *schema.Resource {
Set: schema.HashString,
Description: `A set of key/value label pairs to assign to instances created from this template,`,
},

"reservation_affinity": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Description: `Specifies the reservations that this instance can consume from.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"ANY_RESERVATION", "SPECIFIC_RESERVATION", "NO_RESERVATION"}, false),
Description: `The type of reservation from which this instance can consume resources.`,
},

"specific_reservation": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Description: `Specifies the label selector for the reservation to use.`,

Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
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.`,
},
"values": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Required: true,
ForceNew: true,
Description: `Corresponds to the label values of a reservation resource.`,
},
},
},
},
},
},
},
},
}
}
Expand Down Expand Up @@ -823,6 +868,11 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac
return err
}

reservationAffinity, err := expandReservationAffinity(d)
if err != nil {
return err
}

instanceProperties := &computeBeta.InstanceProperties{
CanIpForward: d.Get("can_ip_forward").(bool),
Description: d.Get("instance_description").(string),
Expand All @@ -837,6 +887,7 @@ func resourceComputeInstanceTemplateCreate(d *schema.ResourceData, meta interfac
Tags: resourceInstanceTags(d),
ShieldedInstanceConfig: expandShieldedVmConfigs(d),
DisplayDevice: expandDisplayDevice(d),
ReservationAffinity: reservationAffinity,
}

if _, ok := d.GetOk("labels"); ok {
Expand Down Expand Up @@ -1224,6 +1275,13 @@ func resourceComputeInstanceTemplateRead(d *schema.ResourceData, meta interface{
return fmt.Errorf("Error setting enable_display: %s", err)
}
}

if reservationAffinity := instanceTemplate.Properties.ReservationAffinity; reservationAffinity != nil {
if err = d.Set("reservation_affinity", flattenReservationAffinity(reservationAffinity)); err != nil {
return fmt.Errorf("Error setting reservation_affinity: %s", err)
}
}

return nil
}

Expand Down
122 changes: 122 additions & 0 deletions google/resource_compute_instance_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,57 @@ func TestAccComputeInstanceTemplate_soleTenantNodeAffinities(t *testing.T) {
})
}

func TestAccComputeInstanceTemplate_reservationAffinities(t *testing.T) {
t.Parallel()

var template computeBeta.InstanceTemplate
var templateName = randString(t, 10)

vcrTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeInstanceTemplateDestroyProducer(t),
Steps: []resource.TestStep{
{
Config: testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate(templateName, "NO_RESERVATION"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template),
testAccCheckComputeInstanceTemplateHasReservationAffinity(&template, "NO_RESERVATION"),
),
},
{
ResourceName: "google_compute_instance_template.foobar",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate(templateName, "ANY_RESERVATION"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template),
testAccCheckComputeInstanceTemplateHasReservationAffinity(&template, "ANY_RESERVATION"),
),
},
{
ResourceName: "google_compute_instance_template.foobar",
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate(templateName, "SPECIFIC_RESERVATION"),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeInstanceTemplateExists(t, "google_compute_instance_template.foobar", &template),
testAccCheckComputeInstanceTemplateHasReservationAffinity(&template, "SPECIFIC_RESERVATION", templateName),
),
},
{
ResourceName: "google_compute_instance_template.foobar",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccComputeInstanceTemplate_shieldedVmConfig1(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1187,6 +1238,36 @@ func testAccCheckComputeInstanceTemplateHasMinCpuPlatform(instanceTemplate *comp
}
}

func testAccCheckComputeInstanceTemplateHasReservationAffinity(instanceTemplate *computeBeta.InstanceTemplate, consumeReservationType string, specificReservationNames ...string) resource.TestCheckFunc {
if len(specificReservationNames) > 1 {
panic("too many specificReservationNames in test")
}

return func(*terraform.State) error {
if instanceTemplate.Properties.ReservationAffinity == nil {
return fmt.Errorf("expected template to have reservation affinity, but it was nil")
}

if actualReservationType := instanceTemplate.Properties.ReservationAffinity.ConsumeReservationType; actualReservationType != consumeReservationType {
return fmt.Errorf("Wrong reservationAffinity consumeReservationType: expected %s, got, %s", consumeReservationType, actualReservationType)
}

if len(specificReservationNames) > 0 {
const reservationNameKey = "compute.googleapis.com/reservation-name"
if actualKey := instanceTemplate.Properties.ReservationAffinity.Key; actualKey != reservationNameKey {
return fmt.Errorf("Wrong reservationAffinity key: expected %s, got, %s", reservationNameKey, actualKey)
}

reservationAffinityValues := instanceTemplate.Properties.ReservationAffinity.Values
if len(reservationAffinityValues) != 1 || reservationAffinityValues[0] != specificReservationNames[0] {
return fmt.Errorf("Wrong reservationAffinity values: expected %s, got, %s", specificReservationNames, reservationAffinityValues)
}
}

return nil
}
}

func testAccCheckComputeInstanceTemplateHasShieldedVmConfig(instanceTemplate *computeBeta.InstanceTemplate, enableSecureBoot bool, enableVtpm bool, enableIntegrityMonitoring bool) resource.TestCheckFunc {

return func(s *terraform.State) error {
Expand Down Expand Up @@ -2039,6 +2120,47 @@ resource "google_compute_instance_template" "foobar" {
`, suffix)
}

func testAccComputeInstanceTemplate_reservationAffinityInstanceTemplate(templateName, consumeReservationType string) string {
var reservationInstanceCfgSection string

if consumeReservationType == "SPECIFIC_RESERVATION" {
reservationInstanceCfgSection = fmt.Sprintf(`
specific_reservation {
key = "compute.googleapis.com/reservation-name"
values = ["%s"]
}`, templateName)
}

return fmt.Sprintf(`
data "google_compute_image" "my_image" {
family = "debian-9"
project = "debian-cloud"
}

resource "google_compute_instance_template" "foobar" {
name = "instancet-test-%s"
machine_type = "e2-medium"
can_ip_forward = false

disk {
source_image = data.google_compute_image.my_image.self_link
auto_delete = true
boot = true
}

network_interface {
network = "default"
}

reservation_affinity {
type = "%s"

%s
}
}
`, templateName, consumeReservationType, reservationInstanceCfgSection)
}

func testAccComputeInstanceTemplate_shieldedVmConfig(suffix string, enableSecureBoot bool, enableVtpm bool, enableIntegrityMonitoring bool) string {
return fmt.Sprintf(`
data "google_compute_image" "my_image" {
Expand Down
Loading