• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Azure / aks-app-routing-operator / 9455033482

10 Jun 2024 08:22PM UTC coverage: 78.693% (-0.5%) from 79.15%
9455033482

Pull #201

github

web-flow
Merge 106a17649 into 8ca6390b9
Pull Request #201: adding custom-http-errors field to crd

12 of 36 new or added lines in 3 files covered. (33.33%)

1 existing line in 1 file now uncovered.

3010 of 3825 relevant lines covered (78.69%)

13.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.56
/pkg/controller/nginxingress/nginx_ingress_controller.go
1
package nginxingress
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "github.com/Azure/aks-app-routing-operator/pkg/controller/keyvault"
8
        "net/url"
9
        "time"
10

11
        approutingv1alpha1 "github.com/Azure/aks-app-routing-operator/api/v1alpha1"
12
        "github.com/Azure/aks-app-routing-operator/pkg/config"
13
        "github.com/Azure/aks-app-routing-operator/pkg/controller/controllername"
14
        "github.com/Azure/aks-app-routing-operator/pkg/controller/metrics"
15
        "github.com/Azure/aks-app-routing-operator/pkg/manifests"
16
        "github.com/Azure/aks-app-routing-operator/pkg/util"
17
        appsv1 "k8s.io/api/apps/v1"
18
        corev1 "k8s.io/api/core/v1"
19
        netv1 "k8s.io/api/networking/v1"
20
        apierrors "k8s.io/apimachinery/pkg/api/errors"
21
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23
        "k8s.io/client-go/tools/record"
24
        "k8s.io/utils/keymutex"
25
        ctrl "sigs.k8s.io/controller-runtime"
26
        "sigs.k8s.io/controller-runtime/pkg/client"
27
        "sigs.k8s.io/controller-runtime/pkg/log"
28
)
29

30
const (
31
        controllerClassMaxLen = 250
32
)
33

34
const (
35
        defaultMinReplicas = 2
36
        defaultMaxReplicas = 100
37
)
38

39
const (
40
        rapidTargetCPUUtilization    = 55
41
        balancedTargetCPUUtilization = 70
42
        steadyTargetCPUUtilization   = 80
43

44
        defaultTargetCPUUtilization = balancedTargetCPUUtilization
45
)
46

47
var (
48
        icCollisionErr   = errors.New("collision on the IngressClass")
49
        maxCollisionsErr = errors.New("max collisions reached")
50
)
51

52
var (
53
        nginxIngressControllerReconcilerName = controllername.New("nginx", "ingress", "controller", "reconciler")
54

55
        // collisionCountMu is used to prevent multiple nginxIngressController resources from determining their collisionCount at the same time. We use
56
        // a hashed key mutex because collisions can only occur when the nginxIngressController resources have the same spec.ControllerNamePrefix field.
57
        // This is the field used to key into this mutex. Using this mutex prevents a race condition of multiple nginxIngressController resources attempting
58
        // to determine their collisionCount at the same time then both attempting to create and take ownership of the same resources.
59
        collisionCountMu = keymutex.NewHashed(6) // 6 is the number of "buckets". It's not too big, not too small
60
)
61

62
// nginxIngressControllerReconciler reconciles a NginxIngressController object
63
type nginxIngressControllerReconciler struct {
64
        client                    client.Client
65
        conf                      *config.Config
66
        events                    record.EventRecorder
67
        defaultNicControllerClass string
68
}
69

70
// NewReconciler sets up the controller with the Manager.
71
func NewReconciler(conf *config.Config, mgr ctrl.Manager, defaultIngressClassControllerClass string) error {
×
72
        metrics.InitControllerMetrics(nginxIngressControllerReconcilerName)
×
73

×
74
        reconciler := &nginxIngressControllerReconciler{
×
75
                client:                    mgr.GetClient(),
×
76
                conf:                      conf,
×
77
                events:                    mgr.GetEventRecorderFor("aks-app-routing-operator"),
×
78
                defaultNicControllerClass: defaultIngressClassControllerClass,
×
79
        }
×
80

×
81
        if err := nginxIngressControllerReconcilerName.AddToController(
×
82
                ctrl.NewControllerManagedBy(mgr).
×
83
                        For(&approutingv1alpha1.NginxIngressController{}).
×
84
                        Owns(&appsv1.Deployment{}),
×
85
                mgr.GetLogger(),
×
86
        ).Complete(reconciler); err != nil {
×
87
                return err
×
88
        }
×
89

90
        return nil
×
91
}
92

