diff --git a/pkg/cloudprovider/providers/gce/BUILD b/pkg/cloudprovider/providers/gce/BUILD index 94f70388b2e..70bd0988cf0 100644 --- a/pkg/cloudprovider/providers/gce/BUILD +++ b/pkg/cloudprovider/providers/gce/BUILD @@ -20,6 +20,7 @@ go_library( "gce_clusterid.go", "gce_clusters.go", "gce_disks.go", + "gce_fake.go", "gce_firewall.go", "gce_forwardingrule.go", "gce_healthchecks.go", @@ -50,6 +51,7 @@ go_library( "//pkg/cloudprovider/providers/gce/cloud:go_default_library", "//pkg/cloudprovider/providers/gce/cloud/filter:go_default_library", "//pkg/cloudprovider/providers/gce/cloud/meta:go_default_library", + "//pkg/cloudprovider/providers/gce/cloud/mock:go_default_library", "//pkg/controller:go_default_library", "//pkg/features:go_default_library", "//pkg/kubelet/apis:go_default_library", @@ -120,7 +122,6 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//staging/src/k8s.io/client-go/tools/cache:go_default_library", "//staging/src/k8s.io/client-go/tools/record:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", diff --git a/pkg/cloudprovider/providers/gce/cloud/mock/mock.go b/pkg/cloudprovider/providers/gce/cloud/mock/mock.go index 6a28c9f8e4b..a2a6ade5741 100644 --- a/pkg/cloudprovider/providers/gce/cloud/mock/mock.go +++ b/pkg/cloudprovider/providers/gce/cloud/mock/mock.go @@ -44,6 +44,8 @@ var ( InUseError = &googleapi.Error{Code: http.StatusBadRequest, Message: "It's being used by god."} // InternalServerError is shared variable with error code StatusInternalServerError for error verification. InternalServerError = &googleapi.Error{Code: http.StatusInternalServerError} + // UnauthorizedErr wraps a Google API error with code StatusForbidden. + UnauthorizedErr = &googleapi.Error{Code: http.StatusForbidden} ) // gceObject is an abstraction of all GCE API object in go client @@ -436,6 +438,63 @@ func UpdateRegionBackendServiceHook(ctx context.Context, key *meta.Key, obj *ga. return nil } +// UpdateBackendServiceHook defines the hook for updating a BackendService. +// It replaces the object with the same key in the mock with the updated object. +func UpdateBackendServiceHook(ctx context.Context, key *meta.Key, obj *ga.BackendService, m *cloud.MockBackendServices) error { + _, err := m.Get(ctx, key) + if err != nil { + return &googleapi.Error{ + Code: http.StatusNotFound, + Message: fmt.Sprintf("Key: %s was not found in BackendServices", key.String()), + } + } + + obj.Name = key.Name + projectID := m.ProjectRouter.ProjectID(ctx, "ga", "backendServices") + obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "backendServices", key) + + m.Objects[*key] = &cloud.MockBackendServicesObj{Obj: obj} + return nil +} + +// UpdateAlphaBackendServiceHook defines the hook for updating an alpha BackendService. +// It replaces the object with the same key in the mock with the updated object. +func UpdateAlphaBackendServiceHook(ctx context.Context, key *meta.Key, obj *alpha.BackendService, m *cloud.MockAlphaBackendServices) error { + _, err := m.Get(ctx, key) + if err != nil { + return &googleapi.Error{ + Code: http.StatusNotFound, + Message: fmt.Sprintf("Key: %s was not found in BackendServices", key.String()), + } + } + + obj.Name = key.Name + projectID := m.ProjectRouter.ProjectID(ctx, "alpha", "backendServices") + obj.SelfLink = cloud.SelfLink(meta.VersionAlpha, projectID, "backendServices", key) + + m.Objects[*key] = &cloud.MockBackendServicesObj{Obj: obj} + return nil +} + +// UpdateURLMapHook defines the hook for updating a UrlMap. +// It replaces the object with the same key in the mock with the updated object. +func UpdateURLMapHook(ctx context.Context, key *meta.Key, obj *ga.UrlMap, m *cloud.MockUrlMaps) error { + _, err := m.Get(ctx, key) + if err != nil { + return &googleapi.Error{ + Code: http.StatusNotFound, + Message: fmt.Sprintf("Key: %s was not found in UrlMaps", key.String()), + } + } + + obj.Name = key.Name + projectID := m.ProjectRouter.ProjectID(ctx, "ga", "urlMaps") + obj.SelfLink = cloud.SelfLink(meta.VersionGA, projectID, "urlMaps", key) + + m.Objects[*key] = &cloud.MockUrlMapsObj{Obj: obj} + return nil +} + // InsertFirewallsUnauthorizedErrHook mocks firewall insertion. A forbidden error will be thrown as return. func InsertFirewallsUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *ga.Firewall, m *cloud.MockFirewalls) (bool, error) { return true, &googleapi.Error{Code: http.StatusForbidden} @@ -496,6 +555,16 @@ func DeleteAddressesInternalErrHook(ctx context.Context, key *meta.Key, m *cloud return true, InternalServerError } +// InsertAlphaBackendServiceUnauthorizedErrHook mocks inserting an alpha BackendService and returns a forbidden error. +func InsertAlphaBackendServiceUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *alpha.BackendService, m *cloud.MockAlphaBackendServices) (bool, error) { + return true, UnauthorizedErr +} + +// UpdateAlphaBackendServiceUnauthorizedErrHook mocks updating an alpha BackendService and returns a forbidden error. +func UpdateAlphaBackendServiceUnauthorizedErrHook(ctx context.Context, key *meta.Key, obj *alpha.BackendService, m *cloud.MockAlphaBackendServices) error { + return UnauthorizedErr +} + // GetRegionBackendServicesErrHook mocks getting region backend service and returns an internal server error. func GetRegionBackendServicesErrHook(ctx context.Context, key *meta.Key, m *cloud.MockRegionBackendServices) (bool, *ga.BackendService, error) { return true, nil, InternalServerError diff --git a/pkg/cloudprovider/providers/gce/gce_fake.go b/pkg/cloudprovider/providers/gce/gce_fake.go new file mode 100644 index 00000000000..f51bc20719f --- /dev/null +++ b/pkg/cloudprovider/providers/gce/gce_fake.go @@ -0,0 +1,83 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gce + +import ( + "fmt" + "net/http" + + compute "google.golang.org/api/compute/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud" +) + +type TestClusterValues struct { + ProjectID string + Region string + ZoneName string + SecondaryZoneName string + ClusterID string + ClusterName string +} + +func DefaultTestClusterValues() TestClusterValues { + return TestClusterValues{ + ProjectID: "test-project", + Region: "us-central1", + ZoneName: "us-central1-b", + SecondaryZoneName: "us-central1-c", + ClusterID: "test-cluster-id", + ClusterName: "Test Cluster Name", + } +} + +func FakeGCECloud(vals TestClusterValues) *GCECloud { + return simpleFakeGCECloud(vals) +} + +type fakeRoundTripper struct{} + +func (*fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { + return nil, fmt.Errorf("err: test used fake http client") +} + +// Stubs ClusterID so that ClusterID.getOrInitialize() does not require calling +// gce.Initialize() +func fakeClusterID(clusterID string) ClusterID { + return ClusterID{ + clusterID: &clusterID, + store: cache.NewStore(func(obj interface{}) (string, error) { + return "", nil + }), + } +} + +func simpleFakeGCECloud(vals TestClusterValues) *GCECloud { + client := &http.Client{Transport: &fakeRoundTripper{}} + service, _ := compute.New(client) + gce := &GCECloud{ + region: vals.Region, + service: service, + managedZones: []string{vals.ZoneName}, + projectID: vals.ProjectID, + networkProjectID: vals.ProjectID, + ClusterID: fakeClusterID(vals.ClusterID), + } + c := cloud.NewMockGCE(&gceProjectRouter{gce}) + gce.c = c + return gce +} diff --git a/pkg/cloudprovider/providers/gce/gce_loadbalancer_utils_test.go b/pkg/cloudprovider/providers/gce/gce_loadbalancer_utils_test.go index c21cb2e79f2..bdea0d5eb03 100644 --- a/pkg/cloudprovider/providers/gce/gce_loadbalancer_utils_test.go +++ b/pkg/cloudprovider/providers/gce/gce_loadbalancer_utils_test.go @@ -23,9 +23,7 @@ package gce import ( "context" "fmt" - "net/http" "strings" - "sync" "testing" "time" @@ -35,12 +33,9 @@ import ( "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" v1_service "k8s.io/kubernetes/pkg/api/v1/service" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta" - "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/mock" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" ) @@ -54,26 +49,6 @@ const ( errStrUnsupportedTier = "unsupported network tier: \"" + wrongTier + "\"" ) -type TestClusterValues struct { - ProjectID string - Region string - ZoneName string - SecondaryZoneName string - ClusterID string - ClusterName string -} - -func DefaultTestClusterValues() TestClusterValues { - return TestClusterValues{ - ProjectID: "test-project", - Region: "us-central1", - ZoneName: "us-central1-b", - SecondaryZoneName: "us-central1-c", - ClusterID: "test-cluster-id", - ClusterName: "Test Cluster Name", - } -} - func fakeLoadbalancerService(lbType string) *v1.Service { return &v1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -92,73 +67,6 @@ var ( FilewallChangeMsg = fmt.Sprintf("%s %s %s", v1.EventTypeNormal, eventReasonManualChange, eventMsgFirewallChange) ) -type fakeRoundTripper struct{} - -func (*fakeRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { - return nil, fmt.Errorf("err: test used fake http client") -} - -func fakeGCECloud(vals TestClusterValues) (*GCECloud, error) { - client := &http.Client{Transport: &fakeRoundTripper{}} - - service, err := compute.New(client) - if err != nil { - return nil, err - } - - // Used in disk unit tests - fakeManager := newFakeManager(vals.ProjectID, vals.Region) - zonesWithNodes := createNodeZones([]string{vals.ZoneName}) - - alphaFeatureGate := NewAlphaFeatureGate([]string{}) - if err != nil { - return nil, err - } - - gce := &GCECloud{ - region: vals.Region, - service: service, - manager: fakeManager, - managedZones: []string{vals.ZoneName}, - projectID: vals.ProjectID, - networkProjectID: vals.ProjectID, - AlphaFeatureGate: alphaFeatureGate, - nodeZones: zonesWithNodes, - nodeInformerSynced: func() bool { return true }, - ClusterID: fakeClusterID(vals.ClusterID), - } - - c := cloud.NewMockGCE(&gceProjectRouter{gce}) - c.MockTargetPools.AddInstanceHook = mock.AddInstanceHook - c.MockTargetPools.RemoveInstanceHook = mock.RemoveInstanceHook - c.MockForwardingRules.InsertHook = mock.InsertFwdRuleHook - c.MockAddresses.InsertHook = mock.InsertAddressHook - c.MockAlphaAddresses.InsertHook = mock.InsertAlphaAddressHook - c.MockAlphaAddresses.X = mock.AddressAttributes{} - c.MockAddresses.X = mock.AddressAttributes{} - - c.MockInstanceGroups.X = mock.InstanceGroupAttributes{ - InstanceMap: make(map[meta.Key]map[string]*compute.InstanceWithNamedPorts), - Lock: &sync.Mutex{}, - } - c.MockInstanceGroups.AddInstancesHook = mock.AddInstancesHook - c.MockInstanceGroups.RemoveInstancesHook = mock.RemoveInstancesHook - c.MockInstanceGroups.ListInstancesHook = mock.ListInstancesHook - - c.MockRegionBackendServices.UpdateHook = mock.UpdateRegionBackendServiceHook - c.MockHealthChecks.UpdateHook = mock.UpdateHealthCheckHook - c.MockFirewalls.UpdateHook = mock.UpdateFirewallHook - - keyGA := meta.GlobalKey("key-ga") - c.MockZones.Objects[*keyGA] = &cloud.MockZonesObj{ - Obj: &compute.Zone{Name: vals.ZoneName, Region: gce.getRegionLink(vals.Region)}, - } - - gce.c = c - - return gce, nil -} - func createAndInsertNodes(gce *GCECloud, nodeNames []string, zoneName string) ([]*v1.Node, error) { nodes := []*v1.Node{} @@ -208,17 +116,6 @@ func createAndInsertNodes(gce *GCECloud, nodeNames []string, zoneName string) ([ return nodes, nil } -// Stubs ClusterID so that ClusterID.getOrInitialize() does not require calling -// gce.Initialize() -func fakeClusterID(clusterID string) ClusterID { - return ClusterID{ - clusterID: &clusterID, - store: cache.NewStore(func(obj interface{}) (string, error) { - return "", nil - }), - } -} - func assertExternalLbResources(t *testing.T, gce *GCECloud, apiService *v1.Service, vals TestClusterValues, nodeNames []string) { lbName := gce.GetLoadBalancerName(context.TODO(), "", apiService) hcName := MakeNodesHealthCheckName(vals.ClusterID) diff --git a/pkg/cloudprovider/providers/gce/gce_util.go b/pkg/cloudprovider/providers/gce/gce_util.go index 363b5173abe..18ece122ae9 100644 --- a/pkg/cloudprovider/providers/gce/gce_util.go +++ b/pkg/cloudprovider/providers/gce/gce_util.go @@ -24,17 +24,55 @@ import ( "regexp" "sort" "strings" + "sync" "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud" + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta" + "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/mock" "cloud.google.com/go/compute/metadata" compute "google.golang.org/api/compute/v1" "google.golang.org/api/googleapi" ) +func fakeGCECloud(vals TestClusterValues) (*GCECloud, error) { + gce := simpleFakeGCECloud(vals) + + gce.AlphaFeatureGate = NewAlphaFeatureGate([]string{}) + gce.nodeInformerSynced = func() bool { return true } + + mockGCE := gce.c.(*cloud.MockGCE) + mockGCE.MockTargetPools.AddInstanceHook = mock.AddInstanceHook + mockGCE.MockTargetPools.RemoveInstanceHook = mock.RemoveInstanceHook + mockGCE.MockForwardingRules.InsertHook = mock.InsertFwdRuleHook + mockGCE.MockAddresses.InsertHook = mock.InsertAddressHook + mockGCE.MockAlphaAddresses.InsertHook = mock.InsertAlphaAddressHook + mockGCE.MockAlphaAddresses.X = mock.AddressAttributes{} + mockGCE.MockAddresses.X = mock.AddressAttributes{} + + mockGCE.MockInstanceGroups.X = mock.InstanceGroupAttributes{ + InstanceMap: make(map[meta.Key]map[string]*compute.InstanceWithNamedPorts), + Lock: &sync.Mutex{}, + } + mockGCE.MockInstanceGroups.AddInstancesHook = mock.AddInstancesHook + mockGCE.MockInstanceGroups.RemoveInstancesHook = mock.RemoveInstancesHook + mockGCE.MockInstanceGroups.ListInstancesHook = mock.ListInstancesHook + + mockGCE.MockRegionBackendServices.UpdateHook = mock.UpdateRegionBackendServiceHook + mockGCE.MockHealthChecks.UpdateHook = mock.UpdateHealthCheckHook + mockGCE.MockFirewalls.UpdateHook = mock.UpdateFirewallHook + + keyGA := meta.GlobalKey("key-ga") + mockGCE.MockZones.Objects[*keyGA] = &cloud.MockZonesObj{ + Obj: &compute.Zone{Name: vals.ZoneName, Region: gce.getRegionLink(vals.Region)}, + } + + return gce, nil +} + type gceInstance struct { Zone string Name string