Skip to content

Commit 9c642bc

Browse files
Replace InstanceInfo call with Instances call for regional reliability (#8971) (#6313)
* Replace InstanceInfo call with Instances call for regional reliability * Handle unavailable error when no overlap * Handle unavailable error when no overlap * Add tests * Add tests * Add tests * Add tests Signed-off-by: Modular Magician <[email protected]>
1 parent 44003a2 commit 9c642bc

File tree

3 files changed

+131
-9
lines changed

3 files changed

+131
-9
lines changed

.changelog/8971.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
bigtable: improved regional reliability when instance overlaps a downed region in the resource `google_bigtable_instance`
3+
```

google-beta/services/bigtable/resource_bigtable_instance.go

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -260,25 +260,26 @@ func resourceBigtableInstanceRead(d *schema.ResourceData, meta interface{}) erro
260260

261261
ctxWithTimeout, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutRead))
262262
defer cancel()
263-
instance, err := c.InstanceInfo(ctxWithTimeout, instanceName)
264-
if err != nil {
265-
if tpgresource.IsNotFoundGrpcError(err) {
266-
log.Printf("[WARN] Removing %s because it's gone", instanceName)
267-
d.SetId("")
268-
return nil
269-
}
263+
instancesResponse, err := c.Instances(ctxWithTimeout)
264+
instance, stop, err := getInstanceFromResponse(instancesResponse, instanceName, err, d)
265+
if stop {
270266
return err
271267
}
272268

273269
if err := d.Set("project", project); err != nil {
274270
return fmt.Errorf("Error setting project: %s", err)
275271
}
276272

277-
clusters, err := c.Clusters(ctxWithTimeout, instance.Name)
273+
clusters, err := c.Clusters(ctxWithTimeout, instanceName)
278274
if err != nil {
279275
partiallyUnavailableErr, ok := err.(bigtable.ErrPartiallyUnavailable)
280-
281276
if !ok {
277+
// Clusters() fails with 404 if instance does not exist.
278+
if tpgresource.IsNotFoundGrpcError(err) {
279+
log.Printf("[WARN] Removing %s because it's gone", instanceName)
280+
d.SetId("")
281+
return nil
282+
}
282283
return fmt.Errorf("Error retrieving instance clusters. %s", err)
283284
}
284285

@@ -432,6 +433,42 @@ func flattenBigtableCluster(c *bigtable.ClusterInfo) map[string]interface{} {
432433
return cluster
433434
}
434435

436+
func getInstanceFromResponse(instances []*bigtable.InstanceInfo, instanceName string, err error, d *schema.ResourceData) (*bigtable.InstanceInfo, bool, error) {
437+
// Fail on any error other than ParrtiallyUnavailable.
438+
isPartiallyUnavailableError := false
439+
if err != nil {
440+
_, isPartiallyUnavailableError = err.(bigtable.ErrPartiallyUnavailable)
441+
442+
if !isPartiallyUnavailableError {
443+
return nil, true, fmt.Errorf("Error retrieving instance. %s", err)
444+
}
445+
}
446+
447+
// Get instance from response.
448+
var instanceInfo *bigtable.InstanceInfo
449+
for _, instance := range instances {
450+
if instance.Name == instanceName {
451+
instanceInfo = instance
452+
}
453+
}
454+
455+
// If instance found, it either wasn't affected by the outage, or there is no outage.
456+
if instanceInfo != nil {
457+
return instanceInfo, false, nil
458+
}
459+
460+
// If instance wasn't found and error is PartiallyUnavailable,
461+
// continue to clusters call that will reveal overlap between instance regions and unavailable regions.
462+
if isPartiallyUnavailableError {
463+
return nil, false, nil
464+
}
465+
466+
// If instance wasn't found and error is not PartiallyUnavailable, instance doesn't exist.
467+
log.Printf("[WARN] Removing %s because it's gone", instanceName)
468+
d.SetId("")
469+
return nil, true, nil
470+
}
471+
435472
func getUnavailableClusterZones(clusters []interface{}, unavailableZones []string) []string {
436473
var zones []string
437474

google-beta/services/bigtable/resource_bigtable_instance_internal_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
package bigtable
44

55
import (
6+
"fmt"
67
"reflect"
8+
"strings"
79
"testing"
10+
11+
"cloud.google.com/go/bigtable"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
813
)
914

1015
func TestGetUnavailableClusterZones(t *testing.T) {
@@ -50,3 +55,80 @@ func TestGetUnavailableClusterZones(t *testing.T) {
5055
}
5156
}
5257
}
58+
59+
func TestGetInstanceFromResponse(t *testing.T) {
60+
instanceName := "test-instance"
61+
originalId := "original_value"
62+
cases := map[string]struct {
63+
instanceNames []string
64+
listInstancesError error
65+
66+
wantError string
67+
wantInstanceName string
68+
wantStop bool
69+
wantId string
70+
}{
71+
"not found": {
72+
instanceNames: []string{"wrong", "also_wrong"},
73+
listInstancesError: nil,
74+
75+
wantError: "",
76+
wantStop: true,
77+
wantInstanceName: "",
78+
wantId: "",
79+
},
80+
"found": {
81+
instanceNames: []string{"wrong", "also_wrong", instanceName},
82+
listInstancesError: nil,
83+
84+
wantError: "",
85+
wantStop: false,
86+
wantInstanceName: instanceName,
87+
wantId: originalId,
88+
},
89+
"error": {
90+
instanceNames: nil,
91+
listInstancesError: fmt.Errorf("some error"),
92+
93+
wantError: "Error retrieving instance.",
94+
wantStop: true,
95+
wantInstanceName: "",
96+
wantId: originalId,
97+
},
98+
"unavailble error": {
99+
instanceNames: []string{"wrong", "also_wrong"},
100+
listInstancesError: bigtable.ErrPartiallyUnavailable{[]string{"some", "location"}},
101+
102+
wantError: "",
103+
wantStop: false,
104+
wantInstanceName: "",
105+
wantId: originalId,
106+
}}
107+
for tn, tc := range cases {
108+
instancesResponse := []*bigtable.InstanceInfo{}
109+
for _, existingInstance := range tc.instanceNames {
110+
instancesResponse = append(instancesResponse, &bigtable.InstanceInfo{Name: existingInstance})
111+
}
112+
d := &schema.ResourceData{}
113+
d.SetId(originalId)
114+
gotInstance, gotStop, gotErr := getInstanceFromResponse(instancesResponse, instanceName, tc.listInstancesError, d)
115+
116+
if gotStop != tc.wantStop {
117+
t.Errorf("bad stop: %s, got %v, want %v", tn, gotStop, tc.wantStop)
118+
}
119+
if (gotErr != nil && tc.wantError == "") ||
120+
(gotErr == nil && tc.wantError != "") ||
121+
(gotErr != nil && !strings.Contains(gotErr.Error(), tc.wantError)) {
122+
t.Errorf("bad error: %s, got %q, want %q", tn, gotErr, tc.wantError)
123+
}
124+
if (gotInstance == nil && tc.wantInstanceName != "") ||
125+
(gotInstance != nil && tc.wantInstanceName == "") ||
126+
(gotInstance != nil && gotInstance.Name != tc.wantInstanceName) {
127+
t.Errorf("bad instance: %s, got %v, want %q", tn, gotInstance, tc.wantInstanceName)
128+
}
129+
gotId := d.Id()
130+
if gotId != tc.wantId {
131+
t.Errorf("bad ID: %s, got %v, want %q", tn, gotId, tc.wantId)
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)