93
func (n *nginxIngressControllerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) {
×
94
        start := time.Now()
×
95
        lgr := log.FromContext(ctx, "nginxIngressController", req.NamespacedName)
×
96
        ctx = log.IntoContext(ctx, lgr)
×
97
        lgr.Info("reconciling NginxIngressController")
×
98
        defer lgr.Info("finished reconciling resource", "latencySec", time.Since(start).String())
×
99

×
100
        defer func() {
×
101
                metrics.HandleControllerReconcileMetrics(nginxIngressControllerReconcilerName, res, err)
×
102
        }()
×
103

104
        var nginxIngressController approutingv1alpha1.NginxIngressController
×
105
        if err := n.client.Get(ctx, req.NamespacedName, &nginxIngressController); err != nil {
×
106
                if apierrors.IsNotFound(err) { // object was deleted
×
107
                        lgr.Info("NginxIngressController not found")
×
108
                        return ctrl.Result{}, nil
×
109
                }
×
110

111
                lgr.Error(err, "unable to fetch NginxIngressController")
×
112
                return ctrl.Result{}, err
×
113
        }
114
        lgr = lgr.WithValues("generation", nginxIngressController.Generation)
×
115
        ctx = log.IntoContext(ctx, lgr)
×
116

×
117
        var managedRes []approutingv1alpha1.ManagedObjectReference = nil
×
118
        var controllerDeployment *appsv1.Deployment = nil
×
119
        var ingressClass *netv1.IngressClass = nil
×
120

×
121
        lockKey := nginxIngressController.Spec.ControllerNamePrefix
×
122
        collisionCountMu.LockKey(lockKey)
×
123
        defer collisionCountMu.UnlockKey(lockKey)
×
124

×
125
        lgr.Info("determining collision count")
×
126
        startingCollisionCount := nginxIngressController.Status.CollisionCount
×
127
        newCollisionCount, collisionCountErr := n.GetCollisionCount(ctx, &nginxIngressController)
×
128
        if collisionCountErr == nil && newCollisionCount != startingCollisionCount {
×
129
                nginxIngressController.Status.CollisionCount = newCollisionCount
×
130
                if err := n.client.Status().Update(ctx, &nginxIngressController); err != nil {
×
131
                        lgr.Error(err, "unable to update collision count status")
×
132
                        return ctrl.Result{}, fmt.Errorf("updating status: %w", err)
×
133
                }
×
134

135
                return ctrl.Result{Requeue: true}, nil
×
136
        }
137
        defer func() { // defer is before checking err so that we can update status even if there is an error
×
138
                lgr.Info("updating status")
×
139
                n.updateStatus(&nginxIngressController, controllerDeployment, ingressClass, managedRes, collisionCountErr)
×
140
                if statusErr := n.client.Status().Update(ctx, &nginxIngressController); statusErr != nil {
×
141
                        lgr.Error(statusErr, "unable to update NginxIngressController status")
×
142
                        if err == nil {
×
143
                                err = statusErr
×
144
                        }
×
145
                }
146
        }()
147
        if collisionCountErr != nil {
×
148
                if isUnreconcilableError(collisionCountErr) {
×
149
                        lgr.Info("unreconcilable collision count")
×
150
                        return ctrl.Result{RequeueAfter: time.Minute}, nil // requeue in case cx fixes the unreconcilable reason
×
151
                }
×
152

153
                lgr.Error(collisionCountErr, "unable to get determine collision count")
×
154
                return ctrl.Result{}, fmt.Errorf("determining collision count: %w", collisionCountErr)
×
155
        }
156

157
        lgr.Info("calculating managed resources")
×
158
        resources := n.ManagedResources(&nginxIngressController)
×
159
        if resources == nil {
×
160
                return ctrl.Result{}, fmt.Errorf("unable to get managed resources")
×
161
        }
×
162
        controllerDeployment = resources.Deployment
×
163
        ingressClass = resources.IngressClass
×
164

×
165
        lgr.Info("reconciling managed resources")
×
166
        managedRes, err = n.ReconcileResource(ctx, &nginxIngressController, resources)
×
167
        if err != nil {
×
168
                lgr.Error(err, "unable to reconcile resource")
×
169
                return ctrl.Result{}, fmt.Errorf("reconciling resource: %w", err)
×
170
        }
×
171
        if replicas := resources.Deployment.Spec.Replicas; replicas != nil {
×
172
                lgr.Info(fmt.Sprintf("nginx deployment targets %d replicas", *replicas), "replicas", *replicas)
×
173
        }
×
174

175
        return ctrl.Result{}, nil
×
176
}
177

