Skip to content

Commit 939ba6d

Browse files
jacobstrrosbo
authored andcommitted
skip guest accelerators if count is 0. (#866)
* skip guest accelerators if count is 0. Instances in instance groups in google will fail to provision, despite requesting 0 GPUs. This came up for me when trying to provision a similar instance group in all available regions, but only asking for GPU's in those that support them by parameterizing the `count` and setting it to 0. This might be a violation of some terraform principles. For example, testing locally with this change `terraform` did not recognize that indeed my infra needed to be re-deployed (from it's pov, I assume it believes this because inputs hadn't changed). Additionally, there may be valid reasons for creating an instance template with 0 gpu's that can be tuned upwards. * Add guest accelerator skip test for instances. * do not leave empty pointers to guest accelerators. * attempt to clear guest accelerator diff * conditionally customize diff for guest accels
1 parent 2a69744 commit 939ba6d

4 files changed

+139
-16
lines changed

google/resource_compute_instance.go

+59-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99

1010
"github.com/hashicorp/errwrap"
11+
"github.com/hashicorp/terraform/helper/customdiff"
1112
"github.com/hashicorp/terraform/helper/schema"
1213
"github.com/hashicorp/terraform/helper/validation"
1314
"github.com/mitchellh/hashstructure"
@@ -496,6 +497,7 @@ func resourceComputeInstance() *schema.Resource {
496497
"guest_accelerator": &schema.Schema{
497498
Type: schema.TypeList,
498499
Optional: true,
500+
Computed: true,
499501
ForceNew: true,
500502
Elem: &schema.Resource{
501503
Schema: map[string]*schema.Schema{
@@ -556,6 +558,14 @@ func resourceComputeInstance() *schema.Resource {
556558
Deprecated: "Use timeouts block instead.",
557559
},
558560
},
561+
CustomizeDiff: customdiff.All(
562+
customdiff.If(
563+
func(d *schema.ResourceDiff, meta interface{}) bool {
564+
return d.HasChange("guest_accelerator")
565+
},
566+
suppressEmptyGuestAcceleratorDiff,
567+
),
568+
),
559569
}
560570
}
561571

@@ -1216,22 +1226,67 @@ func expandInstanceGuestAccelerators(d TerraformResourceData, config *Config) ([
12161226
return nil, nil
12171227
}
12181228
accels := configs.([]interface{})
1219-
guestAccelerators := make([]*computeBeta.AcceleratorConfig, len(accels))
1220-
for i, raw := range accels {
1229+
guestAccelerators := make([]*computeBeta.AcceleratorConfig, 0, len(accels))
1230+
for _, raw := range accels {
12211231
data := raw.(map[string]interface{})
1232+
if data["count"].(int) == 0 {
1233+
continue
1234+
}
12221235
at, err := ParseAcceleratorFieldValue(data["type"].(string), d, config)
12231236
if err != nil {
12241237
return nil, fmt.Errorf("cannot parse accelerator type: %v", err)
12251238
}
1226-
guestAccelerators[i] = &computeBeta.AcceleratorConfig{
1239+
guestAccelerators = append(guestAccelerators, &computeBeta.AcceleratorConfig{
12271240
AcceleratorCount: int64(data["count"].(int)),
12281241
AcceleratorType: at.RelativeLink(),
1229-
}
1242+
})
12301243
}
12311244

12321245
return guestAccelerators, nil
12331246
}
12341247

1248+
// suppressEmptyGuestAcceleratorDiff is used to work around perpetual diff
1249+
// issues when a count of `0` guest accelerators is desired. This may occur when
1250+
// guest_accelerator support is controlled via a module variable. E.g.:
1251+
//
1252+
// guest_accelerators {
1253+
// count = "${var.enable_gpu ? var.gpu_count : 0}"
1254+
// ...
1255+
// }
1256+
// After reconciling the desired and actual state, we would otherwise see a
1257+
// perpetual resembling:
1258+
// [] != [{"count":0, "type": "nvidia-tesla-k80"}]
1259+
func suppressEmptyGuestAcceleratorDiff(d *schema.ResourceDiff, meta interface{}) error {
1260+
oldi, newi := d.GetChange("guest_accelerator")
1261+
1262+
old, ok := oldi.([]interface{})
1263+
if !ok {
1264+
return fmt.Errorf("Expected old guest accelerator diff to be a slice")
1265+
}
1266+
1267+
new, ok := newi.([]interface{})
1268+
if !ok {
1269+
return fmt.Errorf("Expected new guest accelerator diff to be a slice")
1270+
}
1271+
1272+
if len(old) != 0 && len(new) != 1 {
1273+
return nil
1274+
}
1275+
1276+
firstAccel, ok := new[0].(map[string]interface{})
1277+
if !ok {
1278+
return fmt.Errorf("Unable to type assert guest accelerator")
1279+
}
1280+
1281+
if firstAccel["count"].(int) == 0 {
1282+
if err := d.Clear("guest_accelerator"); err != nil {
1283+
return err
1284+
}
1285+
}
1286+
1287+
return nil
1288+
}
1289+
12351290
func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
12361291
config := meta.(*Config)
12371292

google/resource_compute_instance_template.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -502,15 +502,18 @@ func expandInstanceTemplateGuestAccelerators(d TerraformResourceData, config *Co
502502
return nil
503503
}
504504
accels := configs.([]interface{})
505-
guestAccelerators := make([]*computeBeta.AcceleratorConfig, len(accels))
506-
for i, raw := range accels {
505+
guestAccelerators := make([]*computeBeta.AcceleratorConfig, 0, len(accels))
506+
for _, raw := range accels {
507507
data := raw.(map[string]interface{})
508-
guestAccelerators[i] = &computeBeta.AcceleratorConfig{
508+
if data["count"].(int) == 0 {
509+
continue
510+
}
511+
guestAccelerators = append(guestAccelerators, &computeBeta.AcceleratorConfig{
509512
AcceleratorCount: int64(data["count"].(int)),
510513
// We can't use ParseAcceleratorFieldValue here because an instance
511514
// template does not have a zone we can use.
512515
AcceleratorType: data["type"].(string),
513-
}
516+
})
514517
}
515518

516519
return guestAccelerators

google/resource_compute_instance_template_test.go

+36-4
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ func TestAccComputeInstanceTemplate_guestAccelerator(t *testing.T) {
351351
CheckDestroy: testAccCheckComputeInstanceTemplateDestroy,
352352
Steps: []resource.TestStep{
353353
resource.TestStep{
354-
Config: testAccComputeInstanceTemplate_guestAccelerator(acctest.RandString(10)),
354+
Config: testAccComputeInstanceTemplate_guestAccelerator(acctest.RandString(10), 1),
355355
Check: resource.ComposeTestCheckFunc(
356356
testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate),
357357
testAccCheckComputeInstanceTemplateHasGuestAccelerator(&instanceTemplate, "nvidia-tesla-k80", 1),
@@ -367,6 +367,28 @@ func TestAccComputeInstanceTemplate_guestAccelerator(t *testing.T) {
367367

368368
}
369369

370+
func TestAccComputeInstanceTemplate_guestAcceleratorSkip(t *testing.T) {
371+
t.Parallel()
372+
373+
var instanceTemplate compute.InstanceTemplate
374+
375+
resource.Test(t, resource.TestCase{
376+
PreCheck: func() { testAccPreCheck(t) },
377+
Providers: testAccProviders,
378+
CheckDestroy: testAccCheckComputeInstanceTemplateDestroy,
379+
Steps: []resource.TestStep{
380+
resource.TestStep{
381+
Config: testAccComputeInstanceTemplate_guestAccelerator(acctest.RandString(10), 0),
382+
Check: resource.ComposeTestCheckFunc(
383+
testAccCheckComputeInstanceTemplateExists("google_compute_instance_template.foobar", &instanceTemplate),
384+
testAccCheckComputeInstanceTemplateLacksGuestAccelerator(&instanceTemplate),
385+
),
386+
},
387+
},
388+
})
389+
390+
}
391+
370392
func TestAccComputeInstanceTemplate_minCpuPlatform(t *testing.T) {
371393
t.Parallel()
372394

@@ -664,6 +686,16 @@ func testAccCheckComputeInstanceTemplateHasGuestAccelerator(instanceTemplate *co
664686
}
665687
}
666688

689+
func testAccCheckComputeInstanceTemplateLacksGuestAccelerator(instanceTemplate *compute.InstanceTemplate) resource.TestCheckFunc {
690+
return func(s *terraform.State) error {
691+
if len(instanceTemplate.Properties.GuestAccelerators) > 0 {
692+
return fmt.Errorf("Expected no guest accelerators")
693+
}
694+
695+
return nil
696+
}
697+
}
698+
667699
func testAccCheckComputeInstanceTemplateHasMinCpuPlatform(instanceTemplate *compute.InstanceTemplate, minCpuPlatform string) resource.TestCheckFunc {
668700
return func(s *terraform.State) error {
669701
if instanceTemplate.Properties.MinCpuPlatform != minCpuPlatform {
@@ -1087,7 +1119,7 @@ resource "google_compute_instance_template" "foobar" {
10871119
}`, i, i, i)
10881120
}
10891121

1090-
func testAccComputeInstanceTemplate_guestAccelerator(i string) string {
1122+
func testAccComputeInstanceTemplate_guestAccelerator(i string, count uint8) string {
10911123
return fmt.Sprintf(`
10921124
resource "google_compute_instance_template" "foobar" {
10931125
name = "instance-test-%s"
@@ -1110,10 +1142,10 @@ resource "google_compute_instance_template" "foobar" {
11101142
}
11111143
11121144
guest_accelerator {
1113-
count = 1
1145+
count = %d
11141146
type = "nvidia-tesla-k80"
11151147
}
1116-
}`, i)
1148+
}`, i, count)
11171149
}
11181150

11191151
func testAccComputeInstanceTemplate_minCpuPlatform(i string) string {

google/resource_compute_instance_test.go

+37-4
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ func TestAccComputeInstance_guestAccelerator(t *testing.T) {
801801
CheckDestroy: testAccCheckComputeInstanceDestroy,
802802
Steps: []resource.TestStep{
803803
resource.TestStep{
804-
Config: testAccComputeInstance_guestAccelerator(instanceName),
804+
Config: testAccComputeInstance_guestAccelerator(instanceName, 1),
805805
Check: resource.ComposeTestCheckFunc(
806806
testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance),
807807
testAccCheckComputeInstanceHasGuestAccelerator(&instance, "nvidia-tesla-k80", 1),
@@ -817,6 +817,29 @@ func TestAccComputeInstance_guestAccelerator(t *testing.T) {
817817

818818
}
819819

820+
func TestAccComputeInstance_guestAcceleratorSkip(t *testing.T) {
821+
t.Parallel()
822+
823+
var instance compute.Instance
824+
instanceName := fmt.Sprintf("terraform-test-%s", acctest.RandString(10))
825+
826+
resource.Test(t, resource.TestCase{
827+
PreCheck: func() { testAccPreCheck(t) },
828+
Providers: testAccProviders,
829+
CheckDestroy: testAccCheckComputeInstanceDestroy,
830+
Steps: []resource.TestStep{
831+
resource.TestStep{
832+
Config: testAccComputeInstance_guestAccelerator(instanceName, 0),
833+
Check: resource.ComposeTestCheckFunc(
834+
testAccCheckComputeInstanceExists("google_compute_instance.foobar", &instance),
835+
testAccCheckComputeInstanceLacksGuestAccelerator(&instance),
836+
),
837+
},
838+
},
839+
})
840+
841+
}
842+
820843
func TestAccComputeInstance_minCpuPlatform(t *testing.T) {
821844
t.Parallel()
822845

@@ -1298,6 +1321,16 @@ func testAccCheckComputeInstanceHasGuestAccelerator(instance *compute.Instance,
12981321
}
12991322
}
13001323

1324+
func testAccCheckComputeInstanceLacksGuestAccelerator(instance *compute.Instance) resource.TestCheckFunc {
1325+
return func(s *terraform.State) error {
1326+
if len(instance.GuestAccelerators) > 0 {
1327+
return fmt.Errorf("Expected no guest accelerators")
1328+
}
1329+
1330+
return nil
1331+
}
1332+
}
1333+
13011334
func testAccCheckComputeInstanceHasMinCpuPlatform(instance *compute.Instance, minCpuPlatform string) resource.TestCheckFunc {
13021335
return func(s *terraform.State) error {
13031336
if instance.MinCpuPlatform != minCpuPlatform {
@@ -2282,7 +2315,7 @@ resource "google_compute_subnetwork" "inst-test-subnetwork" {
22822315
`, instance, network, subnetwork)
22832316
}
22842317

2285-
func testAccComputeInstance_guestAccelerator(instance string) string {
2318+
func testAccComputeInstance_guestAccelerator(instance string, count uint8) string {
22862319
return fmt.Sprintf(`
22872320
resource "google_compute_instance" "foobar" {
22882321
name = "%s"
@@ -2305,10 +2338,10 @@ resource "google_compute_instance" "foobar" {
23052338
}
23062339
23072340
guest_accelerator {
2308-
count = 1
2341+
count = %d
23092342
type = "nvidia-tesla-k80"
23102343
}
2311-
}`, instance)
2344+
}`, instance, count)
23122345
}
23132346

23142347
func testAccComputeInstance_minCpuPlatform(instance string) string {

0 commit comments

Comments
 (0)