mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 18:02:01 +00:00
455 lines
15 KiB
Go
455 lines
15 KiB
Go
/*
|
|
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"
|
|
"testing"
|
|
"time"
|
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
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"
|
|
controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers"
|
|
kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
|
"k8s.io/kubernetes/test/integration/framework"
|
|
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_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
|
|
}
|