178
// ReconcileResource reconciles the NginxIngressController resources returning a list of managed resources.
179
func (n *nginxIngressControllerReconciler) ReconcileResource(ctx context.Context, nic *approutingv1alpha1.NginxIngressController, res *manifests.NginxResources) ([]approutingv1alpha1.ManagedObjectReference, error) {
6✔
180
        if nic == nil {
7✔
181
                return nil, errors.New("nginxIngressController cannot be nil")
1✔
182
        }
1✔
183
        if res == nil {
6✔
184
                return nil, errors.New("resources cannot be nil")
1✔
185
        }
1✔
186

187
        lgr := log.FromContext(ctx)
4✔
188

4✔
189
        var managedResourceRefs []approutingv1alpha1.ManagedObjectReference
4✔
190
        for _, obj := range res.Objects() {
49✔
191
                // TODO: upsert works fine for now but we want to set annotations exactly on the nginx service, we should use an alternative strategy for that in the future
45✔
192

45✔
193
                if err := util.Upsert(ctx, n.client, obj); err != nil {
46✔
194
                        // publish an event so cx can see things like their policy is preventing us from creating a resource
1✔
195
                        n.events.Eventf(nic, corev1.EventTypeWarning, "EnsuringManagedResourcesFailed", "Failed to ensure managed resource %s %s/%s: %s", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetNamespace(), obj.GetName(), err.Error())
1✔
196

1✔
197
                        lgr.Error(err, "unable to upsert object", "name", obj.GetName(), "kind", obj.GetObjectKind().GroupVersionKind().Kind, "namespace", obj.GetNamespace())
1✔
198
                        return nil, fmt.Errorf("upserting object: %w", err)
1✔
199
                }
1✔
200

201
                if manifests.HasTopLevelLabels(obj.GetLabels()) {
84✔
202
                        managedResourceRefs = append(managedResourceRefs, approutingv1alpha1.ManagedObjectReference{
40✔
203
                                Name:      obj.GetName(),
40✔
204
                                Namespace: obj.GetNamespace(),
40✔
205
                                Kind:      obj.GetObjectKind().GroupVersionKind().Kind,
40✔
206
                                APIGroup:  obj.GetObjectKind().GroupVersionKind().Group,
40✔
207
                        })
40✔
208
                }
40✔
209
        }
210

211
        return managedResourceRefs, nil
3✔
212
}
213

214
func (n *nginxIngressControllerReconciler) ManagedResources(nic *approutingv1alpha1.NginxIngressController) *manifests.NginxResources {
40✔
215
        if nic == nil {
41✔
216
                return nil
1✔
217
        }
1✔
218

219
        nginxIngressCfg := ToNginxIngressConfig(nic, n.defaultNicControllerClass)
39✔
220
        if nginxIngressCfg == nil {
39✔
221
                return nil
×
222
        }
×
223

224
        res := manifests.GetNginxResources(n.conf, nginxIngressCfg)
39✔
225
        owner := manifests.GetOwnerRefs(nic, true)
39✔
226
        for _, obj := range res.Objects() {
507✔
227
                if manifests.HasTopLevelLabels(obj.GetLabels()) {
897✔
228
                        obj.SetOwnerReferences(owner)
429✔
229
                }
429✔
230
        }
231

232
        return res
39✔
233
}
234

