From 28decb55c366c5ec67e04800aa06179943b765f6 Mon Sep 17 00:00:00 2001 From: Ilya Gurov Date: Tue, 9 Feb 2021 00:44:33 +0300 Subject: [PATCH] feat(bigtable): support partial results in InstanceAdminClient.Clusters() (#2932) Co-authored-by: Chris Cotter Co-authored-by: Christopher Wilcox --- bigtable/admin.go | 11 +++- bigtable/integration_test.go | 117 +++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/bigtable/admin.go b/bigtable/admin.go index 34302429e33..96be9c5e677 100644 --- a/bigtable/admin.go +++ b/bigtable/admin.go @@ -1019,7 +1019,10 @@ func (iac *InstanceAdminClient) UpdateCluster(ctx context.Context, instanceID, c return longrunning.InternalNewOperation(iac.lroClient, lro).Wait(ctx, nil) } -// Clusters lists the clusters in an instance. +// Clusters lists the clusters in an instance. If any location +// (cluster) is unavailable due to some transient conditions, Clusters +// returns partial results and ErrPartiallyUnavailable error with +// unavailable locations list. func (iac *InstanceAdminClient) Clusters(ctx context.Context, instanceID string) ([]*ClusterInfo, error) { ctx = mergeOutgoingMetadata(ctx, iac.md) req := &btapb.ListClustersRequest{Parent: "projects/" + iac.project + "/instances/" + instanceID} @@ -1032,7 +1035,6 @@ func (iac *InstanceAdminClient) Clusters(ctx context.Context, instanceID string) if err != nil { return nil, err } - // TODO(garyelliott): Deal with failed_locations. var cis []*ClusterInfo for _, c := range res.Clusters { nameParts := strings.Split(c.Name, "/") @@ -1045,6 +1047,11 @@ func (iac *InstanceAdminClient) Clusters(ctx context.Context, instanceID string) StorageType: storageTypeFromProto(c.DefaultStorageType), }) } + if len(res.FailedLocations) > 0 { + // Return partial results and an error in + // case of some locations are unavailable. + return cis, ErrPartiallyUnavailable{res.FailedLocations} + } return cis, nil } diff --git a/bigtable/integration_test.go b/bigtable/integration_test.go index 4a5e811bf35..9045bdd088b 100644 --- a/bigtable/integration_test.go +++ b/bigtable/integration_test.go @@ -39,6 +39,10 @@ import ( gax "github.com/googleapis/gax-go/v2" "google.golang.org/api/iterator" btapb "google.golang.org/genproto/googleapis/bigtable/admin/v2" + v1 "google.golang.org/genproto/googleapis/iam/v1" + longrunning "google.golang.org/genproto/googleapis/longrunning" + grpc "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/emptypb" ) const ( @@ -1809,6 +1813,119 @@ func TestIntegration_AdminSnapshot(t *testing.T) { } } +// instanceAdminClientMock is used to test FailedLocations field processing. +type instanceAdminClientMock struct { + Clusters []*btapb.Cluster + UnavailableLocations []string +} + +func (iacm *instanceAdminClientMock) ListClusters(ctx context.Context, req *btapb.ListClustersRequest, opts ...grpc.CallOption) (*btapb.ListClustersResponse, error) { + res := btapb.ListClustersResponse{ + Clusters: iacm.Clusters, + FailedLocations: iacm.UnavailableLocations, + } + return &res, nil +} + +func (iacm *instanceAdminClientMock) CreateInstance(ctx context.Context, in *btapb.CreateInstanceRequest, opts ...grpc.CallOption) (*longrunning.Operation, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) GetInstance(ctx context.Context, in *btapb.GetInstanceRequest, opts ...grpc.CallOption) (*btapb.Instance, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) ListInstances(ctx context.Context, in *btapb.ListInstancesRequest, opts ...grpc.CallOption) (*btapb.ListInstancesResponse, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) UpdateInstance(ctx context.Context, in *btapb.Instance, opts ...grpc.CallOption) (*btapb.Instance, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) PartialUpdateInstance(ctx context.Context, in *btapb.PartialUpdateInstanceRequest, opts ...grpc.CallOption) (*longrunning.Operation, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) DeleteInstance(ctx context.Context, in *btapb.DeleteInstanceRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) CreateCluster(ctx context.Context, in *btapb.CreateClusterRequest, opts ...grpc.CallOption) (*longrunning.Operation, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) GetCluster(ctx context.Context, in *btapb.GetClusterRequest, opts ...grpc.CallOption) (*btapb.Cluster, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) UpdateCluster(ctx context.Context, in *btapb.Cluster, opts ...grpc.CallOption) (*longrunning.Operation, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) DeleteCluster(ctx context.Context, in *btapb.DeleteClusterRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) CreateAppProfile(ctx context.Context, in *btapb.CreateAppProfileRequest, opts ...grpc.CallOption) (*btapb.AppProfile, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) GetAppProfile(ctx context.Context, in *btapb.GetAppProfileRequest, opts ...grpc.CallOption) (*btapb.AppProfile, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) ListAppProfiles(ctx context.Context, in *btapb.ListAppProfilesRequest, opts ...grpc.CallOption) (*btapb.ListAppProfilesResponse, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) UpdateAppProfile(ctx context.Context, in *btapb.UpdateAppProfileRequest, opts ...grpc.CallOption) (*longrunning.Operation, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) DeleteAppProfile(ctx context.Context, in *btapb.DeleteAppProfileRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) GetIamPolicy(ctx context.Context, in *v1.GetIamPolicyRequest, opts ...grpc.CallOption) (*v1.Policy, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) SetIamPolicy(ctx context.Context, in *v1.SetIamPolicyRequest, opts ...grpc.CallOption) (*v1.Policy, error) { + return nil, nil +} +func (iacm *instanceAdminClientMock) TestIamPermissions(ctx context.Context, in *v1.TestIamPermissionsRequest, opts ...grpc.CallOption) (*v1.TestIamPermissionsResponse, error) { + return nil, nil +} + +func TestIntegration_InstanceAdminClient_Clusters_WithFailedLocations(t *testing.T) { + testEnv, err := NewIntegrationEnv() + if err != nil { + t.Fatalf("IntegrationEnv: %v", err) + } + defer testEnv.Close() + + if !testEnv.Config().UseProd { + t.Skip("emulator doesn't support snapshots") + } + + iAdminClient, err := testEnv.NewInstanceAdminClient() + if err != nil { + t.Fatalf("NewInstanceAdminClient: %v", err) + } + defer iAdminClient.Close() + + cluster1 := btapb.Cluster{Name: "cluster1"} + failedLoc := "euro1" + + iAdminClient.iClient = &instanceAdminClientMock{ + Clusters: []*btapb.Cluster{&cluster1}, + UnavailableLocations: []string{failedLoc}, + } + + cis, err := iAdminClient.Clusters(context.Background(), "instance-id") + convertedErr, ok := err.(ErrPartiallyUnavailable) + if !ok { + t.Fatalf("want error ErrPartiallyUnavailable, got other") + } + if got, want := len(convertedErr.Locations), 1; got != want { + t.Fatalf("want %v failed locations, got %v", want, got) + } + if got, want := convertedErr.Locations[0], failedLoc; got != want { + t.Fatalf("want failed location %v, got %v", want, got) + } + if got, want := len(cis), 1; got != want { + t.Fatalf("want %v failed locations, got %v", want, got) + } + if got, want := cis[0].Name, cluster1.Name; got != want { + t.Fatalf("want cluster %v, got %v", want, got) + } +} + func TestIntegration_Granularity(t *testing.T) { testEnv, err := NewIntegrationEnv() if err != nil {