Skip to content

Commit 4203ca3

Browse files
committed
add delete-protection-enabled annotation for LB preservation on IngressClass delete
- Add a new annotation to preserve LB on IngressClass delete - we clear NSG IDs, WAF, and default_ingress BackendSet associated with the LB - Update UpdateNetworkSecurityGroups method to accept parameters instead of a request object to avoid code duplication
1 parent d99a88f commit 4203ca3

File tree

7 files changed

+178
-22
lines changed

7 files changed

+178
-22
lines changed

GettingStarted.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ The native ingress controller itself is lightweight process and pushes all the r
3636
+ [Web Firewall Integration](#web-firewall-integration)
3737
+ [Ingress Level HTTP(S) Listener Ports](#ingress-level-https-listener-ports)
3838
+ [TCP Listener Support](#tcp-listener-support)
39+
+ [Network Security Groups Support](#network-security-groups-support)
40+
+ [Load Balancer Preservation on `IngressClass` delete](#load-balancer-preservation-on-ingressclass-delete)
3941
* [Dependency management](#dependency-management)
4042
+ [How to introduce new modules or upgrade existing ones?](#how-to-introduce-new-modules-or-upgrade-existing-ones)
4143
* [Known Issues](#known-issues)
@@ -50,6 +52,7 @@ Currently supported kubernetes versions are:
5052
- 1.27
5153
- 1.28
5254
- 1.29
55+
- 1.30
5356

5457
We set up the cluster with either native pod networking or flannel CNI and update the security rules.
5558
The documentation for NPN : [Doc Ref](https://docs.oracle.com/en-us/iaas/Content/ContEng/Concepts/contengpodnetworking_topic-OCI_CNI_plugin.htm).
@@ -603,6 +606,38 @@ spec:
603606
number: 8081
604607
```
605608
609+
### Network Security Groups Support
610+
Users can use the `IngressClass` resource annotation `oci-native-ingress.oraclecloud.com/network-security-group-ids` to supply
611+
a comma separated list of Network Security Group OCIDs.
612+
The supplied NSGs will be associated with the LB associated with the `IngressClass`.
613+
614+
Example:
615+
```yaml
616+
apiVersion: networking.k8s.io/v1
617+
kind: IngressClass
618+
metadata:
619+
annotations:
620+
oci-native-ingress.oraclecloud.com/network-security-group-ids: ocid1.networksecuritygroup.oc1.abc,ocid1.networksecuritygroup.oc1.xyz
621+
```
622+
623+
### Load Balancer Preservation on `IngressClass` delete
624+
If you want the Load Balancer associated with an `IngressClass` resource to be preserved after `IngressClass` is deleted,
625+
set the annotation `oci-native-ingress.oraclecloud.com/delete-protection-enabled` annotation to `"true"`.
626+
This annotation defaults to `"false"` when not specified or empty.
627+
628+
OCI Native Ingress Controller will aim to leave the LB in a 'blank' state - clear all NSG associated with the LB,
629+
delete the Web App Firewall associated with the LB if any, and delete the `default_ingress` BackendSet when the `IngressClass` is deleted with this annotation set to true.
630+
Please note that users should first delete all `Ingress` resources associated with this `IngressClass` first, or orphaned resources like Listeners, BackendSets, etc. will
631+
still be present on the LB after the `IngressClass` is deleted
632+
633+
Example:
634+
```yaml
635+
apiVersion: networking.k8s.io/v1
636+
kind: IngressClass
637+
metadata:
638+
annotations:
639+
oci-native-ingress.oraclecloud.com/delete-protection-enabled: "true"
640+
```
606641

607642
### Dependency management
608643
Module [vendoring](https://go.dev/ref/mod#vendoring) is used to manage 3d-party modules in the project.

pkg/controllers/ingressclass/ingressclass.go

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ func (c *Controller) checkForIngressClassParameterUpdates(ctx context.Context, l
406406
}
407407

408408
func (c *Controller) checkForNetworkSecurityGroupsUpdate(ctx context.Context, ic *networkingv1.IngressClass) error {
409-
lb, etag, err := c.getLoadBalancer(ctx, ic)
409+
lb, _, err := c.getLoadBalancer(ctx, ic)
410410
if err != nil {
411411
return err
412412
}
@@ -427,31 +427,68 @@ func (c *Controller) checkForNetworkSecurityGroupsUpdate(ctx context.Context, ic
427427
return nil
428428
}
429429

430-
req := ociloadbalancer.UpdateNetworkSecurityGroupsRequest{
431-
LoadBalancerId: lb.Id,
432-
IfMatch: common.String(etag),
433-
UpdateNetworkSecurityGroupsDetails: ociloadbalancer.UpdateNetworkSecurityGroupsDetails{
434-
NetworkSecurityGroupIds: nsgIdsFromSpec,
435-
},
436-
}
437-
klog.Infof("Update lb nsg ids request: %s", util.PrettyPrint(req))
438-
439-
_, err = wrapperClient.GetLbClient().UpdateNetworkSecurityGroups(context.Background(), req)
430+
_, err = wrapperClient.GetLbClient().UpdateNetworkSecurityGroups(context.Background(), *lb.Id, nsgIdsFromSpec)
440431
return err
441432
}
442433

443434
func (c *Controller) deleteIngressClass(ctx context.Context, ic *networkingv1.IngressClass) error {
435+
if util.GetIngressClassDeleteProtectionEnabled(ic) {
436+
err := c.clearLoadBalancer(ctx, ic)
437+
if err != nil {
438+
return err
439+
}
440+
} else {
441+
err := c.deleteLoadBalancer(ctx, ic)
442+
if err != nil {
443+
return err
444+
}
445+
}
444446

445-
err := c.deleteLoadBalancer(ctx, ic)
447+
err := c.deleteFinalizer(ctx, ic)
446448
if err != nil {
447449
return err
448450
}
449451

450-
err = c.deleteFinalizer(ctx, ic)
452+
return nil
453+
}
454+
455+
// clearLoadBalancer clears the default_ingress backend, NSG attachment, and WAF firewall from the LB
456+
func (c *Controller) clearLoadBalancer(ctx context.Context, ic *networkingv1.IngressClass) error {
457+
lb, _, err := c.getLoadBalancer(ctx, ic)
451458
if err != nil {
452459
return err
453460
}
454461

462+
if lb == nil {
463+
klog.Infof("Tried to clear LB for ic %s/%s, but it is deleted", ic.Namespace, ic.Name)
464+
return nil
465+
}
466+
467+
wrapperClient, ok := ctx.Value(util.WrapperClient).(*client.WrapperClient)
468+
if !ok {
469+
return fmt.Errorf(util.OciClientNotFoundInContextError)
470+
}
471+
472+
fireWallId := util.GetIngressClassFireWallId(ic)
473+
if fireWallId != "" {
474+
wrapperClient.GetWafClient().DeleteWebAppFirewallWithId(fireWallId)
475+
}
476+
477+
nsgIds := util.GetIngressClassNetworkSecurityGroupIds(ic)
478+
if len(nsgIds) > 0 {
479+
_, err = wrapperClient.GetLbClient().UpdateNetworkSecurityGroups(context.Background(), *lb.Id, make([]string, 0))
480+
if err != nil {
481+
klog.Errorf("While clearing LB %s, cannot clear NSG IDs due to %s, will proceed with IngressClass deletion for %s/%s",
482+
*lb.Id, err.Error(), ic.Namespace, ic.Name)
483+
}
484+
}
485+
486+
err = wrapperClient.GetLbClient().DeleteBackendSet(context.Background(), *lb.Id, util.DefaultBackendSetName)
487+
if err != nil {
488+
klog.Errorf("While clearing LB %s, cannot clear BackendSet %s due to %s, will proceed with IngressClass deletion for %s/%s",
489+
*lb.Id, util.DefaultBackendSetName, err.Error(), ic.Namespace, ic.Name)
490+
}
491+
455492
return nil
456493
}
457494

pkg/controllers/ingressclass/ingressclass_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,41 @@ func TestDeleteIngressClass(t *testing.T) {
117117
Expect(err).Should(BeNil())
118118
}
119119

120+
func TestClearLoadBalancerWhenLBFound(t *testing.T) {
121+
RegisterTestingT(t)
122+
ctx, cancel := context.WithCancel(context.Background())
123+
defer cancel()
124+
125+
ingressClassList := util.GetIngressClassListWithLBSet("id")
126+
ingressClassList.Items[0].Annotations[util.IngressClassFireWallIdAnnotation] = "firewallId"
127+
ingressClassList.Items[0].Annotations[util.IngressClassNetworkSecurityGroupIdsAnnotation] = "nsgId"
128+
c := inits(ctx, ingressClassList)
129+
err := c.clearLoadBalancer(getContextWithClient(c, ctx), &ingressClassList.Items[0])
130+
Expect(err).Should(BeNil())
131+
}
132+
133+
func TestClearLoadBalancerWhenLBNotFound(t *testing.T) {
134+
RegisterTestingT(t)
135+
ctx, cancel := context.WithCancel(context.Background())
136+
defer cancel()
137+
138+
ingressClassList := util.GetIngressClassListWithLBSet("notfound")
139+
c := inits(ctx, ingressClassList)
140+
err := c.clearLoadBalancer(getContextWithClient(c, ctx), &ingressClassList.Items[0])
141+
Expect(err).Should(BeNil())
142+
}
143+
144+
func TestClearLoadBalancerWhenNetworkError(t *testing.T) {
145+
RegisterTestingT(t)
146+
ctx, cancel := context.WithCancel(context.Background())
147+
defer cancel()
148+
149+
ingressClassList := util.GetIngressClassListWithLBSet("networkerror")
150+
c := inits(ctx, ingressClassList)
151+
err := c.clearLoadBalancer(getContextWithClient(c, ctx), &ingressClassList.Items[0])
152+
Expect(err).ShouldNot(BeNil())
153+
}
154+
120155
func TestDeleteLoadBalancer(t *testing.T) {
121156
RegisterTestingT(t)
122157
ctx, cancel := context.WithCancel(context.Background())

pkg/loadbalancer/loadbalancer.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,19 @@ func (lbc *LoadBalancerClient) GetBackendSetHealth(ctx context.Context, lbID str
8686
return &resp.BackendSetHealth, nil
8787
}
8888

89-
func (lbc *LoadBalancerClient) UpdateNetworkSecurityGroups(ctx context.Context, req loadbalancer.UpdateNetworkSecurityGroupsRequest) (loadbalancer.UpdateNetworkSecurityGroupsResponse, error) {
89+
func (lbc *LoadBalancerClient) UpdateNetworkSecurityGroups(ctx context.Context, lbId string, nsgIds []string) (loadbalancer.UpdateNetworkSecurityGroupsResponse, error) {
90+
_, etag, err := lbc.GetLoadBalancer(ctx, lbId)
91+
92+
req := loadbalancer.UpdateNetworkSecurityGroupsRequest{
93+
LoadBalancerId: common.String(lbId),
94+
IfMatch: common.String(etag),
95+
UpdateNetworkSecurityGroupsDetails: loadbalancer.UpdateNetworkSecurityGroupsDetails{
96+
NetworkSecurityGroupIds: nsgIds,
97+
},
98+
}
99+
100+
klog.Infof("Update LB NSG IDs request: %s", util.PrettyPrint(req))
101+
90102
resp, err := lbc.LbClient.UpdateNetworkSecurityGroups(ctx, req)
91103
if err != nil {
92104
return resp, err

pkg/loadbalancer/loadbalancer_test.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,15 +208,8 @@ func TestLoadBalancerClient_UpdateListener(t *testing.T) {
208208
func TestLoadBalancerClient_UpdateNetworkSecurityGroups(t *testing.T) {
209209
RegisterTestingT(t)
210210
loadBalancerClient := setupLBClient()
211-
req := ociloadbalancer.UpdateNetworkSecurityGroupsRequest{
212-
LoadBalancerId: common.String("id"),
213-
UpdateNetworkSecurityGroupsDetails: ociloadbalancer.UpdateNetworkSecurityGroupsDetails{
214-
NetworkSecurityGroupIds: []string{"id1", "id2"},
215-
},
216-
OpcRetryToken: common.String("token"),
217-
}
218211

219-
_, err := loadBalancerClient.UpdateNetworkSecurityGroups(context.TODO(), req)
212+
_, err := loadBalancerClient.UpdateNetworkSecurityGroups(context.TODO(), "id", []string{"id1", "id2"})
220213
Expect(err).To(BeNil())
221214
}
222215

pkg/util/util.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const (
6767
IngressClassWafPolicyAnnotation = "oci-native-ingress.oraclecloud.com/waf-policy-ocid"
6868
IngressClassFireWallIdAnnotation = "oci-native-ingress.oraclecloud.com/firewall-id"
6969
IngressClassNetworkSecurityGroupIdsAnnotation = "oci-native-ingress.oraclecloud.com/network-security-group-ids"
70+
IngressClassDeleteProtectionEnabledAnnotation = "oci-native-ingress.oraclecloud.com/delete-protection-enabled"
7071

7172
IngressHealthCheckProtocolAnnotation = "oci-native-ingress.oraclecloud.com/healthcheck-protocol"
7273
IngressHealthCheckPortAnnotation = "oci-native-ingress.oraclecloud.com/healthcheck-port"
@@ -171,6 +172,23 @@ func GetIngressClassNetworkSecurityGroupIds(ic *networkingv1.IngressClass) []str
171172
return networkSecurityGroupIds
172173
}
173174

175+
func GetIngressClassDeleteProtectionEnabled(ic *networkingv1.IngressClass) bool {
176+
annotation := IngressClassDeleteProtectionEnabledAnnotation
177+
value, ok := ic.Annotations[annotation]
178+
179+
if !ok || strings.TrimSpace(value) == "" {
180+
return false
181+
}
182+
183+
result, err := strconv.ParseBool(value)
184+
if err != nil {
185+
klog.Errorf("Error parsing value %s for flag %s as boolean. Setting the default value as 'false'", value, annotation)
186+
return false
187+
}
188+
189+
return result
190+
}
191+
174192
func GetIngressProtocol(i *networkingv1.Ingress) string {
175193
protocol, ok := i.Annotations[IngressProtocolAnnotation]
176194
if !ok {

pkg/util/util_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,32 @@ func TestGetIngressClassNetworkSecurityGroupIds(t *testing.T) {
180180
Should(Equal([]string{"id1", "id2", "id3", "id4"}))
181181
}
182182

183+
func TestGetIngressClassDeleteProtectionEnabled(t *testing.T) {
184+
RegisterTestingT(t)
185+
186+
getIngressClassWithDeleteProtectionEnabledAnnotation := func(annotation string) *networkingv1.IngressClass {
187+
return &networkingv1.IngressClass{
188+
ObjectMeta: metav1.ObjectMeta{
189+
Annotations: map[string]string{IngressClassDeleteProtectionEnabledAnnotation: annotation},
190+
},
191+
}
192+
}
193+
194+
ingressClassWithNoAnnotation := &networkingv1.IngressClass{
195+
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
196+
}
197+
ingressClassWithEmptyAnnotation := getIngressClassWithDeleteProtectionEnabledAnnotation("")
198+
ingressClassWithProtectionEnabled := getIngressClassWithDeleteProtectionEnabledAnnotation("true")
199+
ingressClassWithProtectionDisabled := getIngressClassWithDeleteProtectionEnabledAnnotation("false")
200+
ingressClassWithWrongAnnotation := getIngressClassWithDeleteProtectionEnabledAnnotation("n/a")
201+
202+
Expect(GetIngressClassDeleteProtectionEnabled(ingressClassWithNoAnnotation)).Should(BeFalse())
203+
Expect(GetIngressClassDeleteProtectionEnabled(ingressClassWithEmptyAnnotation)).Should(BeFalse())
204+
Expect(GetIngressClassDeleteProtectionEnabled(ingressClassWithProtectionEnabled)).Should(BeTrue())
205+
Expect(GetIngressClassDeleteProtectionEnabled(ingressClassWithProtectionDisabled)).Should(BeFalse())
206+
Expect(GetIngressClassDeleteProtectionEnabled(ingressClassWithWrongAnnotation)).Should(BeFalse())
207+
}
208+
183209
func TestGetIngressClassLoadBalancerId(t *testing.T) {
184210
RegisterTestingT(t)
185211
lbId := "lbId"

0 commit comments

Comments
 (0)