235
func (n *nginxIngressControllerReconciler) GetCollisionCount(ctx context.Context, nic *approutingv1alpha1.NginxIngressController) (int32, error) {
7✔
236
        lgr := log.FromContext(ctx)
7✔
237

7✔
238
        // it's not this fn's job to overwrite the collision count, so we revert any changes we make
7✔
239
        startingCollisionCount := nic.Status.CollisionCount
7✔
240
        defer func() {
14✔
241
                nic.Status.CollisionCount = startingCollisionCount
7✔
242
        }()
7✔
243

244
        for {
29✔
245
                lgr = lgr.WithValues("collisionCount", nic.Status.CollisionCount)
22✔
246

22✔
247
                if nic.Status.CollisionCount == approutingv1alpha1.MaxCollisions {
23✔
248
                        lgr.Info("max collisions reached")
1✔
249
                        return 0, maxCollisionsErr
1✔
250
                }
1✔
251

252
                collision, err := n.collides(ctx, nic)
21✔
253
                if err != nil {
21✔
254
                        lgr.Error(err, "unable to determine collision")
×
255
                        return 0, fmt.Errorf("determining collision: %w", err)
×
256
                }
×
257

258
                if collision == collisionIngressClass {
22✔
259
                        // rare since our webhook guards against it, but it's possible
1✔
260
                        lgr.Info("ingress class collision")
1✔
261
                        return 0, icCollisionErr
1✔
262
                }
1✔
263

264
                if collision == collisionNone {
25✔
265
                        break
5✔
266
                }
267

268
                lgr.Info("reconcilable collision detected, incrementing")
15✔
269
                nic.Status.CollisionCount++
15✔
270
        }
271

272
        return nic.Status.CollisionCount, nil
5✔
273
}
274

275
func (n *nginxIngressControllerReconciler) collides(ctx context.Context, nic *approutingv1alpha1.NginxIngressController) (collision, error) {
26✔
276
        lgr := log.FromContext(ctx)
26✔
277

26✔
278
        // ignore any collisions on default nic for migration story. Need to acquire ownership of old app routing resources
26✔
279
        if IsDefaultNic(nic) {
27✔
280
                return collisionNone, nil
1✔
281
        }
1✔
282

283
        res := n.ManagedResources(nic)
25✔
284
        if res == nil {
25✔
285
                return collisionNone, fmt.Errorf("getting managed objects")
×
286
        }
×
287

288
        for _, obj := range res.Objects() {
161✔
289
                lgr := lgr.WithValues("kind", obj.GetObjectKind().GroupVersionKind().Kind, "name", obj.GetName(), "namespace", obj.GetNamespace())
136✔
290

136✔
291
                // if we won't own the resource, we don't care about collisions.
136✔
292
                // this is most commonly used for namespaces since we shouldn't own
136✔
293
                // namespaces
136✔
294
                if !manifests.HasTopLevelLabels(obj.GetLabels()) {
161✔
295
                        lgr.Info("skipping collision check because we don't own the resource")
25✔
296
                        continue
25✔
297
                }
298

299
                u := &unstructured.Unstructured{}
111✔
300
                u.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())
111✔
301
                err := n.client.Get(ctx, client.ObjectKeyFromObject(obj), u)
111✔
302
                if err != nil {
204✔
303
                        if apierrors.IsNotFound(err) {
186✔
304
                                continue
93✔
305
                        }
306

307
                        return collisionNone, fmt.Errorf("getting object: %w", err)
×
308
                }
309

310
                if owner := util.FindOwnerKind(u.GetOwnerReferences(), nic.Kind); owner == nic.Name {
18✔
311
                        lgr.Info("the nginxIngressController owns this resource")
×
312
                        continue
×
313
                }
314

315
                lgr.Info("collision detected")
18✔
316
                if obj.GetObjectKind().GroupVersionKind().Kind == "IngressClass" {
20✔
317
                        lgr.Info("collision is with an IngressClass")
2✔
318
                        return collisionIngressClass, nil
2✔
319
                }
2✔
320

321
                return collisionOther, nil
16✔
322
        }
323

324
        lgr.Info("no collisions detected")
7✔
325
        return collisionNone, nil
7✔
326
}
327

