diff --git a/cloud/services/compute/firewalls/reconcile_test.go b/cloud/services/compute/firewalls/reconcile_test.go new file mode 100644 index 0000000000..96c371f1e8 --- /dev/null +++ b/cloud/services/compute/firewalls/reconcile_test.go @@ -0,0 +1,324 @@ +/* +Copyright 2022 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 firewalls + +import ( + "context" + "fmt" + "google.golang.org/api/googleapi" + "net/http" + "testing" + + "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud" + "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta" + "google.golang.org/api/compute/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" + infrav1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-gcp/cloud/scope" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func init() { + _ = clusterv1.AddToScheme(scheme.Scheme) + _ = infrav1.AddToScheme(scheme.Scheme) +} + +var fakeCluster = &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: clusterv1.ClusterSpec{}, +} + +var fakeGCPCluster = &infrav1.GCPCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: infrav1.GCPClusterSpec{ + Project: "my-proj", + Region: "us-central1", + Network: infrav1.NetworkSpec{ + Name: ptr.To("my-network"), + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + Name: "workers", + CidrBlock: "10.0.0.1/28", + Region: "us-central1", + Purpose: ptr.To[string]("INTERNAL_HTTPS_LOAD_BALANCER"), + }, + }, + }, + }, + Status: infrav1.GCPClusterStatus{ + Network: infrav1.Network{ + FirewallRules: map[string]string{ + "my-cluster-apiserver": "test", + "my-cluster-apiintserver": "test", + }, + }, + }, +} + +var fakeGCPClusterSharedVPC = &infrav1.GCPCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: infrav1.GCPClusterSpec{ + Project: "my-proj", + Region: "us-central1", + Network: infrav1.NetworkSpec{ + HostProject: ptr.To("my-shared-vpc-project"), + Name: ptr.To("my-network"), + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + Name: "workers", + CidrBlock: "10.0.0.1/28", + Region: "us-central1", + Purpose: ptr.To[string]("INTERNAL_HTTPS_LOAD_BALANCER"), + }, + }, + }, + }, + Status: infrav1.GCPClusterStatus{ + Network: infrav1.Network{ + FirewallRules: map[string]string{ + "my-cluster-apiserver": "test", + "my-cluster-apiintserver": "test", + }, + }, + }, +} + +//func getSharedVPCCluster() *infrav1.GCPCluster { +// fakeGCPClusterSharedVPC := fakeGCPCluster +// fakeGCPClusterSharedVPC.Spec.Network.HostProject = ptr.To("my-shared-vpc-project") +// return fakeGCPClusterSharedVPC +//} + +type testCase struct { + name string + scope func() Scope + mockFirewalls *cloud.MockFirewalls + wantErr bool + assert func(ctx context.Context, t testCase) error +} + +func TestService_Reconcile(t *testing.T) { + fakec := fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + Build() + + clusterScope, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPCluster, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + //fakeGCPClusterSharedVPC := getSharedVPCCluster() + clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPClusterSharedVPC, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + tests := []testCase{ + { + name: "firewall rule already exist (should return existing firewall rule)", + scope: func() Scope { return clusterScope }, + mockFirewalls: &cloud.MockFirewalls{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockFirewallsObj{ + *meta.GlobalKey(fmt.Sprintf("allow-%s-healthchecks", *fakeGCPCluster.Spec.Network.Name)): {}, + }, + }, + }, + { + name: "error getting instance with non 404 error code (should return an error)", + scope: func() Scope { return clusterScope }, + mockFirewalls: &cloud.MockFirewalls{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockFirewallsObj{}, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockFirewalls) (bool, *compute.Firewall, error) { + return true, &compute.Firewall{}, &googleapi.Error{Code: http.StatusBadRequest} + }, + }, + wantErr: true, + }, + //{ + // name: "subnet does not exist (should create subnet)", + // scope: func() Scope { return clusterScope }, + // mockSubnetworks: &cloud.MockSubnetworks{ + // ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + // Objects: map[meta.Key]*cloud.MockSubnetworksObj{}, + // }, + // assert: func(ctx context.Context, t testCase) error { + // key := meta.RegionalKey(fakeGCPCluster.Spec.Network.Subnets[0].Name, fakeGCPCluster.Spec.Region) + // subnet, err := t.mockSubnetworks.Get(ctx, key) + // if err != nil { + // return err + // } + // + // if subnet.Name != fakeGCPCluster.Spec.Network.Subnets[0].Name || + // subnet.IpCidrRange != fakeGCPCluster.Spec.Network.Subnets[0].CidrBlock || + // subnet.Purpose != *fakeGCPCluster.Spec.Network.Subnets[0].Purpose { + // return errors.New("subnet was created but with wrong values") + // } + // + // return nil + // }, + //}, + { + name: "firewall rule creation fails (should return an error)", + scope: func() Scope { return clusterScope }, + mockFirewalls: &cloud.MockFirewalls{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockFirewallsObj{}, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockFirewalls) (bool, *compute.Firewall, error) { + return true, &compute.Firewall{}, &googleapi.Error{Code: http.StatusNotFound} + }, + InsertError: map[meta.Key]error{ + *meta.GlobalKey(fmt.Sprintf("allow-%s-healthchecks", *fakeGCPCluster.Spec.Network.Name)): &googleapi.Error{Code: http.StatusNotFound}, + }, + }, + wantErr: false, + }, + { + name: "firewall return no error using shared vpc", + scope: func() Scope { return clusterScopeSharedVpc }, + mockFirewalls: &cloud.MockFirewalls{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockFirewallsObj{ + *meta.GlobalKey(fmt.Sprintf("allow-%s-healthchecks", *fakeGCPCluster.Spec.Network.Name)): {}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + s := New(tt.scope()) + s.firewalls = tt.mockFirewalls + err := s.Reconcile(ctx) + if (err != nil) != tt.wantErr { + t.Errorf("Service.Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.assert != nil { + err = tt.assert(ctx, tt) + if err != nil { + t.Errorf("firewall rule was not created as expected: %v", err) + return + } + } + }) + } +} + +//func TestService_Delete(t *testing.T) { +// fakec := fake.NewClientBuilder(). +// WithScheme(scheme.Scheme). +// Build() +// +// clusterScope, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ +// Client: fakec, +// Cluster: fakeCluster, +// GCPCluster: fakeGCPCluster, +// GCPServices: scope.GCPServices{ +// Compute: &compute.Service{}, +// }, +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// fakeGCPClusterSharedVPC := getSharedVPCCluster() +// clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ +// Client: fakec, +// Cluster: fakeCluster, +// GCPCluster: fakeGCPClusterSharedVPC, +// GCPServices: scope.GCPServices{ +// Compute: &compute.Service{}, +// }, +// }) +// if err != nil { +// t.Fatal(err) +// } +// +// tests := []testCase{ +// { +// name: "subnet does not exist, should do nothing", +// scope: func() Scope { return clusterScope }, +// mockSubnetworks: &cloud.MockSubnetworks{ +// ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, +// DeleteError: map[meta.Key]error{ +// *meta.RegionalKey(fakeGCPCluster.Spec.Network.Subnets[0].Name, fakeGCPCluster.Spec.Region): &googleapi.Error{Code: http.StatusNotFound}, +// }, +// }, +// }, +// { +// name: "error deleting subnet, should return error", +// scope: func() Scope { return clusterScope }, +// mockSubnetworks: &cloud.MockSubnetworks{ +// ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, +// DeleteError: map[meta.Key]error{ +// *meta.RegionalKey(fakeGCPCluster.Spec.Network.Subnets[0].Name, fakeGCPCluster.Spec.Region): &googleapi.Error{Code: http.StatusBadRequest}, +// }, +// }, +// wantErr: true, +// }, +// { +// name: "subnet deletion with shared vpc", +// scope: func() Scope { return clusterScopeSharedVpc }, +// mockSubnetworks: &cloud.MockSubnetworks{ +// ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, +// DeleteError: map[meta.Key]error{ +// *meta.RegionalKey(fakeGCPCluster.Spec.Network.Subnets[0].Name, fakeGCPCluster.Spec.Region): &googleapi.Error{Code: http.StatusNotFound}, +// }, +// }, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// ctx := context.TODO() +// s := New(tt.scope()) +// s.subnets = tt.mockSubnetworks +// err := s.Delete(ctx) +// if (err != nil) != tt.wantErr { +// t.Errorf("Service.Delete() error = %v, wantErr %v", err, tt.wantErr) +// return +// } +// }) +// } +//} diff --git a/cloud/services/compute/networks/reconcile_test.go b/cloud/services/compute/networks/reconcile_test.go new file mode 100644 index 0000000000..aa1650dac9 --- /dev/null +++ b/cloud/services/compute/networks/reconcile_test.go @@ -0,0 +1,413 @@ +/* +Copyright 2022 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 networks + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud" + "github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/meta" + "google.golang.org/api/compute/v1" + "google.golang.org/api/googleapi" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" + infrav1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" + "sigs.k8s.io/cluster-api-provider-gcp/cloud/scope" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func init() { + _ = clusterv1.AddToScheme(scheme.Scheme) + _ = infrav1.AddToScheme(scheme.Scheme) +} + +var fakeCluster = &clusterv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: clusterv1.ClusterSpec{}, +} + +var fakeGCPCluster = &infrav1.GCPCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: infrav1.GCPClusterSpec{ + Project: "my-proj", + Region: "us-central1", + Network: infrav1.NetworkSpec{ + Name: ptr.To("my-network"), + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + Name: "workers", + CidrBlock: "10.0.0.1/28", + Region: "us-central1", + Purpose: ptr.To[string]("INTERNAL_HTTPS_LOAD_BALANCER"), + }, + }, + }, + }, +} + +var fakeGCPClusterSharedVPC = &infrav1.GCPCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: infrav1.GCPClusterSpec{ + Project: "my-proj", + Region: "us-central1", + Network: infrav1.NetworkSpec{ + HostProject: ptr.To("my-shared-vpc-project"), + Name: ptr.To("my-network"), + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + Name: "workers", + CidrBlock: "10.0.0.1/28", + Region: "us-central1", + Purpose: ptr.To[string]("INTERNAL_HTTPS_LOAD_BALANCER"), + }, + }, + }, + }, +} + +type testCase struct { + name string + scope func() Scope + mockNetwork *cloud.MockNetworks + mockRouter *cloud.MockRouters + wantErr bool + assert func(ctx context.Context, t testCase) error +} + +func TestService_createOrGetNetwork(t *testing.T) { + fakec := fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + Build() + + clusterScope, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPCluster, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPClusterSharedVPC, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + tests := []testCase{ + { + name: "network already exist (should return existing network)", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): {}, + }, + }, + }, + { + name: "error getting network instance with non 404 error code (should return an error)", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): {}, + }, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockNetworks) (bool, *compute.Network, error) { + return true, &compute.Network{}, &googleapi.Error{Code: http.StatusBadRequest} + }, + }, + wantErr: true, + }, + { + name: "network list error find issue shared vpc", + scope: func() Scope { return clusterScopeSharedVpc }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): {}, + }, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockNetworks) (bool, *compute.Network, error) { + return true, &compute.Network{}, &googleapi.Error{Code: http.StatusNotFound} + }, + }, + wantErr: true, + }, + { + name: "network creation fails (should return an error)", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{}, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockNetworks) (bool, *compute.Network, error) { + return true, &compute.Network{}, &googleapi.Error{Code: http.StatusNotFound} + }, + InsertError: map[meta.Key]error{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): &googleapi.Error{Code: http.StatusBadRequest}, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + s := New(tt.scope()) + s.networks = tt.mockNetwork + _, err := s.createOrGetNetwork(ctx) + if (err != nil) != tt.wantErr { + t.Errorf("Service.createOrGetNetwork error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.assert != nil { + err = tt.assert(ctx, tt) + if err != nil { + t.Errorf("network was not created as expected: %v", err) + return + } + } + }) + } +} + +func TestService_createOrGetRouter(t *testing.T) { + fakec := fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + Build() + + clusterScope, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPCluster, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPClusterSharedVPC, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + tests := []testCase{ + { + name: "error getting router instance with non 404 error code (should return an error)", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): {}, + }, + }, + mockRouter: &cloud.MockRouters{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockRoutersObj{ + *meta.RegionalKey(fmt.Sprintf("%s-%s", *fakeGCPCluster.Spec.Network.Name, "router"), fakeGCPCluster.Spec.Region): {}, + }, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockRouters) (bool, *compute.Router, error) { + return true, &compute.Router{}, &googleapi.Error{Code: http.StatusBadRequest} + }, + }, + wantErr: true, + }, + { + name: "router list error find issue shared vpc", + scope: func() Scope { return clusterScopeSharedVpc }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): {}, + }, + }, + mockRouter: &cloud.MockRouters{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockRoutersObj{ + *meta.RegionalKey(fmt.Sprintf("%s-%s", *fakeGCPCluster.Spec.Network.Name, "router"), fakeGCPCluster.Spec.Region): {}, + }, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockRouters) (bool, *compute.Router, error) { + return true, &compute.Router{}, &googleapi.Error{Code: http.StatusBadRequest} + }, + }, + wantErr: true, + }, + { + name: "router creation fails (should return an error)", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockNetworksObj{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): {}, + }, + }, + mockRouter: &cloud.MockRouters{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockRoutersObj{}, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockRouters) (bool, *compute.Router, error) { + return true, &compute.Router{}, &googleapi.Error{Code: http.StatusNotFound} + }, + InsertError: map[meta.Key]error{ + *meta.RegionalKey(fmt.Sprintf("%s-%s", *fakeGCPCluster.Spec.Network.Name, "router"), fakeGCPCluster.Spec.Region): &googleapi.Error{Code: http.StatusBadRequest}, + }, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + s := New(tt.scope()) + s.networks = tt.mockNetwork + s.routers = tt.mockRouter + + network, err := s.createOrGetNetwork(ctx) + if err != nil { + t.Errorf("Service.createOrGetNetwork error = %v", err) + return + } + + _, err = s.createOrGetRouter(ctx, network) + if (err != nil) != tt.wantErr { + t.Errorf("Service.createOrGetRouter error = %v, wantErr %v", err, tt.wantErr) + return + } + if tt.assert != nil { + err = tt.assert(ctx, tt) + if err != nil { + t.Errorf("router was not created as expected: %v", err) + return + } + } + }) + } +} + +func TestService_Delete(t *testing.T) { + fakec := fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + Build() + + clusterScope, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPCluster, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPClusterSharedVPC, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + + tests := []testCase{ + { + name: "network does not exist, should do nothing", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + GetError: map[meta.Key]error{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): &googleapi.Error{Code: http.StatusNotFound}, + }, + }, + }, + { + name: "error deleting network, should return error", + scope: func() Scope { return clusterScope }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + GetError: map[meta.Key]error{ + *meta.GlobalKey(*fakeGCPCluster.Spec.Network.Name): &googleapi.Error{Code: http.StatusBadGateway}, + }, + }, + wantErr: true, + }, + { + name: "network shared vpc, should do nothing", + scope: func() Scope { return clusterScopeSharedVpc }, + mockNetwork: &cloud.MockNetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + }, + }, + //{ + // name: "subnet deletion with shared vpc", + // scope: func() Scope { return clusterScopeSharedVpc }, + // mockSubnetworks: &cloud.MockSubnetworks{ + // ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + // DeleteError: map[meta.Key]error{ + // *meta.RegionalKey(fakeGCPCluster.Spec.Network.Subnets[0].Name, fakeGCPCluster.Spec.Region): &googleapi.Error{Code: http.StatusNotFound}, + // }, + // }, + //}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.TODO() + s := New(tt.scope()) + s.networks = tt.mockNetwork + err := s.Delete(ctx) + if (err != nil) != tt.wantErr { + t.Errorf("Service.Delete() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/cloud/services/compute/subnets/reconcile_test.go b/cloud/services/compute/subnets/reconcile_test.go index 9a83f323a9..81486b8d4f 100644 --- a/cloud/services/compute/subnets/reconcile_test.go +++ b/cloud/services/compute/subnets/reconcile_test.go @@ -69,6 +69,28 @@ var fakeGCPCluster = &infrav1.GCPCluster{ }, } +var fakeGCPClusterSharedVPC = &infrav1.GCPCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "default", + }, + Spec: infrav1.GCPClusterSpec{ + Project: "my-proj", + Region: "us-central1", + Network: infrav1.NetworkSpec{ + HostProject: ptr.To("my-shared-vpc-project"), + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + Name: "workers", + CidrBlock: "10.0.0.1/28", + Region: "us-central1", + Purpose: ptr.To[string]("INTERNAL_HTTPS_LOAD_BALANCER"), + }, + }, + }, + }, +} + type testCase struct { name string scope func() Scope @@ -94,6 +116,18 @@ func TestService_Reconcile(t *testing.T) { t.Fatal(err) } + clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPClusterSharedVPC, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } + tests := []testCase{ { name: "subnet already exist (should return existing subnet)", @@ -152,6 +186,18 @@ func TestService_Reconcile(t *testing.T) { }, wantErr: true, }, + { + name: "subnet list error find issue shared vpc", + scope: func() Scope { return clusterScopeSharedVpc }, + mockSubnetworks: &cloud.MockSubnetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + Objects: map[meta.Key]*cloud.MockSubnetworksObj{}, + GetHook: func(ctx context.Context, key *meta.Key, m *cloud.MockSubnetworks) (bool, *compute.Subnetwork, error) { + return true, &compute.Subnetwork{}, &googleapi.Error{Code: http.StatusBadRequest} + }, + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -190,6 +236,18 @@ func TestService_Delete(t *testing.T) { if err != nil { t.Fatal(err) } + + clusterScopeSharedVpc, err := scope.NewClusterScope(context.TODO(), scope.ClusterScopeParams{ + Client: fakec, + Cluster: fakeCluster, + GCPCluster: fakeGCPClusterSharedVPC, + GCPServices: scope.GCPServices{ + Compute: &compute.Service{}, + }, + }) + if err != nil { + t.Fatal(err) + } tests := []testCase{ { @@ -213,6 +271,16 @@ func TestService_Delete(t *testing.T) { }, wantErr: true, }, + { + name: "subnet deletion with shared vpc", + scope: func() Scope { return clusterScopeSharedVpc }, + mockSubnetworks: &cloud.MockSubnetworks{ + ProjectRouter: &cloud.SingleProjectRouter{ID: "my-proj"}, + DeleteError: map[meta.Key]error{ + *meta.RegionalKey(fakeGCPCluster.Spec.Network.Subnets[0].Name, fakeGCPCluster.Spec.Region): &googleapi.Error{Code: http.StatusNotFound}, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {