-
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 | ||||
---|---|---|---|---|---|---|
|
@@ -228,8 +228,6 @@ var ( | |||||
// local-with-fallback annotation for kube-proxy (see | ||||||
// <https://bugzilla.redhat.com/show_bug.cgi?id=1960284>). | ||||||
localWithFallbackAnnotation, | ||||||
// AWS load balancer type annotation to set either CLB/ELB or NLB | ||||||
AWSLBTypeAnnotation, | ||||||
// awsLBProxyProtocolAnnotation is used to enable the PROXY protocol on any | ||||||
// AWS load balancer services created. | ||||||
// | ||||||
|
@@ -279,6 +277,11 @@ var ( | |||||
managedAtServiceCreationLBServiceAnnotations = func() map[configv1.PlatformType]sets.Set[string] { | ||||||
result := map[configv1.PlatformType]sets.Set[string]{ | ||||||
configv1.AWSPlatformType: sets.New[string]( | ||||||
// Annotation to set either CLB/ELB or NLB. | ||||||
// This annotation was previously fully managed (https://issues.redhat.com/browse/NE-865), | ||||||
// but now required service recreation due to issues with AWS leaking resources when updating | ||||||
// LB Type without recreating the service (see https://issues.redhat.com/browse/OCPBUGS-16728). | ||||||
AWSLBTypeAnnotation, | ||||||
// 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 | ||||||
|
@@ -801,29 +804,61 @@ func loadBalancerServiceIsUpgradeable(ic *operatorv1.IngressController, deployme | |||||
return nil | ||||||
} | ||||||
|
||||||
// effectuateMessage returns a message describing how to effectuate a | ||||||
// change that requires the service to be deleted. | ||||||
func effectuateMessage(changedMsg, ocPatchRevertCmd string, service *corev1.Service) string { | ||||||
return fmt.Sprintf( | ||||||
"%[1]s To effectuate this change, you must delete the service: `oc -n %[2]s delete svc/%[3]s`; "+ | ||||||
"the service load-balancer will then be deprovisioned and a new one created. This will most likely "+ | ||||||
"cause the new load-balancer to have a different host name and IP address and cause disruption. To "+ | ||||||
"return to the previous state, you can revert the change to the IngressController: `%[4]s`. "+ | ||||||
"Direct updates to the service annotations are not supported.", | ||||||
changedMsg, service.Namespace, service.Name, ocPatchRevertCmd, | ||||||
) | ||||||
} | ||||||
|
||||||
// loadBalancerServiceIsProgressing returns an error value indicating if the | ||||||
// load balancer service is in progressing status. | ||||||
// Tricky: Status fields have mixed functions. Older status fields often represent the | ||||||
// *desired* state, while some newer fields reflect the *actual* state of the object. | ||||||
func loadBalancerServiceIsProgressing(ic *operatorv1.IngressController, service *corev1.Service, platform *configv1.PlatformStatus, subnetsAWSEnabled bool, eipAllocationsAWSEnabled bool) error { | ||||||
var errs []error | ||||||
// Tricky: Scope in the status indicates the *desired* scope (user desire + defaulting). | ||||||
wantScope := ic.Status.EndpointPublishingStrategy.LoadBalancer.Scope | ||||||
haveScope := operatorv1.ExternalLoadBalancer | ||||||
if IsServiceInternal(service) { | ||||||
haveScope = operatorv1.InternalLoadBalancer | ||||||
} | ||||||
if wantScope != haveScope { | ||||||
err := fmt.Errorf("The IngressController scope was changed from %q to %q.", haveScope, wantScope) | ||||||
changedMsg := fmt.Sprintf("The IngressController scope was changed from %q to %q.", haveScope, wantScope) | ||||||
err := fmt.Errorf(changedMsg) | ||||||
if _, ok := platformsWithMutableScope[platform.Type]; !ok { | ||||||
err = fmt.Errorf("%[1]s To effectuate this change, you must delete the service: `oc -n %[2]s delete svc/%[3]s`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address from the old one's. Alternatively, you can revert the change to the IngressController: `oc -n openshift-ingress-operator patch ingresscontrollers/%[4]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"loadBalancer\":{\"scope\":\"%[5]s\"}}}}'`", err.Error(), service.Namespace, service.Name, ic.Name, haveScope) | ||||||
ocPatchRevertCmd := fmt.Sprintf("oc -n openshift-ingress-operator patch ingresscontrollers/%[1]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"loadBalancer\":{\"scope\":\"%[2]s\"}}}}'", ic.Name, haveScope) | ||||||
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
|
||||||
err = fmt.Errorf(effectuateMessage(changedMsg, ocPatchRevertCmd, service)) | ||||||
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. I think you have extra format expansion again, here and everywhere where you have |
||||||
} | ||||||
errs = append(errs, err) | ||||||
} | ||||||
|
||||||
haveLBType := getAWSLoadBalancerTypeFromServiceAnnotation(service) | ||||||
if platform.Type == configv1.AWSPlatformType { | ||||||
// Tricky: LB Type in the status indicates the *desired* scope (user desire + defaulting). | ||||||
wantLBType := getAWSLoadBalancerTypeInStatus(ic) | ||||||
Comment on lines
+841
to
+844
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. This logic is confusing. Why is |
||||||
|
||||||
if wantLBType != haveLBType { | ||||||
changedMsg := fmt.Sprintf("The IngressController load balancer type was changed from %q to %q.", haveLBType, wantLBType) | ||||||
ocPatchRevertCmd := fmt.Sprintf("oc -n openshift-ingress-operator patch ingresscontrollers/%[1]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"type\":\"LoadBalancerService\",\"loadBalancer\":{\"providerParameters\":{\"type\":\"AWS\",\"aws\":{\"type\":\"%[2]s\"}}}}}}'", ic.Name, haveLBType) | ||||||
err := fmt.Errorf(effectuateMessage(changedMsg, ocPatchRevertCmd, service)) | ||||||
errs = append(errs, err) | ||||||
} | ||||||
} | ||||||
|
||||||
if platform.Type == configv1.AWSPlatformType && subnetsAWSEnabled { | ||||||
var ( | ||||||
// Tricky: Subnets in the status indicates the *actual* scope. | ||||||
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. Similar question. Can we read subnets from the service annotation directly? 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. Subnets and EIPs status do reflect the actual state, but yes, we can also read it from the service annotation directly. It doesn't really matter either way. I implemented this way because I liked the idea of putting the annotation into the IC status value, then using the status as a single point to access the actual value. |
||||||
wantSubnets, haveSubnets *operatorv1.AWSSubnets | ||||||
paramsFieldName string | ||||||
) | ||||||
switch getAWSLoadBalancerTypeInStatus(ic) { | ||||||
switch haveLBType { | ||||||
case operatorv1.AWSNetworkLoadBalancer: | ||||||
if nlbParams := getAWSNetworkLoadBalancerParametersInSpec(ic); nlbParams != nil { | ||||||
wantSubnets = nlbParams.Subnets | ||||||
|
@@ -848,14 +883,15 @@ func loadBalancerServiceIsProgressing(ic *operatorv1.IngressController, service | |||||
haveSubnetsPrettyJson := convertAWSSubnetListToPatchJson(haveSubnets, "{}", "[]") | ||||||
wantSubnetsPrettyJson := convertAWSSubnetListToPatchJson(wantSubnets, "{}", "[]") | ||||||
changedMsg := fmt.Sprintf("The IngressController subnets were changed from %q to %q.", haveSubnetsPrettyJson, wantSubnetsPrettyJson) | ||||||
ocPatchRevertCmd := fmt.Sprintf("oc -n openshift-ingress-operator patch ingresscontrollers/%[1]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"type\":\"LoadBalancerService\",\"loadBalancer\":{\"providerParameters\":{\"type\":\"AWS\",\"aws\":{\"type\":\"%[2]s\",\"%[3]s\":{\"subnets\":%[4]s}}}}}}}'", ic.Name, getAWSLoadBalancerTypeInStatus(ic), paramsFieldName, haveSubnetsPatchJson) | ||||||
err := fmt.Errorf("%[1]s To effectuate this change, you must delete the service: `oc -n %[2]s delete svc/%[3]s`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `%[4]s`", changedMsg, service.Namespace, service.Name, ocPatchRevertCmd) | ||||||
ocPatchRevertCmd := fmt.Sprintf("oc -n openshift-ingress-operator patch ingresscontrollers/%[1]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"type\":\"LoadBalancerService\",\"loadBalancer\":{\"providerParameters\":{\"type\":\"AWS\",\"aws\":{\"type\":\"%[2]s\",\"%[3]s\":{\"subnets\":%[4]s}}}}}}}'", ic.Name, haveLBType, paramsFieldName, haveSubnetsPatchJson) | ||||||
err := fmt.Errorf(effectuateMessage(changedMsg, ocPatchRevertCmd, service)) | ||||||
errs = append(errs, err) | ||||||
} | ||||||
} | ||||||
|
||||||
if platform.Type == configv1.AWSPlatformType && eipAllocationsAWSEnabled && getAWSLoadBalancerTypeInStatus(ic) == operatorv1.AWSNetworkLoadBalancer { | ||||||
if platform.Type == configv1.AWSPlatformType && eipAllocationsAWSEnabled && haveLBType == operatorv1.AWSNetworkLoadBalancer { | ||||||
var ( | ||||||
// Tricky: EIP Allocations in the status indicate the *actual* scope. | ||||||
wantEIPAllocations, haveEIPAllocations []operatorv1.EIPAllocation | ||||||
) | ||||||
if nlbParams := getAWSNetworkLoadBalancerParametersInSpec(ic); nlbParams != nil { | ||||||
|
@@ -871,20 +907,21 @@ func loadBalancerServiceIsProgressing(ic *operatorv1.IngressController, service | |||||
haveEIPAllocationsPrettyJson := convertAWSEIPAllocationsListToPatchJson(haveEIPAllocations, "[]") | ||||||
wantEIPAllocationsPrettyJson := convertAWSEIPAllocationsListToPatchJson(wantEIPAllocations, "[]") | ||||||
changedMsg := fmt.Sprintf("The IngressController eipAllocations were changed from %q to %q.", haveEIPAllocationsPrettyJson, wantEIPAllocationsPrettyJson) | ||||||
ocPatchRevertCmd := fmt.Sprintf("oc -n openshift-ingress-operator patch ingresscontrollers/%[1]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"type\":\"LoadBalancerService\",\"loadBalancer\":{\"providerParameters\":{\"type\":\"AWS\",\"aws\":{\"type\":\"%[2]s\",\"%[3]s\":{\"eipAllocations\":%[4]s}}}}}}}'", ic.Name, getAWSLoadBalancerTypeInStatus(ic), "networkLoadBalancer", haveEIPAllocationsPatchJson) | ||||||
err := fmt.Errorf("%[1]s To effectuate this change, you must delete the service: `oc -n %[2]s delete svc/%[3]s`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `%[4]s`", changedMsg, service.Namespace, service.Name, ocPatchRevertCmd) | ||||||
ocPatchRevertCmd := fmt.Sprintf("oc -n openshift-ingress-operator patch ingresscontrollers/%[1]s --type=merge --patch='{\"spec\":{\"endpointPublishingStrategy\":{\"type\":\"LoadBalancerService\",\"loadBalancer\":{\"providerParameters\":{\"type\":\"AWS\",\"aws\":{\"type\":\"%[2]s\",\"%[3]s\":{\"eipAllocations\":%[4]s}}}}}}}'", ic.Name, haveLBType, "networkLoadBalancer", haveEIPAllocationsPatchJson) | ||||||
err := fmt.Errorf(effectuateMessage(changedMsg, ocPatchRevertCmd, service)) | ||||||
errs = append(errs, err) | ||||||
} | ||||||
} | ||||||
|
||||||
if platform.Type == configv1.OpenStackPlatformType { | ||||||
// Tricky: FloatingIP in the status indicate the *actual* scope. | ||||||
wantFloatingIP := getOpenStackFloatingIPInSpec(ic) | ||||||
haveFloatingIP := getOpenStackFloatingIPInStatus(ic) | ||||||
// OpenStack CCM does not support updating Service.Spec.LoadBalancerIP after creation, the load balancer will never be updated. | ||||||
if wantFloatingIP != haveFloatingIP { | ||||||
changeMsg := fmt.Sprintf("The IngressController floatingIP was changed from %q to %q.", haveFloatingIP, wantFloatingIP) | ||||||
changedMsg := fmt.Sprintf("The IngressController floatingIP was changed from %q to %q.", haveFloatingIP, wantFloatingIP) | ||||||
ocPatchRevertCmd := fmt.Sprintf(`oc -n %[1]s patch ingresscontrollers/%[2]s --type=merge --patch='{"spec":{"endpointPublishingStrategy":{"type":"LoadBalancerService","loadBalancer":{"providerParameters":{"type":"OpenStack","openstack":{"floatingIP":"%[3]s"}}}}}}'`, ic.Namespace, ic.Name, haveFloatingIP) | ||||||
err := fmt.Errorf("%[1]s To effectuate this change, you must delete the service: `oc -n %[2]s delete svc/%[3]s`; the service load-balancer will then be deprovisioned and a new one created. This will most likely cause the new load-balancer to have a different host name and IP address and cause disruption. To return to the previous state, you can revert the change to the IngressController: `%[4]s`", changeMsg, service.Namespace, service.Name, ocPatchRevertCmd) | ||||||
err := fmt.Errorf(effectuateMessage(changedMsg, ocPatchRevertCmd, service)) | ||||||
errs = append(errs, err) | ||||||
} | ||||||
} | ||||||
|
@@ -997,6 +1034,21 @@ func loadBalancerSourceRangesMatch(ic *operatorv1.IngressController, current *co | |||||
return fmt.Errorf("You have manually edited an operator-managed object. You must revert your modifications by removing the Spec.LoadBalancerSourceRanges field of LoadBalancer-typed service %q. You can use the new AllowedSourceRanges API field on the ingresscontroller to configure this setting instead.", current.Name) | ||||||
} | ||||||
|
||||||
// getAWSLoadBalancerTypeFromServiceAnnotation gets the effective load balancer type by looking at the | ||||||
// service.beta.kubernetes.io/aws-load-balancer-type annotation of the LoadBalancer-type Service. | ||||||
// If the annotation isn't specified, the function returns the default of Classic. | ||||||
func getAWSLoadBalancerTypeFromServiceAnnotation(service *corev1.Service) operatorv1.AWSLoadBalancerType { | ||||||
if service == nil { | ||||||
return "" | ||||||
} | ||||||
|
||||||
if a, ok := service.Annotations[AWSLBTypeAnnotation]; ok && a == AWSNLBAnnotation { | ||||||
return operatorv1.AWSNetworkLoadBalancer | ||||||
} | ||||||
|
||||||
return operatorv1.AWSClassicLoadBalancer | ||||||
} | ||||||
|
||||||
// getSubnetsFromServiceAnnotation gets the effective subnets by looking at the | ||||||
// service.beta.kubernetes.io/aws-load-balancer-subnets annotation of the LoadBalancer-type Service. | ||||||
// If no subnets are specified in the annotation, this function returns nil. | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1200,7 +1200,14 @@ func Test_loadBalancerServiceChanged(t *testing.T) { | |||||
svc.Annotations["service.beta.kubernetes.io/aws-load-balancer-subnets"] = "foo-subnet" | ||||||
svc.Annotations["service.beta.kubernetes.io/aws-load-balancer-type"] = "NLB" | ||||||
}, | ||||||
expect: true, | ||||||
expect: false, | ||||||
}, | ||||||
{ | ||||||
description: "if the service.beta.kubernetes.io/aws-load-balancer-type is added", | ||||||
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
|
||||||
mutate: func(svc *corev1.Service) { | ||||||
svc.Annotations["service.beta.kubernetes.io/aws-load-balancer-type"] = "NLB" | ||||||
}, | ||||||
expect: false, | ||||||
}, | ||||||
{ | ||||||
description: "if the service.beta.kubernetes.io/aws-load-balancer-eip-allocations annotation added", | ||||||
|
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.
I don't think we want double format expansion, do we?
or
or
import "errors"
and then