328
// updateStatus updates the status of the NginxIngressController resource. If a nil controller Deployment or IngressClass is passed, the status is defaulted for those fields if they are not already set.
329
func (n *nginxIngressControllerReconciler) updateStatus(nic *approutingv1alpha1.NginxIngressController, controllerDeployment *appsv1.Deployment, ic *netv1.IngressClass, managedResourceRefs []approutingv1alpha1.ManagedObjectReference, err error) {
×
330
        n.updateStatusManagedResourceRefs(nic, managedResourceRefs)
×
331

×
332
        n.updateStatusIngressClass(nic, ic)
×
333

×
334
        // default conditions
×
335
        if controllerDeployment == nil || controllerDeployment.CreationTimestamp.IsZero() {
×
336
                n.updateStatusNilDeployment(nic)
×
337
        } else {
×
338
                for _, cond := range controllerDeployment.Status.Conditions {
×
339
                        switch cond.Type {
×
340
                        case appsv1.DeploymentProgressing:
×
341
                                n.updateStatusControllerProgressing(nic, cond)
×
342
                        case appsv1.DeploymentAvailable:
×
343
                                n.updateStatusControllerAvailable(nic, cond)
×
344
                        }
345
                }
346
        }
347

348
        n.updateStatusControllerReplicas(nic, controllerDeployment)
×
349
        n.updateStatusAvailable(nic)
×
350

×
351
        // error checking at end to take precedence over other conditions
×
352
        n.updateStatusFromError(nic, err)
×
353
}
354

355
func (n *nginxIngressControllerReconciler) updateStatusManagedResourceRefs(nic *approutingv1alpha1.NginxIngressController, managedResourceRefs []approutingv1alpha1.ManagedObjectReference) {
2✔
356
        if managedResourceRefs == nil {
3✔
357
                return
1✔
358
        }
1✔
359

360
        nic.Status.ManagedResourceRefs = managedResourceRefs
1✔
361
}
362

363
func (n *nginxIngressControllerReconciler) updateStatusIngressClass(nic *approutingv1alpha1.NginxIngressController, ic *netv1.IngressClass) {
3✔
364
        if ic == nil || ic.CreationTimestamp.IsZero() {
5✔
365
                nic.SetCondition(metav1.Condition{
2✔
366
                        Type:    approutingv1alpha1.ConditionTypeIngressClassReady,
2✔
367
                        Status:  metav1.ConditionUnknown,
2✔
368
                        Reason:  "IngressClassUnknown",
2✔
369
                        Message: "IngressClass is unknown",
2✔
370
                })
2✔
371
        } else {
3✔
372
                nic.SetCondition(metav1.Condition{
1✔
373
                        Type:    approutingv1alpha1.ConditionTypeIngressClassReady,
1✔
374
                        Status:  "True",
1✔
375
                        Reason:  "IngressClassIsReady",
1✔
376
                        Message: "Ingress Class is up-to-date ",
1✔
377
                })
1✔
378
        }
1✔
379
}
380

381
func (n *nginxIngressControllerReconciler) updateStatusNilDeployment(nic *approutingv1alpha1.NginxIngressController) {
1✔
382
        nic.SetCondition(metav1.Condition{
1✔
383
                Type:    approutingv1alpha1.ConditionTypeControllerAvailable,
1✔
384
                Status:  metav1.ConditionUnknown,
1✔
385
                Reason:  "ControllerDeploymentUnknown",
1✔
386
                Message: "Controller deployment is unknown",
1✔
387
        })
1✔
388
        nic.SetCondition(metav1.Condition{
1✔
389
                Type:    approutingv1alpha1.ConditionTypeProgressing,
1✔
390
                Status:  metav1.ConditionUnknown,
1✔
391
                Reason:  "ControllerDeploymentUnknown",
1✔
392
                Message: "Controller deployment is unknown",
1✔
393
        })
1✔
394
}
1✔
395

