-
Notifications
You must be signed in to change notification settings - Fork 192
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
OCPBUGS-16728: Require Service Deletion for LB Type Updates #1142
base: master
Are you sure you want to change the base?
Changes from 1 commit
c25c6c1
3dcff76
0d249ce
5862310
1a57afd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |||||||||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||||||||||
"encoding/json" | ||||||||||||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||||||||||||
"k8s.io/apimachinery/pkg/util/sets" | ||||||||||||||||||||||||||||||||||||
"reflect" | ||||||||||||||||||||||||||||||||||||
"strconv" | ||||||||||||||||||||||||||||||||||||
"strings" | ||||||||||||||||||||||||||||||||||||
|
@@ -23,8 +24,6 @@ import ( | |||||||||||||||||||||||||||||||||||
"k8s.io/apimachinery/pkg/api/errors" | ||||||||||||||||||||||||||||||||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||||||||||||||||||||||||||||||||
kerrors "k8s.io/apimachinery/pkg/util/errors" | ||||||||||||||||||||||||||||||||||||
"k8s.io/apimachinery/pkg/util/sets" | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
crclient "sigs.k8s.io/controller-runtime/pkg/client" | ||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
|
@@ -203,9 +202,11 @@ var ( | |||||||||||||||||||||||||||||||||||
configv1.GCPPlatformType: {}, | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// managedLoadBalancerServiceAnnotations is a set of annotation keys for | ||||||||||||||||||||||||||||||||||||
// annotations that the operator manages for LoadBalancer-type services. | ||||||||||||||||||||||||||||||||||||
// The operator preserves all other annotations. | ||||||||||||||||||||||||||||||||||||
// managedLBServiceAnnotations is a set of annotation keys for | ||||||||||||||||||||||||||||||||||||
// annotations that the operator fully manages for LoadBalancer-type services. | ||||||||||||||||||||||||||||||||||||
// The operator preserves all other annotations. Any desired updates to | ||||||||||||||||||||||||||||||||||||
// these annotations are immediately applied to the service without | ||||||||||||||||||||||||||||||||||||
// requiring service deletion. | ||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||
// Be careful when adding annotation keys to this set. If a new release | ||||||||||||||||||||||||||||||||||||
// of the operator starts managing an annotation that it previously | ||||||||||||||||||||||||||||||||||||
|
@@ -214,8 +215,8 @@ var ( | |||||||||||||||||||||||||||||||||||
// <https://bugzilla.redhat.com/show_bug.cgi?id=1905490>). In order to | ||||||||||||||||||||||||||||||||||||
// avoid problems, make sure the previous release blocks upgrades when | ||||||||||||||||||||||||||||||||||||
// the user has modified an annotation that the new release manages. | ||||||||||||||||||||||||||||||||||||
managedLoadBalancerServiceAnnotations = func() sets.String { | ||||||||||||||||||||||||||||||||||||
result := sets.NewString( | ||||||||||||||||||||||||||||||||||||
managedLBServiceAnnotations = func() sets.Set[string] { | ||||||||||||||||||||||||||||||||||||
result := sets.New[string]( | ||||||||||||||||||||||||||||||||||||
// AWS LB health check interval annotation (see | ||||||||||||||||||||||||||||||||||||
// <https://bugzilla.redhat.com/show_bug.cgi?id=1908758>). | ||||||||||||||||||||||||||||||||||||
awsLBHealthCheckIntervalAnnotation, | ||||||||||||||||||||||||||||||||||||
|
@@ -264,6 +265,55 @@ var ( | |||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
return result | ||||||||||||||||||||||||||||||||||||
}() | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// managedAtServiceCreationLBServiceAnnotations maps platform to annotation keys | ||||||||||||||||||||||||||||||||||||
// that the operator manages, but only during service creation. This means that | ||||||||||||||||||||||||||||||||||||
// the operator will not apply a desired annotation update right away. | ||||||||||||||||||||||||||||||||||||
// Instead, it will set a Progressing=True status condition with a message explaining | ||||||||||||||||||||||||||||||||||||
// how to apply the change manually (e.g., by deleting the service so that it can be | ||||||||||||||||||||||||||||||||||||
// recreated). | ||||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||||
// Note that the ingress.operator.openshift.io/auto-delete-load-balancer annotation enables | ||||||||||||||||||||||||||||||||||||
// these annotations to be applied immediately by automatically deleting and recreating the | ||||||||||||||||||||||||||||||||||||
// service. | ||||||||||||||||||||||||||||||||||||
managedAtServiceCreationLBServiceAnnotations = func() map[configv1.PlatformType]sets.Set[string] { | ||||||||||||||||||||||||||||||||||||
result := map[configv1.PlatformType]sets.Set[string]{ | ||||||||||||||||||||||||||||||||||||
configv1.AWSPlatformType: sets.New[string]( | ||||||||||||||||||||||||||||||||||||
// Annotation for configuring load balancer subnets. | ||||||||||||||||||||||||||||||||||||
// This requires service recreation because NLBs do not support updates to subnets, | ||||||||||||||||||||||||||||||||||||
// the operator cannot detect errors if the subnets are invalid, and to prevent | ||||||||||||||||||||||||||||||||||||
// compatibility issues during upgrades since this annotation was previously unmanaged. | ||||||||||||||||||||||||||||||||||||
awsLBSubnetsAnnotation, | ||||||||||||||||||||||||||||||||||||
// Annotation for configuring load balancer EIPs. | ||||||||||||||||||||||||||||||||||||
// This requires service recreation to prevent compatibility issues during upgrades since | ||||||||||||||||||||||||||||||||||||
// this annotation was previously unmanaged. | ||||||||||||||||||||||||||||||||||||
awsEIPAllocationsAnnotation, | ||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// Add the annotations that do NOT support mutable scope. | ||||||||||||||||||||||||||||||||||||
for platform, annotation := range InternalLBAnnotations { | ||||||||||||||||||||||||||||||||||||
if _, ok := platformsWithMutableScope[platform]; !ok { | ||||||||||||||||||||||||||||||||||||
for name := range annotation { | ||||||||||||||||||||||||||||||||||||
Comment on lines
+298
to
+300
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||
if result[platform] == nil { | ||||||||||||||||||||||||||||||||||||
result[platform] = sets.New[string]() | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
result[platform].Insert(name) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
Comment on lines
+299
to
+306
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A little easier to read:
Suggested change
|
||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
for platform, annotation := range externalLBAnnotations { | ||||||||||||||||||||||||||||||||||||
if _, ok := platformsWithMutableScope[platform]; !ok { | ||||||||||||||||||||||||||||||||||||
for name := range annotation { | ||||||||||||||||||||||||||||||||||||
if result[platform] == nil { | ||||||||||||||||||||||||||||||||||||
result[platform] = sets.New[string]() | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
result[platform].Insert(name) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
return result | ||||||||||||||||||||||||||||||||||||
}() | ||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// ensureLoadBalancerService creates an LB service if one is desired but absent. | ||||||||||||||||||||||||||||||||||||
|
@@ -598,8 +648,8 @@ func (r *reconciler) deleteLoadBalancerService(service *corev1.Service, options | |||||||||||||||||||||||||||||||||||
// updateLoadBalancerService updates a load balancer service. Returns a Boolean | ||||||||||||||||||||||||||||||||||||
// indicating whether the service was updated, and an error value. | ||||||||||||||||||||||||||||||||||||
func (r *reconciler) updateLoadBalancerService(current, desired *corev1.Service, platform *configv1.PlatformStatus, autoDeleteLB bool) (bool, error) { | ||||||||||||||||||||||||||||||||||||
if shouldRecreateLB, reason := shouldRecreateLoadBalancer(current, desired, platform); shouldRecreateLB && autoDeleteLB { | ||||||||||||||||||||||||||||||||||||
log.Info("deleting and recreating the load balancer because "+reason, "namespace", desired.Namespace, "name", desired.Name) | ||||||||||||||||||||||||||||||||||||
if shouldRecreateLB, changedAnnotations, changedFields := shouldRecreateLoadBalancer(current, desired, platform); shouldRecreateLB && autoDeleteLB { | ||||||||||||||||||||||||||||||||||||
log.Info("deleting and recreating the load balancer", "annotations", changedAnnotations, "fields", changedFields, "namespace", desired.Namespace, "name", desired.Name) | ||||||||||||||||||||||||||||||||||||
foreground := metav1.DeletePropagationForeground | ||||||||||||||||||||||||||||||||||||
deleteOptions := crclient.DeleteOptions{PropagationPolicy: &foreground} | ||||||||||||||||||||||||||||||||||||
if err := r.deleteLoadBalancerService(current, &deleteOptions); err != nil { | ||||||||||||||||||||||||||||||||||||
|
@@ -624,47 +674,16 @@ func (r *reconciler) updateLoadBalancerService(current, desired *corev1.Service, | |||||||||||||||||||||||||||||||||||
return true, nil | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// scopeEqual returns true if the scope is the same between the two given | ||||||||||||||||||||||||||||||||||||
// services and false if the scope is different. | ||||||||||||||||||||||||||||||||||||
func scopeEqual(a, b *corev1.Service, platform *configv1.PlatformStatus) bool { | ||||||||||||||||||||||||||||||||||||
aAnnotations := a.Annotations | ||||||||||||||||||||||||||||||||||||
if aAnnotations == nil { | ||||||||||||||||||||||||||||||||||||
aAnnotations = map[string]string{} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
bAnnotations := b.Annotations | ||||||||||||||||||||||||||||||||||||
if bAnnotations == nil { | ||||||||||||||||||||||||||||||||||||
bAnnotations = map[string]string{} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
for name := range InternalLBAnnotations[platform.Type] { | ||||||||||||||||||||||||||||||||||||
if aAnnotations[name] != bAnnotations[name] { | ||||||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
for name := range externalLBAnnotations[platform.Type] { | ||||||||||||||||||||||||||||||||||||
if aAnnotations[name] != bAnnotations[name] { | ||||||||||||||||||||||||||||||||||||
return false | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
return true | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// shouldRecreateLoadBalancer determines whether a load balancer needs to be | ||||||||||||||||||||||||||||||||||||
// recreated and returns the reason for its recreation. | ||||||||||||||||||||||||||||||||||||
func shouldRecreateLoadBalancer(current, desired *corev1.Service, platform *configv1.PlatformStatus) (bool, string) { | ||||||||||||||||||||||||||||||||||||
_, platformHasMutableScope := platformsWithMutableScope[platform.Type] | ||||||||||||||||||||||||||||||||||||
if !platformHasMutableScope && !scopeEqual(current, desired, platform) { | ||||||||||||||||||||||||||||||||||||
return true, "its scope changed" | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
if platform.Type == configv1.AWSPlatformType && !serviceSubnetsEqual(current, desired) { | ||||||||||||||||||||||||||||||||||||
return true, "its subnets changed" | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
if platform.Type == configv1.AWSPlatformType && !serviceEIPAllocationsEqual(current, desired) { | ||||||||||||||||||||||||||||||||||||
return true, "its eipAllocations changed" | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
// recreated and returns the changed annotations and fields that require recreation. | ||||||||||||||||||||||||||||||||||||
func shouldRecreateLoadBalancer(current, desired *corev1.Service, platform *configv1.PlatformStatus) (bool, []string, []string) { | ||||||||||||||||||||||||||||||||||||
shouldRecreateLB, changedAnnotations, _ := loadBalancerServiceAnnotationsChanged(current, desired, managedAtServiceCreationLBServiceAnnotations[platform.Type]) | ||||||||||||||||||||||||||||||||||||
var changedFields []string | ||||||||||||||||||||||||||||||||||||
if platform.Type == configv1.OpenStackPlatformType && current.Spec.LoadBalancerIP != desired.Spec.LoadBalancerIP { | ||||||||||||||||||||||||||||||||||||
return true, "its load balancer IP changed" | ||||||||||||||||||||||||||||||||||||
shouldRecreateLB = true | ||||||||||||||||||||||||||||||||||||
changedFields = append(changedFields, "spec.loadBalancerIP") | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
return false, "" | ||||||||||||||||||||||||||||||||||||
return shouldRecreateLB, changedAnnotations, changedFields | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// loadBalancerServiceChanged checks if the current load balancer service | ||||||||||||||||||||||||||||||||||||
|
@@ -678,7 +697,7 @@ func loadBalancerServiceChanged(current, expected *corev1.Service) (bool, *corev | |||||||||||||||||||||||||||||||||||
// avoid problems, make sure the previous release blocks upgrades when | ||||||||||||||||||||||||||||||||||||
// the user has modified an annotation or spec field that the new | ||||||||||||||||||||||||||||||||||||
// release manages. | ||||||||||||||||||||||||||||||||||||
changed, updated := loadBalancerServiceAnnotationsChanged(current, expected, managedLoadBalancerServiceAnnotations) | ||||||||||||||||||||||||||||||||||||
changed, _, updated := loadBalancerServiceAnnotationsChanged(current, expected, managedLBServiceAnnotations) | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// If spec.loadBalancerSourceRanges is nonempty on the service, that | ||||||||||||||||||||||||||||||||||||
// means that allowedSourceRanges is nonempty on the ingresscontroller, | ||||||||||||||||||||||||||||||||||||
|
@@ -706,18 +725,17 @@ func loadBalancerServiceChanged(current, expected *corev1.Service) (bool, *corev | |||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// loadBalancerServiceAnnotationsChanged checks if the annotations on the expected Service | ||||||||||||||||||||||||||||||||||||
// match the ones on the current Service. | ||||||||||||||||||||||||||||||||||||
func loadBalancerServiceAnnotationsChanged(current, expected *corev1.Service, annotations sets.String) (bool, *corev1.Service) { | ||||||||||||||||||||||||||||||||||||
changed := false | ||||||||||||||||||||||||||||||||||||
func loadBalancerServiceAnnotationsChanged(current, expected *corev1.Service, annotations sets.Set[string]) (bool, []string, *corev1.Service) { | ||||||||||||||||||||||||||||||||||||
var changedAnnotations []string | ||||||||||||||||||||||||||||||||||||
for annotation := range annotations { | ||||||||||||||||||||||||||||||||||||
currentVal, have := current.Annotations[annotation] | ||||||||||||||||||||||||||||||||||||
expectedVal, want := expected.Annotations[annotation] | ||||||||||||||||||||||||||||||||||||
if (want && (!have || currentVal != expectedVal)) || (have && !want) { | ||||||||||||||||||||||||||||||||||||
changed = true | ||||||||||||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||||||||||||
changedAnnotations = append(changedAnnotations, annotation) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
if !changed { | ||||||||||||||||||||||||||||||||||||
return false, nil | ||||||||||||||||||||||||||||||||||||
if len(changedAnnotations) == 0 { | ||||||||||||||||||||||||||||||||||||
return false, nil, nil | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
updated := current.DeepCopy() | ||||||||||||||||||||||||||||||||||||
|
@@ -736,7 +754,7 @@ func loadBalancerServiceAnnotationsChanged(current, expected *corev1.Service, an | |||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
return true, updated | ||||||||||||||||||||||||||||||||||||
return true, changedAnnotations, updated | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// IsServiceInternal returns a Boolean indicating whether the provided service | ||||||||||||||||||||||||||||||||||||
|
@@ -774,7 +792,7 @@ func loadBalancerServiceIsUpgradeable(ic *operatorv1.IngressController, deployme | |||||||||||||||||||||||||||||||||||
// Since the status logic runs after the controller sets the annotations, it checks for | ||||||||||||||||||||||||||||||||||||
// any discrepancy (in case modified) between the desired annotation values of the controller | ||||||||||||||||||||||||||||||||||||
// and the current annotation values. | ||||||||||||||||||||||||||||||||||||
changed, updated := loadBalancerServiceAnnotationsChanged(current, desired, managedLoadBalancerServiceAnnotations) | ||||||||||||||||||||||||||||||||||||
changed, updated, _ := loadBalancerServiceAnnotationsChanged(current, desired, managedLBServiceAnnotations) | ||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||
if changed { | ||||||||||||||||||||||||||||||||||||
diff := cmp.Diff(current, updated, cmpopts.EquateEmpty()) | ||||||||||||||||||||||||||||||||||||
return fmt.Errorf("load balancer service has been modified; changes must be reverted before upgrading: %s", diff) | ||||||||||||||||||||||||||||||||||||
|
@@ -1047,16 +1065,6 @@ func getEIPAllocationsFromServiceAnnotation(service *corev1.Service) []operatorv | |||||||||||||||||||||||||||||||||||
return awsEIPAllocations | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// serviceSubnetsEqual compares the subnet annotations on two services to determine if they are equivalent, | ||||||||||||||||||||||||||||||||||||
// ignoring the order of the subnets. | ||||||||||||||||||||||||||||||||||||
func serviceSubnetsEqual(a, b *corev1.Service) bool { | ||||||||||||||||||||||||||||||||||||
return awsSubnetsEqual(getSubnetsFromServiceAnnotation(a), getSubnetsFromServiceAnnotation(b)) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
func serviceEIPAllocationsEqual(a, b *corev1.Service) bool { | ||||||||||||||||||||||||||||||||||||
return awsEIPAllocationsEqual(getEIPAllocationsFromServiceAnnotation(a), getEIPAllocationsFromServiceAnnotation(b)) | ||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||
// awsEIPAllocationsEqual compares two AWSEIPAllocation slices and returns a boolean | ||||||||||||||||||||||||||||||||||||
// whether they are equal are not. The order of the EIP Allocations are ignored. | ||||||||||||||||||||||||||||||||||||
func awsEIPAllocationsEqual(eipAllocations1, eipAllocations2 []operatorv1.EIPAllocation) bool { | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This import got erroneously moved.