Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support loadBalancerClass in service reconciliation #7706

Merged
merged 4 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pkg/controller/common/service_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func needsRecreate(expected, reconciled *corev1.Service) bool {
return true
}

// LoadBalancerClass is immutable once set if target type is load balancer
if expected.Spec.Type == corev1.ServiceTypeLoadBalancer && !reflect.DeepEqual(expected.Spec.LoadBalancerClass, reconciled.Spec.LoadBalancerClass) {
return true
}

return false
}

Expand Down Expand Up @@ -139,6 +144,19 @@ func applyServerSideValues(expected, reconciled *corev1.Service) {
if expected.Spec.InternalTrafficPolicy == nil {
expected.Spec.InternalTrafficPolicy = reconciled.Spec.InternalTrafficPolicy
}

if expected.Spec.ExternalTrafficPolicy == "" {
expected.Spec.ExternalTrafficPolicy = reconciled.Spec.ExternalTrafficPolicy
}

// LoadBalancerClass may be defaulted by the API server starting K8s v.1.24
if expected.Spec.Type == corev1.ServiceTypeLoadBalancer && expected.Spec.LoadBalancerClass == nil {
expected.Spec.LoadBalancerClass = reconciled.Spec.LoadBalancerClass
}

if expected.Spec.AllocateLoadBalancerNodePorts == nil {
expected.Spec.AllocateLoadBalancerNodePorts = reconciled.Spec.AllocateLoadBalancerNodePorts
}
}

// hasNodePort returns for a given service type, if the service ports have a NodePort or not.
Expand Down
115 changes: 106 additions & 9 deletions pkg/controller/common/service_control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/ptr"

kbv1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/kibana/v1"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/comparison"
Expand Down Expand Up @@ -154,7 +155,7 @@ func Test_needsUpdate(t *testing.T) {
}
}

func Test_needsDelete(t *testing.T) {
func Test_needsRecreate(t *testing.T) {
type args struct {
expected corev1.Service
reconciled corev1.Service
Expand Down Expand Up @@ -246,6 +247,48 @@ func Test_needsDelete(t *testing.T) {
},
want: false,
},
{
pebrc marked this conversation as resolved.
Show resolved Hide resolved
name: "No need to recreate if LoadBalancerClass has not changed",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("my-customer/lb"),
}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("my-customer/lb"),
}},
},
want: false,
},
{
name: "Needs recreate if LoadBalancerClass is changed",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("my-customer/lb"),
}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("something/else"),
}},
},
want: true,
},
{
name: "Removing the load balancer class is OK if target type is no longer LoadBalancer",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
LoadBalancerClass: nil,
}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("something/else"),
}},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -257,7 +300,7 @@ func Test_needsDelete(t *testing.T) {
}

func Test_applyServerSideValues(t *testing.T) {
ptr := func(policyType corev1.ServiceInternalTrafficPolicyType) *corev1.ServiceInternalTrafficPolicyType {
pointer := func(policyType corev1.ServiceInternalTrafficPolicyType) *corev1.ServiceInternalTrafficPolicyType {
return &policyType
}
type args struct {
Expand Down Expand Up @@ -521,29 +564,83 @@ func Test_applyServerSideValues(t *testing.T) {
}},
},
{
name: "Reconciled InternalTrafficPolicy is used if the expected one is empty",
name: "Reconciled InternalTrafficPolicy/ExternalTrafficPolicy/AllocateLoadBalancerPorts are used if the expected one is empty",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: ptr(corev1.ServiceInternalTrafficPolicyCluster),
InternalTrafficPolicy: pointer(corev1.ServiceInternalTrafficPolicyCluster),
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyCluster,
AllocateLoadBalancerNodePorts: ptr.To(true),
}},
},
want: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: pointer(corev1.ServiceInternalTrafficPolicyCluster),
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyCluster,
AllocateLoadBalancerNodePorts: ptr.To(true),
}},
},
{
name: "Expected InternalTrafficPolicy/ExternalTrafficPolicy/AllocateLoadBalancerPorts are used if not empty",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: pointer(corev1.ServiceInternalTrafficPolicyLocal),
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
AllocateLoadBalancerNodePorts: ptr.To(false),
}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: pointer(corev1.ServiceInternalTrafficPolicyCluster),
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyCluster,
AllocateLoadBalancerNodePorts: ptr.To(true),
}},
},
want: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: pointer(corev1.ServiceInternalTrafficPolicyLocal),
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
AllocateLoadBalancerNodePorts: ptr.To(false),
}},
},
{
name: "Reconciled LoadBalancerClass is used if the expected one is empty",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("service.k8s.aws/nlb"),
}},
},
want: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("service.k8s.aws/nlb"),
}},
},
{
name: "Expected LoadBalancerClass is used if not empty",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("explicit.lb/class"),
}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{}},
},
want: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: ptr(corev1.ServiceInternalTrafficPolicyCluster),
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("explicit.lb/class"),
}},
},
{
name: "Expected InternalTrafficPolicy is used if not empty",
name: "Expected LoadBalancerClass can be reset if Type is no longer LoadBalancer",
args: args{
expected: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: ptr(corev1.ServiceInternalTrafficPolicyLocal),
Type: corev1.ServiceTypeClusterIP,
LoadBalancerClass: nil,
}},
reconciled: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: ptr(corev1.ServiceInternalTrafficPolicyCluster),
Type: corev1.ServiceTypeLoadBalancer,
LoadBalancerClass: ptr.To("service.k8s.aws/nlb"),
}},
},
want: corev1.Service{Spec: corev1.ServiceSpec{
InternalTrafficPolicy: ptr(corev1.ServiceInternalTrafficPolicyLocal),
Type: corev1.ServiceTypeClusterIP,
}},
},
}
Expand Down