396
func (n *nginxIngressControllerReconciler) updateStatusControllerReplicas(nic *approutingv1alpha1.NginxIngressController, deployment *appsv1.Deployment) {
2✔
397
        if deployment == nil {
3✔
398
                return
1✔
399
        }
1✔
400

401
        nic.Status.ControllerReadyReplicas = deployment.Status.ReadyReplicas
1✔
402
        nic.Status.ControllerAvailableReplicas = deployment.Status.AvailableReplicas
1✔
403
        nic.Status.ControllerUnavailableReplicas = deployment.Status.UnavailableReplicas
1✔
404
        nic.Status.ControllerReplicas = deployment.Status.Replicas
1✔
405
}
406

407
func (n *nginxIngressControllerReconciler) updateStatusAvailable(nic *approutingv1alpha1.NginxIngressController) {
6✔
408
        controllerAvailable := nic.GetCondition(approutingv1alpha1.ConditionTypeControllerAvailable)
6✔
409
        icAvailable := nic.GetCondition(approutingv1alpha1.ConditionTypeIngressClassReady)
6✔
410
        if controllerAvailable != nil && icAvailable != nil && controllerAvailable.Status == metav1.ConditionTrue && icAvailable.Status == metav1.ConditionTrue {
7✔
411
                nic.SetCondition(metav1.Condition{
1✔
412
                        Type:    approutingv1alpha1.ConditionTypeAvailable,
1✔
413
                        Status:  metav1.ConditionTrue,
1✔
414
                        Reason:  "ControllerIsAvailable",
1✔
415
                        Message: "Controller Deployment has minimum availability and IngressClass is up-to-date",
1✔
416
                })
1✔
417
        } else {
6✔
418
                nic.SetCondition(metav1.Condition{
5✔
419
                        Type:    approutingv1alpha1.ConditionTypeAvailable,
5✔
420
                        Status:  metav1.ConditionFalse,
5✔
421
                        Reason:  "ControllerIsNotAvailable",
5✔
422
                        Message: "Controller Deployment does not have minimum availability or IngressClass is not up-to-date",
5✔
423
                })
5✔
424
        }
5✔
425
}
426

427
func (n *nginxIngressControllerReconciler) updateStatusFromError(nic *approutingv1alpha1.NginxIngressController, err error) {
4✔
428
        if errors.Is(err, icCollisionErr) {
5✔
429
                nic.SetCondition(metav1.Condition{
1✔
430
                        Type:    approutingv1alpha1.ConditionTypeProgressing,
1✔
431
                        Status:  metav1.ConditionFalse,
1✔
432
                        Reason:  "IngressClassCollision",
1✔
433
                        Message: "IngressClass already exists and is not owned by this controller",
1✔
434
                })
1✔
435
                n.events.Event(nic, corev1.EventTypeWarning, "IngressClassCollision", "IngressClass already exists and is not owned by this controller. Change the spec.IngressClassName to an unused IngressClass name in a new NginxIngressController.")
1✔
436
        }
1✔
437
        if errors.Is(err, maxCollisionsErr) {
5✔
438
                nic.SetCondition(metav1.Condition{
1✔
439
                        Type:    approutingv1alpha1.ConditionTypeProgressing,
1✔
440
                        Status:  metav1.ConditionFalse,
1✔
441
                        Reason:  "TooManyCollisions",
1✔
442
                        Message: "Too many collisions with existing resources",
1✔
443
                })
1✔
444
                n.events.Event(nic, corev1.EventTypeWarning, "TooManyCollisions", "Too many collisions with existing resources. Change the spec.ControllerNamePrefix to something more unique in a new NginxIngressController.")
1✔
445
        }
1✔
446
}
447

