| /* |
| Copyright 2020 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 service |
| |
| import ( |
| "context" |
| "encoding/json" |
| "reflect" |
| "testing" |
| "time" |
| |
| corev1 "k8s.io/api/core/v1" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/types" |
| "k8s.io/apimachinery/pkg/util/strategicpatch" |
| utilfeature "k8s.io/apiserver/pkg/util/feature" |
| "k8s.io/client-go/informers" |
| clientset "k8s.io/client-go/kubernetes" |
| servicecontroller "k8s.io/cloud-provider/controllers/service" |
| fakecloud "k8s.io/cloud-provider/fake" |
| featuregatetesting "k8s.io/component-base/featuregate/testing" |
| controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers" |
| kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" |
| "k8s.io/kubernetes/pkg/features" |
| "k8s.io/kubernetes/test/integration/framework" |
| "k8s.io/utils/net" |
| utilpointer "k8s.io/utils/pointer" |
| ) |
| |
| // Test_ServiceLoadBalancerAllocateNodePorts tests that a Service with spec.allocateLoadBalancerNodePorts=false |
| // does not allocate node ports for the Service. |
| func Test_ServiceLoadBalancerDisableAllocateNodePorts(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-allocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| AllocateLoadBalancerNodePorts: utilpointer.Bool(false), |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if serviceHasNodePorts(service) { |
| t.Error("found node ports when none was expected") |
| } |
| } |
| |
| // Test_ServiceUpdateLoadBalancerAllocateNodePorts tests that a Service that is updated from ClusterIP to LoadBalancer |
| // with spec.allocateLoadBalancerNodePorts=false does not allocate node ports for the Service |
| func Test_ServiceUpdateLoadBalancerDisableAllocateNodePorts(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-allocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeClusterIP, |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if serviceHasNodePorts(service) { |
| t.Error("found node ports when none was expected") |
| } |
| |
| service.Spec.Type = corev1.ServiceTypeLoadBalancer |
| service.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) |
| service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) |
| if err != nil { |
| t.Fatalf("Error updating test service: %v", err) |
| } |
| |
| if serviceHasNodePorts(service) { |
| t.Error("found node ports when none was expected") |
| } |
| } |
| |
| // Test_ServiceLoadBalancerSwitchToDeallocatedNodePorts test that switching a Service |
| // to spec.allocateLoadBalancerNodePorts=false, does not de-allocate existing node ports. |
| func Test_ServiceLoadBalancerEnableThenDisableAllocatedNodePorts(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-deallocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| AllocateLoadBalancerNodePorts: utilpointer.Bool(true), |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if !serviceHasNodePorts(service) { |
| t.Error("expected node ports but found none") |
| } |
| |
| service.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) |
| service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) |
| if err != nil { |
| t.Fatalf("Error updating test service: %v", err) |
| } |
| |
| if !serviceHasNodePorts(service) { |
| t.Error("node ports were unexpectedly deallocated") |
| } |
| } |
| |
| // Test_ServiceLoadBalancerDisableAllocatedNodePort test that switching a Service |
| // to spec.allocateLoadBalancerNodePorts=false can de-allocate existing node ports. |
| func Test_ServiceLoadBalancerDisableAllocatedNodePort(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-deallocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| AllocateLoadBalancerNodePorts: utilpointer.Bool(true), |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if !serviceHasNodePorts(service) { |
| t.Error("expected node ports but found none") |
| } |
| |
| service.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) |
| service.Spec.Ports[0].NodePort = 0 |
| service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) |
| if err != nil { |
| t.Fatalf("Error updating test service: %v", err) |
| } |
| |
| if serviceHasNodePorts(service) { |
| t.Error("node ports were expected to be deallocated") |
| } |
| } |
| |
| // Test_ServiceLoadBalancerDisableAllocatedNodePorts test that switching a Service |
| // to spec.allocateLoadBalancerNodePorts=false can de-allocate one of existing node ports. |
| func Test_ServiceLoadBalancerDisableAllocatedNodePorts(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-deallocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| AllocateLoadBalancerNodePorts: utilpointer.Bool(true), |
| Ports: []corev1.ServicePort{{ |
| Name: "np-1", |
| Port: int32(80), |
| }, { |
| Name: "np-2", |
| Port: int32(81), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if !serviceHasNodePorts(service) { |
| t.Error("expected node ports but found none") |
| } |
| |
| service.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) |
| service.Spec.Ports[0].NodePort = 0 |
| service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) |
| if err != nil { |
| t.Fatalf("Error updating test service: %v", err) |
| } |
| |
| if service.Spec.Ports[0].NodePort != 0 { |
| t.Error("node ports[0] was expected to be deallocated") |
| } |
| if service.Spec.Ports[1].NodePort == 0 { |
| t.Error("node ports was not expected to be deallocated") |
| } |
| } |
| |
| // Test_ServiceLoadBalancerDisableAllocatedNodePortsByPatch test that switching a Service |
| // to spec.allocateLoadBalancerNodePorts=false with path can de-allocate one of existing node ports. |
| func Test_ServiceLoadBalancerDisableAllocatedNodePortsByPatch(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-deallocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| AllocateLoadBalancerNodePorts: utilpointer.Bool(true), |
| Ports: []corev1.ServicePort{{ |
| Name: "np-1", |
| Port: int32(80), |
| }, { |
| Name: "np-2", |
| Port: int32(81), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if !serviceHasNodePorts(service) { |
| t.Error("expected node ports but found none") |
| } |
| |
| clone := service.DeepCopy() |
| clone.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(false) |
| clone.Spec.Ports[0].NodePort = 0 |
| |
| oldData, err := json.Marshal(service) |
| if err != nil { |
| t.Fatalf("Error marshalling test service: %v", err) |
| } |
| newData, err := json.Marshal(clone) |
| if err != nil { |
| t.Fatalf("Error marshalling test service: %v", err) |
| } |
| patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, corev1.Service{}) |
| if err != nil { |
| t.Fatalf("Error creating patch: %v", err) |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Patch(context.TODO(), service.Name, types.StrategicMergePatchType, patch, metav1.PatchOptions{}) |
| if err != nil { |
| t.Fatalf("Error updating test service: %v", err) |
| } |
| |
| if service.Spec.Ports[0].NodePort != 0 { |
| t.Error("node ports[0] was expected to be deallocated") |
| } |
| if service.Spec.Ports[1].NodePort == 0 { |
| t.Error("node ports was not expected to be deallocated") |
| } |
| } |
| |
| // Test_ServiceLoadBalancerDisableThenEnableAllocatedNodePorts test that switching a Service |
| // to spec.allocateLoadBalancerNodePorts=true from false, allocate new node ports. |
| func Test_ServiceLoadBalancerDisableThenEnableAllocatedNodePorts(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-reallocate-node-ports", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| AllocateLoadBalancerNodePorts: utilpointer.Bool(false), |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| Selector: map[string]string{ |
| "foo": "bar", |
| }, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| if serviceHasNodePorts(service) { |
| t.Error("not expected node ports but found one") |
| } |
| |
| service.Spec.AllocateLoadBalancerNodePorts = utilpointer.Bool(true) |
| service, err = client.CoreV1().Services(ns.Name).Update(context.TODO(), service, metav1.UpdateOptions{}) |
| if err != nil { |
| t.Fatalf("Error updating test service: %v", err) |
| } |
| |
| if !serviceHasNodePorts(service) { |
| t.Error("expected node ports but found none") |
| } |
| } |
| |
| func serviceHasNodePorts(svc *corev1.Service) bool { |
| for _, port := range svc.Spec.Ports { |
| if port.NodePort > 0 { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| // Test_ServiceLoadBalancerEnableLoadBalancerClass tests that when a LoadBalancer |
| // type of service has spec.LoadBalancerClass set, cloud provider should not create default load balancer. |
| func Test_ServiceLoadBalancerEnableLoadBalancerClass(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-load-balancer-class", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| controller, cloud, informer := newServiceController(t, client) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| informer.Start(ctx.Done()) |
| go controller.Run(ctx, 1, controllersmetrics.NewControllerManagerMetrics("loadbalancer-test")) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-load-balancer-class", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| LoadBalancerClass: utilpointer.String("test.com/test"), |
| }, |
| } |
| |
| _, err = client.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| time.Sleep(5 * time.Second) // sleep 5 second to wait for the service controller reconcile |
| if len(cloud.Calls) > 0 { |
| t.Errorf("Unexpected cloud provider calls: %v", cloud.Calls) |
| } |
| } |
| |
| // Test_SetLoadBalancerClassThenUpdateLoadBalancerClass tests that when a LoadBalancer |
| // type of service has spec.LoadBalancerClass set, it should be immutable as long as the service type |
| // is still LoadBalancer. |
| func Test_SetLoadBalancerClassThenUpdateLoadBalancerClass(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-immutable-load-balancer-class", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| controller, cloud, informer := newServiceController(t, client) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| informer.Start(ctx.Done()) |
| go controller.Run(ctx, 1, controllersmetrics.NewControllerManagerMetrics("loadbalancer-test")) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-load-balancer-class", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| LoadBalancerClass: utilpointer.String("test.com/test"), |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| service.Spec.LoadBalancerClass = utilpointer.String("test.com/update") |
| _, err = client.CoreV1().Services(ns.Name).Update(ctx, service, metav1.UpdateOptions{}) |
| if err == nil { |
| t.Fatal("Error: updating test service load balancer class should throw error, field is immutable") |
| } |
| |
| time.Sleep(5 * time.Second) // sleep 5 second to wait for the service controller reconcile |
| if len(cloud.Calls) > 0 { |
| t.Errorf("Unexpected cloud provider calls: %v", cloud.Calls) |
| } |
| } |
| |
| // Test_UpdateLoadBalancerWithLoadBalancerClass tests that when a Load Balancer type of Service that |
| // is updated from non loadBalancerClass set to loadBalancerClass set, it should be not allowed. |
| func Test_UpdateLoadBalancerWithLoadBalancerClass(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-update-load-balancer-class", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| controller, cloud, informer := newServiceController(t, client) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| informer.Start(ctx.Done()) |
| go controller.Run(ctx, 1, controllersmetrics.NewControllerManagerMetrics("loadbalancer-test")) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-update-load-balancer-class", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| service.Spec.LoadBalancerClass = utilpointer.String("test.com/test") |
| _, err = client.CoreV1().Services(ns.Name).Update(ctx, service, metav1.UpdateOptions{}) |
| if err == nil { |
| t.Fatal("Error: updating test service load balancer class should throw error, field is immutable") |
| } |
| |
| time.Sleep(5 * time.Second) // sleep 5 second to wait for the service controller reconcile |
| if len(cloud.Calls) == 0 { |
| t.Errorf("expected cloud provider calls to create load balancer") |
| } |
| } |
| |
| // Test_ServiceLoadBalancerMixedProtocolSetup tests that a LoadBalancer Service with different protocol values |
| // can be created. |
| func Test_ServiceLoadBalancerMixedProtocolSetup(t *testing.T) { |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-mixed-protocols", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| controller, cloud, informer := newServiceController(t, client) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| informer.Start(ctx.Done()) |
| go controller.Run(ctx, 1, controllersmetrics.NewControllerManagerMetrics("loadbalancer-test")) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-123", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| Ports: []corev1.ServicePort{ |
| { |
| Name: "tcpport", |
| Port: int32(53), |
| Protocol: corev1.ProtocolTCP, |
| }, |
| { |
| Name: "udpport", |
| Port: int32(53), |
| Protocol: corev1.ProtocolUDP, |
| }, |
| }, |
| }, |
| } |
| |
| _, err = client.CoreV1().Services(ns.Name).Create(context.TODO(), service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| time.Sleep(5 * time.Second) // sleep 5 second to wait for the service controller reconcile |
| if len(cloud.Calls) == 0 { |
| t.Errorf("expected cloud provider calls to create load balancer") |
| } |
| } |
| |
| func newServiceController(t *testing.T, client *clientset.Clientset) (*servicecontroller.Controller, *fakecloud.Cloud, informers.SharedInformerFactory) { |
| cloud := &fakecloud.Cloud{} |
| informerFactory := informers.NewSharedInformerFactory(client, 0) |
| serviceInformer := informerFactory.Core().V1().Services() |
| nodeInformer := informerFactory.Core().V1().Nodes() |
| |
| controller, err := servicecontroller.New(cloud, |
| client, |
| serviceInformer, |
| nodeInformer, |
| "test-cluster", |
| utilfeature.DefaultFeatureGate) |
| if err != nil { |
| t.Fatalf("Error creating service controller: %v", err) |
| } |
| cloud.ClearCalls() // ignore any cloud calls made in init() |
| return controller, cloud, informerFactory |
| } |
| |
| // Test_ServiceLoadBalancerIPMode tests whether the cloud provider has correctly updated the ipMode field. |
| func Test_ServiceLoadBalancerIPMode(t *testing.T) { |
| ipModeVIP := corev1.LoadBalancerIPModeVIP |
| testCases := []struct { |
| ipModeEnabled bool |
| externalIP string |
| expectedIPMode *corev1.LoadBalancerIPMode |
| }{ |
| { |
| ipModeEnabled: false, |
| externalIP: "1.2.3.4", |
| expectedIPMode: nil, |
| }, |
| { |
| ipModeEnabled: true, |
| externalIP: "1.2.3.5", |
| expectedIPMode: &ipModeVIP, |
| }, |
| } |
| |
| for _, tc := range testCases { |
| t.Run("", func(t *testing.T) { |
| defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.LoadBalancerIPMode, tc.ipModeEnabled)() |
| server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) |
| defer server.TearDownFn() |
| |
| client, err := clientset.NewForConfig(server.ClientConfig) |
| if err != nil { |
| t.Fatalf("Error creating clientset: %v", err) |
| } |
| |
| ns := framework.CreateNamespaceOrDie(client, "test-service-update-load-balancer-ip-mode", t) |
| defer framework.DeleteNamespaceOrDie(client, ns, t) |
| |
| controller, cloud, informer := newServiceController(t, client) |
| cloud.ExternalIP = net.ParseIPSloppy(tc.externalIP) |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| informer.Start(ctx.Done()) |
| go controller.Run(ctx, 1, controllersmetrics.NewControllerManagerMetrics("loadbalancer-test")) |
| |
| service := &corev1.Service{ |
| ObjectMeta: metav1.ObjectMeta{ |
| Name: "test-update-load-balancer-ip-mode", |
| }, |
| Spec: corev1.ServiceSpec{ |
| Type: corev1.ServiceTypeLoadBalancer, |
| Ports: []corev1.ServicePort{{ |
| Port: int32(80), |
| }}, |
| }, |
| } |
| |
| service, err = client.CoreV1().Services(ns.Name).Create(ctx, service, metav1.CreateOptions{}) |
| if err != nil { |
| t.Fatalf("Error creating test service: %v", err) |
| } |
| |
| time.Sleep(5 * time.Second) // sleep 5 second to wait for the service controller reconcile |
| service, err = client.CoreV1().Services(ns.Name).Get(ctx, service.Name, metav1.GetOptions{}) |
| if err != nil { |
| t.Fatalf("Error getting test service: %v", err) |
| } |
| |
| if len(service.Status.LoadBalancer.Ingress) == 0 { |
| t.Fatalf("unexpected load balancer status") |
| } |
| |
| gotIngress := service.Status.LoadBalancer.Ingress[0] |
| if gotIngress.IP != tc.externalIP || !reflect.DeepEqual(gotIngress.IPMode, tc.expectedIPMode) { |
| t.Errorf("unexpected load balancer ingress, got ingress %v, expected IP %v, expected ipMode %v", |
| gotIngress, tc.externalIP, tc.expectedIPMode) |
| } |
| }) |
| } |
| } |