diff --git a/pkg/cloudprovider/providers/gce/BUILD b/pkg/cloudprovider/providers/gce/BUILD index 5caac64dcf1..f33f482e6e9 100644 --- a/pkg/cloudprovider/providers/gce/BUILD +++ b/pkg/cloudprovider/providers/gce/BUILD @@ -89,6 +89,8 @@ go_test( "//pkg/kubelet/apis:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/golang.org/x/oauth2/google:go_default_library", + "//vendor/google.golang.org/api/compute/v0.alpha:go_default_library", + "//vendor/google.golang.org/api/compute/v0.beta:go_default_library", "//vendor/google.golang.org/api/compute/v1:go_default_library", "//vendor/google.golang.org/api/googleapi:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", diff --git a/pkg/cloudprovider/providers/gce/gce.go b/pkg/cloudprovider/providers/gce/gce.go index a4dd5eab159..e9f78a83c53 100644 --- a/pkg/cloudprovider/providers/gce/gce.go +++ b/pkg/cloudprovider/providers/gce/gce.go @@ -80,6 +80,11 @@ const ( gceComputeAPIEndpoint = "https://www.googleapis.com/compute/v1/" ) +// gceObject is an abstraction of all GCE API object in go client +type gceObject interface { + MarshalJSON() ([]byte, error) +} + // GCECloud is an implementation of Interface, LoadBalancer and Instances for Google Compute Engine. type GCECloud struct { // ClusterID contains functionality for getting (and initializing) the ingress-uid. Call GCECloud.Initialize() diff --git a/pkg/cloudprovider/providers/gce/gce_op.go b/pkg/cloudprovider/providers/gce/gce_op.go index 631a4f41514..fc1549d448a 100644 --- a/pkg/cloudprovider/providers/gce/gce_op.go +++ b/pkg/cloudprovider/providers/gce/gce_op.go @@ -17,17 +17,20 @@ limitations under the License. package gce import ( + "encoding/json" "fmt" "time" "k8s.io/apimachinery/pkg/util/wait" "github.com/golang/glog" - compute "google.golang.org/api/compute/v1" + computealpha "google.golang.org/api/compute/v0.alpha" + computebeta "google.golang.org/api/compute/v0.beta" + computev1 "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" ) -func (gce *GCECloud) waitForOp(op *compute.Operation, getOperation func(operationName string) (*compute.Operation, error), mc *metricContext) error { +func (gce *GCECloud) waitForOp(op *computev1.Operation, getOperation func(operationName string) (*computev1.Operation, error), mc *metricContext) error { if op == nil { return mc.Observe(fmt.Errorf("operation must not be nil")) } @@ -72,11 +75,11 @@ func (gce *GCECloud) waitForOp(op *compute.Operation, getOperation func(operatio }) } -func opIsDone(op *compute.Operation) bool { +func opIsDone(op *computev1.Operation) bool { return op != nil && op.Status == "DONE" } -func getErrorFromOp(op *compute.Operation) error { +func getErrorFromOp(op *computev1.Operation) error { if op != nil && op.Error != nil && len(op.Error.Errors) > 0 { err := &googleapi.Error{ Code: int(op.HttpErrorStatusCode), @@ -89,20 +92,77 @@ func getErrorFromOp(op *compute.Operation) error { return nil } -func (gce *GCECloud) waitForGlobalOp(op *compute.Operation, mc *metricContext) error { - return gce.waitForOp(op, func(operationName string) (*compute.Operation, error) { - return gce.service.GlobalOperations.Get(gce.projectID, operationName).Do() - }, mc) +func (gce *GCECloud) waitForGlobalOp(op gceObject, mc *metricContext) error { + switch v := op.(type) { + case *computealpha.Operation: + return gce.waitForOp(convertToV1Operation(op), func(operationName string) (*computev1.Operation, error) { + op, err := gce.serviceAlpha.GlobalOperations.Get(gce.projectID, operationName).Do() + return convertToV1Operation(op), err + }, mc) + case *computebeta.Operation: + return gce.waitForOp(convertToV1Operation(op), func(operationName string) (*computev1.Operation, error) { + op, err := gce.serviceBeta.GlobalOperations.Get(gce.projectID, operationName).Do() + return convertToV1Operation(op), err + }, mc) + case *computev1.Operation: + return gce.waitForOp(op.(*computev1.Operation), func(operationName string) (*computev1.Operation, error) { + return gce.service.GlobalOperations.Get(gce.projectID, operationName).Do() + }, mc) + default: + return fmt.Errorf("unexpected type: %T", v) + } } -func (gce *GCECloud) waitForRegionOp(op *compute.Operation, region string, mc *metricContext) error { - return gce.waitForOp(op, func(operationName string) (*compute.Operation, error) { - return gce.service.RegionOperations.Get(gce.projectID, region, operationName).Do() - }, mc) +func (gce *GCECloud) waitForRegionOp(op gceObject, region string, mc *metricContext) error { + switch v := op.(type) { + case *computealpha.Operation: + return gce.waitForOp(convertToV1Operation(op), func(operationName string) (*computev1.Operation, error) { + op, err := gce.serviceAlpha.RegionOperations.Get(gce.projectID, region, operationName).Do() + return convertToV1Operation(op), err + }, mc) + case *computebeta.Operation: + return gce.waitForOp(convertToV1Operation(op), func(operationName string) (*computev1.Operation, error) { + op, err := gce.serviceBeta.RegionOperations.Get(gce.projectID, region, operationName).Do() + return convertToV1Operation(op), err + }, mc) + case *computev1.Operation: + return gce.waitForOp(op.(*computev1.Operation), func(operationName string) (*computev1.Operation, error) { + return gce.service.RegionOperations.Get(gce.projectID, region, operationName).Do() + }, mc) + default: + return fmt.Errorf("unexpected type: %T", v) + } } -func (gce *GCECloud) waitForZoneOp(op *compute.Operation, zone string, mc *metricContext) error { - return gce.waitForOp(op, func(operationName string) (*compute.Operation, error) { - return gce.service.ZoneOperations.Get(gce.projectID, zone, operationName).Do() - }, mc) +func (gce *GCECloud) waitForZoneOp(op gceObject, zone string, mc *metricContext) error { + switch v := op.(type) { + case *computealpha.Operation: + return gce.waitForOp(convertToV1Operation(op), func(operationName string) (*computev1.Operation, error) { + op, err := gce.serviceAlpha.ZoneOperations.Get(gce.projectID, zone, operationName).Do() + return convertToV1Operation(op), err + }, mc) + case *computebeta.Operation: + return gce.waitForOp(convertToV1Operation(op), func(operationName string) (*computev1.Operation, error) { + op, err := gce.serviceBeta.ZoneOperations.Get(gce.projectID, zone, operationName).Do() + return convertToV1Operation(op), err + }, mc) + case *computev1.Operation: + return gce.waitForOp(op.(*computev1.Operation), func(operationName string) (*computev1.Operation, error) { + return gce.service.ZoneOperations.Get(gce.projectID, zone, operationName).Do() + }, mc) + default: + return fmt.Errorf("unexpected type: %T", v) + } +} + +func convertToV1Operation(object gceObject) *computev1.Operation { + enc, err := object.MarshalJSON() + if err != nil { + panic(fmt.Sprintf("Failed to encode to json: %v", err)) + } + var op computev1.Operation + if err := json.Unmarshal(enc, &op); err != nil { + panic(fmt.Sprintf("Failed to convert GCE apiObject %v to v1 operation: %v", object, err)) + } + return &op } diff --git a/pkg/cloudprovider/providers/gce/gce_test.go b/pkg/cloudprovider/providers/gce/gce_test.go index a4ac14734b4..da70b1246f6 100644 --- a/pkg/cloudprovider/providers/gce/gce_test.go +++ b/pkg/cloudprovider/providers/gce/gce_test.go @@ -17,10 +17,15 @@ limitations under the License. package gce import ( + "encoding/json" "golang.org/x/oauth2/google" "reflect" "strings" "testing" + + computealpha "google.golang.org/api/compute/v0.alpha" + computebeta "google.golang.org/api/compute/v0.beta" + computev1 "google.golang.org/api/compute/v1" ) func TestExtraKeyInConfig(t *testing.T) { @@ -437,3 +442,53 @@ func TestGenerateCloudConfigs(t *testing.T) { } } } + +func TestConvertToV1Operation(t *testing.T) { + v1Op := getTestOperation() + enc, _ := v1Op.MarshalJSON() + var op interface{} + var alphaOp computealpha.Operation + var betaOp computebeta.Operation + + if err := json.Unmarshal(enc, &alphaOp); err != nil { + t.Errorf("Failed to unmarshal operation: %v", err) + } + + if err := json.Unmarshal(enc, &betaOp); err != nil { + t.Errorf("Failed to unmarshal operation: %v", err) + } + + op = convertToV1Operation(&alphaOp) + if _, ok := op.(*computev1.Operation); ok { + if !reflect.DeepEqual(op, v1Op) { + t.Errorf("Failed to maintain consistency across conversion") + } + } else { + t.Errorf("Expect output to be type v1 operation, but got %v", op) + } + + op = convertToV1Operation(&betaOp) + if _, ok := op.(*computev1.Operation); ok { + if !reflect.DeepEqual(op, v1Op) { + t.Errorf("Failed to maintain consistency across conversion") + } + } else { + t.Errorf("Expect output to be type v1 operation, but got %v", op) + } +} + +func getTestOperation() *computev1.Operation { + return &computev1.Operation{ + Name: "test", + Description: "test", + Id: uint64(12345), + Error: &computev1.OperationError{ + Errors: []*computev1.OperationErrorErrors{ + { + Code: "555", + Message: "error", + }, + }, + }, + } +}