448
func (n *nginxIngressControllerReconciler) updateStatusControllerAvailable(nic *approutingv1alpha1.NginxIngressController, availableCondition appsv1.DeploymentCondition) {
4✔
449
        if availableCondition.Type != appsv1.DeploymentAvailable {
5✔
450
                return
1✔
451
        }
1✔
452

453
        var cond metav1.Condition
3✔
454
        switch availableCondition.Status {
3✔
455
        case corev1.ConditionTrue:
1✔
456
                cond = metav1.Condition{
1✔
457
                        Type:    approutingv1alpha1.ConditionTypeControllerAvailable,
1✔
458
                        Status:  metav1.ConditionTrue,
1✔
459
                        Reason:  "ControllerDeploymentAvailable",
1✔
460
                        Message: "Controller Deployment is available",
1✔
461
                }
1✔
462
        case corev1.ConditionFalse:
1✔
463
                cond = metav1.Condition{
1✔
464
                        Type:    approutingv1alpha1.ConditionTypeControllerAvailable,
1✔
465
                        Status:  metav1.ConditionFalse,
1✔
466
                        Reason:  "ControllerDeploymentNotAvailable",
1✔
467
                        Message: "Controller Deployment is not available",
1✔
468
                }
1✔
469
        default:
1✔
470
                cond = metav1.Condition{
1✔
471
                        Type:    approutingv1alpha1.ConditionTypeControllerAvailable,
1✔
472
                        Status:  metav1.ConditionUnknown,
1✔
473
                        Reason:  "ControllerDeploymentUnknown",
1✔
474
                        Message: "Controller Deployment is in an unknown state",
1✔
475
                }
1✔
476
        }
477

478
        nic.SetCondition(cond)
3✔
479
}
480

481
func (n *nginxIngressControllerReconciler) updateStatusControllerProgressing(nic *approutingv1alpha1.NginxIngressController, progressingCondition appsv1.DeploymentCondition) {
4✔
482
        if progressingCondition.Type != appsv1.DeploymentProgressing {
5✔
483
                return
1✔
484
        }
1✔
485

486
        var cond metav1.Condition
3✔
487
        switch progressingCondition.Status {
3✔
488
        case corev1.ConditionTrue:
1✔
489
                cond = metav1.Condition{
1✔
490
                        Type:    approutingv1alpha1.ConditionTypeProgressing,
1✔
491
                        Status:  metav1.ConditionTrue,
1✔
492
                        Reason:  "ControllerDeploymentProgressing",
1✔
493
                        Message: "Controller Deployment has successfully progressed",
1✔
494
                }
1✔
495
        case corev1.ConditionFalse:
1✔
496
                cond = metav1.Condition{
1✔
497
                        Type:    approutingv1alpha1.ConditionTypeProgressing,
1✔
498
                        Status:  metav1.ConditionFalse,
1✔
499
                        Reason:  "ControllerDeploymentNotProgressing",
1✔
500
                        Message: "Controller Deployment has failed to progress",
1✔
501
                }
1✔
502
        default:
1✔
503
                cond = metav1.Condition{
1✔
504
                        Type:    approutingv1alpha1.ConditionTypeProgressing,
1✔
505
                        Status:  metav1.ConditionUnknown,
1✔
506
                        Reason:  "ControllerDeploymentProgressingUnknown",
1✔
507
                        Message: "Controller Deployment progress is unknown",
1✔
508
                }
1✔
509
        }
510

511
        nic.SetCondition(cond)
3✔
512
}
513

514
func isUnreconcilableError(err error) bool {
5✔
515
        return errors.Is(err, icCollisionErr) || errors.Is(err, maxCollisionsErr)
5✔
516
}
5✔
517

518
func ToNginxIngressConfig(nic *approutingv1alpha1.NginxIngressController, defaultNicControllerClass string) *manifests.NginxIngressConfig {
58✔
519
        if nic == nil {
58✔
520
                return nil
×
521
        }
×
522

523
        cc := "approuting.kubernetes.azure.com/" + url.PathEscape(nic.Name)
58✔
524
        if len(cc) > controllerClassMaxLen {
59✔
525
                cc = cc[:controllerClassMaxLen]
1✔
526
        }
1✔
527

528
        resourceName := fmt.Sprintf("%s-%d", nic.Spec.ControllerNamePrefix, nic.Status.CollisionCount)
58✔
529

58✔
530
        if IsDefaultNic(nic) {
65✔
531
                cc = defaultNicControllerClass
7✔
532
                resourceName = DefaultNicResourceName
7✔
533
        }
7✔
534

535
        scaling := nic.Spec.Scaling
58✔
536
        var minReplicas int32 = defaultMinReplicas
58✔
537
        if scaling != nil && scaling.MinReplicas != nil {
61✔
538
                minReplicas = *scaling.MinReplicas
3✔
539
        }
3✔
540
        var maxReplicas int32 = defaultMaxReplicas
58✔
541
        if scaling != nil && scaling.MaxReplicas != nil {
61✔
542
                maxReplicas = *scaling.MaxReplicas
3✔
543
        }
3✔
544

545
        // we use CEL validation on crd to enforce min <= max if it's defined. There's an edge case where they define max to 1 but don't define min which defaults to 2. The opposite is true too
546
        if minReplicas > maxReplicas {
60✔
547
                if scaling == nil || scaling.MinReplicas == nil {
3✔
548
                        minReplicas = maxReplicas
1✔
549
                } else {
2✔
550
                        maxReplicas = minReplicas
1✔
551
                }
1✔
552
        }
553

554
        nginxIng := &manifests.NginxIngressConfig{
58✔
555
                ControllerClass: cc,
58✔
556
                ResourceName:    resourceName,
58✔
557
                IcName:          nic.Spec.IngressClassName,
58✔
558
                ServiceConfig: &manifests.ServiceConfig{
58✔
559
                        Annotations: nic.Spec.LoadBalancerAnnotations,
58✔
560
                },
58✔
561
                MinReplicas:                    minReplicas,
58✔
562
                MaxReplicas:                    maxReplicas,
58✔
563
                TargetCPUUtilizationPercentage: getTargetCPUUtilizationPercentage(nic),
58✔
564
        }
58✔
565

58✔
566
        if cert := nic.Spec.DefaultSSLCertificate; cert != nil {
64✔
567
                if cert.Secret != nil && cert.Secret.Name != "" && cert.Secret.Namespace != "" {
9✔
568
                        nginxIng.DefaultSSLCertificate = cert.Secret.Namespace + "/" + cert.Secret.Name
3✔
569
                }
3✔
570

571
                if cert.Secret == nil && cert.KeyVaultURI != nil {
7✔
572
                        nginxIng.DefaultSSLCertificate = config.DefaultNs + "/" + keyvault.DefaultNginxCertName(nic)
1✔
573
                }
1✔
574

575
                if cert.ForceSSLRedirect {
7✔
576
                        nginxIng.ForceSSLRedirect = true
1✔
577
                }
1✔
578
        }
579

580
        if nic.Spec.DefaultBackendService != nil && nic.Spec.DefaultBackendService.Name != "" && nic.Spec.DefaultBackendService.Namespace != "" {
59✔
581
                nginxIng.DefaultBackendService = nic.Spec.DefaultBackendService.Namespace + "/" + nic.Spec.DefaultBackendService.Name
1✔
582
        }
1✔
583

584
        if nic.Spec.CustomHTTPErrors != nil {
58✔
NEW
585
                errStr := ""
×
NEW
586
                for i, errCode := range nic.Spec.CustomHTTPErrors {
×
NEW
587
                        errStr += string(rune(errCode))
×
NEW
588
                        if i+1 < len(nic.Spec.CustomHTTPErrors) {
×
NEW
589
                                errStr += ","
×
NEW
590
                        }
×
591
                }
592
        }
593

594
        return nginxIng
58✔
595
}
596

597
func getTargetCPUUtilizationPercentage(nic *approutingv1alpha1.NginxIngressController) int32 {
64✔
598
        if nic == nil {
65✔
599
                return defaultTargetCPUUtilization
1✔
600
        }
1✔
601

602
        scaling := nic.Spec.Scaling
63✔
603
        if scaling == nil {
113✔
604
                return defaultTargetCPUUtilization
50✔
605
        }
50✔
606

607
        thresh := scaling.Threshold
13✔
608
        if thresh == nil {
20✔
609
                return defaultTargetCPUUtilization
7✔
610
        }
7✔
611

612
        switch *thresh {
6✔
613
        case approutingv1alpha1.RapidThreshold:
2✔
614
                return rapidTargetCPUUtilization
2✔
615
        case approutingv1alpha1.BalancedThreshold:
2✔
616
                return balancedTargetCPUUtilization
2✔
617
        case approutingv1alpha1.SteadyThreshold:
2✔
618
                return steadyTargetCPUUtilization
2✔
619

620
        default:
×
621
                return defaultTargetCPUUtilization
×
622
        }
623
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc