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

zalando-incubator / stackset-controller / 8647751539

11 Apr 2024 01:42PM UTC coverage: 50.527% (-1.3%) from 51.844%
8647751539

Pull #620

github

gargravarr
Remove comment.

Signed-off-by: Rodrigo Reis <rodrigo.gargravarr@gmail.com>
Pull Request #620: Remove central ingress/routegroup logic

17 of 57 new or added lines in 5 files covered. (29.82%)

134 existing lines in 3 files now uncovered.

2828 of 5597 relevant lines covered (50.53%)

0.57 hits per line

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

35.59
/controller/stackset.go
1
package controller
2

3
import (
4
        "context"
5
        "fmt"
6
        "net/http"
7
        "runtime/debug"
8
        "strings"
9
        "sync"
10
        "time"
11

12
        "github.com/google/go-cmp/cmp"
13
        "github.com/google/go-cmp/cmp/cmpopts"
14
        "github.com/heptiolabs/healthcheck"
15
        "github.com/prometheus/client_golang/prometheus"
16
        log "github.com/sirupsen/logrus"
17
        rgv1 "github.com/szuecs/routegroup-client/apis/zalando.org/v1"
18
        zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
19
        "github.com/zalando-incubator/stackset-controller/pkg/clientset"
20
        "github.com/zalando-incubator/stackset-controller/pkg/core"
21
        "github.com/zalando-incubator/stackset-controller/pkg/recorder"
22
        "golang.org/x/sync/errgroup"
23
        v1 "k8s.io/api/core/v1"
24
        networking "k8s.io/api/networking/v1"
25
        "k8s.io/apimachinery/pkg/api/equality"
26
        "k8s.io/apimachinery/pkg/api/errors"
27
        "k8s.io/apimachinery/pkg/api/resource"
28
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29
        "k8s.io/apimachinery/pkg/fields"
30
        "k8s.io/apimachinery/pkg/runtime"
31
        "k8s.io/apimachinery/pkg/types"
32
        "k8s.io/client-go/tools/cache"
33
        kube_record "k8s.io/client-go/tools/record"
34
)
35

36
const (
37
        PrescaleStacksAnnotationKey               = "alpha.stackset-controller.zalando.org/prescale-stacks"
38
        ResetHPAMinReplicasDelayAnnotationKey     = "alpha.stackset-controller.zalando.org/reset-hpa-min-replicas-delay"
39
        StacksetControllerControllerAnnotationKey = "stackset-controller.zalando.org/controller"
40
        ControllerLastUpdatedAnnotationKey        = "stackset-controller.zalando.org/updated-timestamp"
41

42
        reasonFailedManageStackSet = "FailedManageStackSet"
43

44
        defaultResetMinReplicasDelay = 10 * time.Minute
45
)
46

47
var configurationResourceNameError = "ConfigurationResource name must be prefixed by Stack name. ConfigurationResource: %s, Stack: %s"
48

49
// StackSetController is the main controller. It watches for changes to
50
// stackset resources and starts and maintains other controllers per
51
// stackset resource.
52
type StackSetController struct {
53
        logger                      *log.Entry
54
        client                      clientset.Interface
55
        namespace                   string
56
        syncIngressAnnotations      []string
57
        controllerID                string
58
        backendWeightsAnnotationKey string
59
        clusterDomains              []string
60
        interval                    time.Duration
61
        stacksetEvents              chan stacksetEvent
62
        stacksetStore               map[types.UID]zv1.StackSet
63
        recorder                    kube_record.EventRecorder
64
        metricsReporter             *core.MetricsReporter
65
        HealthReporter              healthcheck.Handler
66
        routeGroupSupportEnabled    bool
67
        now                         func() string
68
        reconcileWorkers            int
69
        configMapSupportEnabled     bool
70
        secretSupportEnabled        bool
71
        sync.Mutex
72
}
73

74
type stacksetEvent struct {
75
        Deleted  bool
76
        StackSet *zv1.StackSet
77
}
78

79
// eventedError wraps an error that was already exposed as an event to the user
80
type eventedError struct {
81
        err error
82
}
83

84
func (ee *eventedError) Error() string {
×
85
        return ee.err.Error()
×
86
}
×
87

88
func now() string {
×
89
        return time.Now().Format(time.RFC3339)
×
90
}
×
91

92
// NewStackSetController initializes a new StackSetController.
93
func NewStackSetController(
94
        client clientset.Interface,
95
        namespace string,
96
        controllerID string,
97
        parallelWork int,
98
        backendWeightsAnnotationKey string,
99
        clusterDomains []string,
100
        registry prometheus.Registerer,
101
        interval time.Duration,
102
        routeGroupSupportEnabled bool,
103
        syncIngressAnnotations []string,
104
        configMapSupportEnabled bool,
105
        secretSupportEnabled bool,
106
) (*StackSetController, error) {
1✔
107
        metricsReporter, err := core.NewMetricsReporter(registry)
1✔
108
        if err != nil {
1✔
109
                return nil, err
×
110
        }
×
111

112
        return &StackSetController{
1✔
113
                logger:                      log.WithFields(log.Fields{"controller": "stackset"}),
1✔
114
                client:                      client,
1✔
115
                namespace:                   namespace,
1✔
116
                controllerID:                controllerID,
1✔
117
                backendWeightsAnnotationKey: backendWeightsAnnotationKey,
1✔
118
                clusterDomains:              clusterDomains,
1✔
119
                interval:                    interval,
1✔
120
                stacksetEvents:              make(chan stacksetEvent, 1),
1✔
121
                stacksetStore:               make(map[types.UID]zv1.StackSet),
1✔
122
                recorder:                    recorder.CreateEventRecorder(client),
1✔
123
                metricsReporter:             metricsReporter,
1✔
124
                HealthReporter:              healthcheck.NewHandler(),
1✔
125
                routeGroupSupportEnabled:    routeGroupSupportEnabled,
1✔
126
                syncIngressAnnotations:      syncIngressAnnotations,
1✔
127
                configMapSupportEnabled:     configMapSupportEnabled,
1✔
128
                secretSupportEnabled:        secretSupportEnabled,
1✔
129
                now:                         now,
1✔
130
                reconcileWorkers:            parallelWork,
1✔
131
        }, nil
1✔
132
}
133

134
func (c *StackSetController) stacksetLogger(ssc *core.StackSetContainer) *log.Entry {
×
135
        return c.logger.WithFields(map[string]interface{}{
×
136
                "namespace": ssc.StackSet.Namespace,
×
137
                "stackset":  ssc.StackSet.Name,
×
138
        })
×
139
}
×
140

141
func (c *StackSetController) stackLogger(ssc *core.StackSetContainer, sc *core.StackContainer) *log.Entry {
×
142
        return c.logger.WithFields(map[string]interface{}{
×
143
                "namespace": ssc.StackSet.Namespace,
×
144
                "stackset":  ssc.StackSet.Name,
×
145
                "stack":     sc.Name(),
×
146
        })
×
147
}
×
148

149
// Run runs the main loop of the StackSetController. Before the loops it
150
// sets up a watcher to watch StackSet resources. The watch will send
151
// changes over a channel which is polled from the main loop.
152
func (c *StackSetController) Run(ctx context.Context) error {
×
153
        var nextCheck time.Time
×
154

×
155
        // We're not alive if nextCheck is too far in the past
×
156
        c.HealthReporter.AddLivenessCheck("nextCheck", func() error {
×
157
                if time.Since(nextCheck) > 5*c.interval {
×
158
                        return fmt.Errorf("nextCheck too old")
×
159
                }
×
160
                return nil
×
161
        })
162

163
        err := c.startWatch(ctx)
×
164
        if err != nil {
×
165
                return err
×
166
        }
×
167

168
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
169

×
170
        nextCheck = time.Now().Add(-c.interval)
×
171

×
172
        for {
×
173
                select {
×
174
                case <-time.After(time.Until(nextCheck)):
×
175

×
176
                        nextCheck = time.Now().Add(c.interval)
×
177

×
178
                        stackSetContainers, err := c.collectResources(ctx)
×
179
                        if err != nil {
×
180
                                c.logger.Errorf("Failed to collect resources: %v", err)
×
181
                                continue
×
182
                        }
183

184
                        var reconcileGroup errgroup.Group
×
185
                        reconcileGroup.SetLimit(c.reconcileWorkers)
×
186
                        for stackset, container := range stackSetContainers {
×
187
                                container := container
×
188
                                stackset := stackset
×
189

×
190
                                reconcileGroup.Go(func() error {
×
191
                                        if _, ok := c.stacksetStore[stackset]; ok {
×
192
                                                err := c.ReconcileStackSet(ctx, container)
×
193
                                                if err != nil {
×
194
                                                        c.stacksetLogger(container).Errorf("unable to reconcile a stackset: %v", err)
×
195
                                                        return c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
196
                                                }
×
197
                                        }
198
                                        return nil
×
199
                                })
200
                        }
201

202
                        err = reconcileGroup.Wait()
×
203
                        if err != nil {
×
204
                                c.logger.Errorf("Failed waiting for reconcilers: %v", err)
×
205
                        }
×
206
                        err = c.metricsReporter.Report(stackSetContainers)
×
207
                        if err != nil {
×
208
                                c.logger.Errorf("Failed reporting metrics: %v", err)
×
209
                        }
×
210
                case e := <-c.stacksetEvents:
×
211
                        stackset := *e.StackSet
×
212
                        fixupStackSetTypeMeta(&stackset)
×
213

×
214
                        // update/delete existing entry
×
215
                        if _, ok := c.stacksetStore[stackset.UID]; ok {
×
216
                                if e.Deleted || !c.hasOwnership(&stackset) {
×
217
                                        delete(c.stacksetStore, stackset.UID)
×
218
                                        continue
×
219
                                }
220

221
                                // update stackset entry
222
                                c.stacksetStore[stackset.UID] = stackset
×
223
                                continue
×
224
                        }
225

226
                        // check if stackset should be managed by the controller
227
                        if !c.hasOwnership(&stackset) {
×
228
                                continue
×
229
                        }
230

231
                        c.logger.Infof("Adding entry for StackSet %s/%s", stackset.Namespace, stackset.Name)
×
232
                        c.stacksetStore[stackset.UID] = stackset
×
233
                case <-ctx.Done():
×
234
                        c.logger.Info("Terminating main controller loop.")
×
235
                        return nil
×
236
                }
237
        }
238
}
239

240
// collectResources collects resources for all stacksets at once and stores them per StackSet/Stack so that we don't
241
// overload the API requests with unnecessary requests
242
func (c *StackSetController) collectResources(ctx context.Context) (map[types.UID]*core.StackSetContainer, error) {
1✔
243
        stacksets := make(map[types.UID]*core.StackSetContainer, len(c.stacksetStore))
1✔
244
        for uid, stackset := range c.stacksetStore {
2✔
245
                stackset := stackset
1✔
246

1✔
247
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
248

1✔
249
                // use prescaling logic if enabled with an annotation
1✔
250
                if _, ok := stackset.Annotations[PrescaleStacksAnnotationKey]; ok {
2✔
251
                        resetDelay := defaultResetMinReplicasDelay
1✔
252
                        if resetDelayValue, ok := getResetMinReplicasDelay(stackset.Annotations); ok {
2✔
253
                                resetDelay = resetDelayValue
1✔
254
                        }
1✔
255
                        reconciler = &core.PrescalingTrafficReconciler{
1✔
256
                                ResetHPAMinReplicasTimeout: resetDelay,
1✔
257
                        }
1✔
258
                }
259

260
                stacksetContainer := core.NewContainer(
1✔
261
                        &stackset,
1✔
262
                        reconciler,
1✔
263
                        c.backendWeightsAnnotationKey,
1✔
264
                        c.clusterDomains,
1✔
265
                        c.syncIngressAnnotations,
1✔
266
                )
1✔
267
                stacksets[uid] = stacksetContainer
1✔
268
        }
269

270
        err := c.collectStacks(ctx, stacksets)
1✔
271
        if err != nil {
1✔
272
                return nil, err
×
273
        }
×
274

275
        err = c.collectIngresses(ctx, stacksets)
1✔
276
        if err != nil {
1✔
277
                return nil, err
×
278
        }
×
279

280
        if c.routeGroupSupportEnabled {
2✔
281
                err = c.collectRouteGroups(ctx, stacksets)
1✔
282
                if err != nil {
1✔
283
                        return nil, err
×
284
                }
×
285
        }
286

287
        err = c.collectDeployments(ctx, stacksets)
1✔
288
        if err != nil {
1✔
289
                return nil, err
×
290
        }
×
291

292
        err = c.collectServices(ctx, stacksets)
1✔
293
        if err != nil {
1✔
294
                return nil, err
×
295
        }
×
296

297
        err = c.collectHPAs(ctx, stacksets)
1✔
298
        if err != nil {
1✔
299
                return nil, err
×
300
        }
×
301

302
        if c.configMapSupportEnabled {
2✔
303
                err = c.collectConfigMaps(ctx, stacksets)
1✔
304
                if err != nil {
1✔
305
                        return nil, err
×
306
                }
×
307
        }
308

309
        if c.secretSupportEnabled {
2✔
310
                err = c.collectSecrets(ctx, stacksets)
1✔
311
                if err != nil {
1✔
312
                        return nil, err
×
313
                }
×
314
        }
315

316
        return stacksets, nil
1✔
317
}
318

319
func (c *StackSetController) collectIngresses(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
320
        ingresses, err := c.client.NetworkingV1().Ingresses(c.namespace).List(ctx, metav1.ListOptions{})
1✔
321

1✔
322
        if err != nil {
1✔
323
                return fmt.Errorf("failed to list Ingresses: %v", err)
×
324
        }
×
325

326
        for _, i := range ingresses.Items {
2✔
327
                ingress := i
1✔
328
                if uid, ok := getOwnerUID(ingress.ObjectMeta); ok {
2✔
329
                        // stackset ingress
1✔
330
                        if s, ok := stacksets[uid]; ok {
2✔
331
                                s.Ingress = &ingress
1✔
332
                                continue
1✔
333
                        }
334

335
                        // stack ingress
336
                        for _, stackset := range stacksets {
2✔
337
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
338
                                        if strings.HasSuffix(
1✔
339
                                                ingress.ObjectMeta.Name,
1✔
340
                                                core.SegmentSuffix,
1✔
341
                                        ) {
2✔
342
                                                // Traffic Segment
1✔
343
                                                s.Resources.IngressSegment = &ingress
1✔
344
                                        } else {
2✔
345
                                                s.Resources.Ingress = &ingress
1✔
346
                                        }
1✔
347
                                        break
1✔
348
                                }
349
                        }
350
                }
351
        }
352
        return nil
1✔
353
}
354

355
func (c *StackSetController) collectRouteGroups(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
356
        rgs, err := c.client.RouteGroupV1().RouteGroups(c.namespace).List(
1✔
357
                ctx,
1✔
358
                metav1.ListOptions{},
1✔
359
        )
1✔
360
        if err != nil {
1✔
361
                return fmt.Errorf("failed to list RouteGroups: %v", err)
×
362
        }
×
363

364
        for _, rg := range rgs.Items {
2✔
365
                routegroup := rg
1✔
366
                if uid, ok := getOwnerUID(routegroup.ObjectMeta); ok {
2✔
367
                        // stackset routegroups
1✔
368
                        if s, ok := stacksets[uid]; ok {
2✔
369
                                s.RouteGroup = &routegroup
1✔
370
                                continue
1✔
371
                        }
372

373
                        // stack routegroups
374
                        for _, stackset := range stacksets {
2✔
375
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
376
                                        if strings.HasSuffix(
1✔
377
                                                routegroup.ObjectMeta.Name,
1✔
378
                                                core.SegmentSuffix,
1✔
379
                                        ) {
2✔
380
                                                // Traffic Segment
1✔
381
                                                s.Resources.RouteGroupSegment = &routegroup
1✔
382
                                        } else {
2✔
383
                                                s.Resources.RouteGroup = &routegroup
1✔
384
                                        }
1✔
385
                                        break
1✔
386
                                }
387
                        }
388
                }
389
        }
390
        return nil
1✔
391
}
392

393
func (c *StackSetController) collectStacks(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
394
        stacks, err := c.client.ZalandoV1().Stacks(c.namespace).List(ctx, metav1.ListOptions{})
1✔
395
        if err != nil {
1✔
396
                return fmt.Errorf("failed to list Stacks: %v", err)
×
397
        }
×
398

399
        for _, stack := range stacks.Items {
2✔
400
                if uid, ok := getOwnerUID(stack.ObjectMeta); ok {
2✔
401
                        if s, ok := stacksets[uid]; ok {
2✔
402
                                stack := stack
1✔
403
                                fixupStackTypeMeta(&stack)
1✔
404

1✔
405
                                s.StackContainers[stack.UID] = &core.StackContainer{
1✔
406
                                        Stack: &stack,
1✔
407
                                }
1✔
408
                                continue
1✔
409
                        }
410
                }
411
        }
412
        return nil
1✔
413
}
414

415
func (c *StackSetController) collectDeployments(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
416
        deployments, err := c.client.AppsV1().Deployments(c.namespace).List(ctx, metav1.ListOptions{})
1✔
417
        if err != nil {
1✔
418
                return fmt.Errorf("failed to list Deployments: %v", err)
×
419
        }
×
420

421
        for _, d := range deployments.Items {
2✔
422
                deployment := d
1✔
423
                if uid, ok := getOwnerUID(deployment.ObjectMeta); ok {
2✔
424
                        for _, stackset := range stacksets {
2✔
425
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
426
                                        s.Resources.Deployment = &deployment
1✔
427
                                        break
1✔
428
                                }
429
                        }
430
                }
431
        }
432
        return nil
1✔
433
}
434

435
func (c *StackSetController) collectServices(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
436
        services, err := c.client.CoreV1().Services(c.namespace).List(ctx, metav1.ListOptions{})
1✔
437
        if err != nil {
1✔
438
                return fmt.Errorf("failed to list Services: %v", err)
×
439
        }
×
440

441
Items:
1✔
442
        for _, s := range services.Items {
2✔
443
                service := s
1✔
444
                if uid, ok := getOwnerUID(service.ObjectMeta); ok {
2✔
445
                        for _, stackset := range stacksets {
2✔
446
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
447
                                        s.Resources.Service = &service
1✔
448
                                        continue Items
1✔
449
                                }
450

451
                                // service/HPA used to be owned by the deployment for some reason
452
                                for _, stack := range stackset.StackContainers {
2✔
453
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
454
                                                stack.Resources.Service = &service
1✔
455
                                                continue Items
1✔
456
                                        }
457
                                }
458
                        }
459
                }
460
        }
461
        return nil
1✔
462
}
463

464
func (c *StackSetController) collectHPAs(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
465
        hpas, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(c.namespace).List(ctx, metav1.ListOptions{})
1✔
466
        if err != nil {
1✔
467
                return fmt.Errorf("failed to list HPAs: %v", err)
×
468
        }
×
469

470
Items:
1✔
471
        for _, h := range hpas.Items {
2✔
472
                hpa := h
1✔
473
                if uid, ok := getOwnerUID(hpa.ObjectMeta); ok {
2✔
474
                        for _, stackset := range stacksets {
2✔
475
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
476
                                        s.Resources.HPA = &hpa
1✔
477
                                        continue Items
1✔
478
                                }
479

480
                                // service/HPA used to be owned by the deployment for some reason
481
                                for _, stack := range stackset.StackContainers {
2✔
482
                                        if stack.Resources.Deployment != nil && stack.Resources.Deployment.UID == uid {
2✔
483
                                                stack.Resources.HPA = &hpa
1✔
484
                                                continue Items
1✔
485
                                        }
486
                                }
487
                        }
488
                }
489
        }
490
        return nil
1✔
491
}
492

493
func (c *StackSetController) collectConfigMaps(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
494
        configMaps, err := c.client.CoreV1().ConfigMaps(c.namespace).List(ctx, metav1.ListOptions{})
1✔
495
        if err != nil {
1✔
496
                return fmt.Errorf("failed to list ConfigMaps: %v", err)
×
497
        }
×
498

499
        for _, cm := range configMaps.Items {
2✔
500
                configMap := cm
1✔
501
                if uid, ok := getOwnerUID(configMap.ObjectMeta); ok {
2✔
502
                        for _, stackset := range stacksets {
2✔
503
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
504
                                        s.Resources.ConfigMaps = append(s.Resources.ConfigMaps, &configMap)
1✔
505
                                        break
1✔
506
                                }
507
                        }
508
                }
509
        }
510
        return nil
1✔
511
}
512

513
func (c *StackSetController) collectSecrets(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
514
        secrets, err := c.client.CoreV1().Secrets(c.namespace).List(ctx, metav1.ListOptions{})
1✔
515
        if err != nil {
1✔
516
                return fmt.Errorf("failed to list Secrets: %v", err)
×
517
        }
×
518

519
        for _, sct := range secrets.Items {
2✔
520
                secret := sct
1✔
521
                if uid, ok := getOwnerUID(secret.ObjectMeta); ok {
2✔
522
                        for _, stackset := range stacksets {
2✔
523
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
524
                                        s.Resources.Secrets = append(s.Resources.Secrets, &secret)
1✔
525
                                        break
1✔
526
                                }
527
                        }
528
                }
529
        }
530
        return nil
1✔
531
}
532

533
func getOwnerUID(objectMeta metav1.ObjectMeta) (types.UID, bool) {
1✔
534
        if len(objectMeta.OwnerReferences) == 1 {
2✔
535
                return objectMeta.OwnerReferences[0].UID, true
1✔
536
        }
1✔
537
        return "", false
1✔
538
}
539

540
func (c *StackSetController) errorEventf(object runtime.Object, reason string, err error) error {
×
541
        switch err.(type) {
×
542
        case *eventedError:
×
543
                // already notified
×
544
                return err
×
545
        default:
×
546
                c.recorder.Eventf(
×
547
                        object,
×
548
                        v1.EventTypeWarning,
×
549
                        reason,
×
550
                        err.Error())
×
551
                return &eventedError{err: err}
×
552
        }
553
}
554

555
// hasOwnership returns true if the controller is the "owner" of the stackset.
556
// Whether it's owner is determined by the value of the
557
// 'stackset-controller.zalando.org/controller' annotation. If the value
558
// matches the controllerID then it owns it, or if the controllerID is
559
// "" and there's no annotation set.
560
func (c *StackSetController) hasOwnership(stackset *zv1.StackSet) bool {
×
561
        if stackset.Annotations != nil {
×
562
                if owner, ok := stackset.Annotations[StacksetControllerControllerAnnotationKey]; ok {
×
563
                        return owner == c.controllerID
×
564
                }
×
565
        }
566
        return c.controllerID == ""
×
567
}
568

569
func (c *StackSetController) startWatch(ctx context.Context) error {
×
570
        informer := cache.NewSharedIndexInformer(
×
571
                cache.NewListWatchFromClient(c.client.ZalandoV1().RESTClient(), "stacksets", c.namespace, fields.Everything()),
×
572
                &zv1.StackSet{},
×
573
                0, // skip resync
×
574
                cache.Indexers{},
×
575
        )
×
576

×
577
        _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
578
                AddFunc:    c.add,
×
579
                UpdateFunc: c.update,
×
580
                DeleteFunc: c.del,
×
581
        })
×
582
        if err != nil {
×
583
                return fmt.Errorf("failed to add event handler: %w", err)
×
584
        }
×
585

586
        go informer.Run(ctx.Done())
×
587
        if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
×
588
                return fmt.Errorf("timed out waiting for caches to sync")
×
589
        }
×
590
        c.logger.Info("Synced StackSet watcher")
×
591

×
592
        return nil
×
593
}
594

595
func (c *StackSetController) add(obj interface{}) {
×
596
        stackset, ok := obj.(*zv1.StackSet)
×
597
        if !ok {
×
598
                return
×
599
        }
×
600

601
        c.logger.Infof("New StackSet added %s/%s", stackset.Namespace, stackset.Name)
×
602
        c.stacksetEvents <- stacksetEvent{
×
603
                StackSet: stackset.DeepCopy(),
×
604
        }
×
605
}
606

607
func (c *StackSetController) update(oldObj, newObj interface{}) {
×
608
        newStackset, ok := newObj.(*zv1.StackSet)
×
609
        if !ok {
×
610
                return
×
611
        }
×
612

613
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
614
        if !ok {
×
615
                return
×
616
        }
×
617

618
        c.logger.Debugf("StackSet %s/%s changed: %s",
×
619
                newStackset.Namespace,
×
620
                newStackset.Name,
×
621
                cmp.Diff(oldStackset, newStackset, cmpopts.IgnoreUnexported(resource.Quantity{})),
×
622
        )
×
623

×
624
        c.logger.Infof("StackSet updated %s/%s", newStackset.Namespace, newStackset.Name)
×
625
        c.stacksetEvents <- stacksetEvent{
×
626
                StackSet: newStackset.DeepCopy(),
×
627
        }
×
628
}
629

630
func (c *StackSetController) del(obj interface{}) {
×
631
        stackset, ok := obj.(*zv1.StackSet)
×
632
        if !ok {
×
633
                return
×
634
        }
×
635

636
        c.logger.Infof("StackSet deleted %s/%s", stackset.Namespace, stackset.Name)
×
637
        c.stacksetEvents <- stacksetEvent{
×
638
                StackSet: stackset.DeepCopy(),
×
639
                Deleted:  true,
×
640
        }
×
641
}
642

643
func retryUpdate(updateFn func(retry bool) error) error {
×
644
        retry := false
×
645
        for {
×
646
                err := updateFn(retry)
×
647
                if err != nil {
×
648
                        if errors.IsConflict(err) {
×
649
                                retry = true
×
650
                                continue
×
651
                        }
652
                        return err
×
653
                }
654
                return nil
×
655
        }
656
}
657

658
// ReconcileStatuses reconciles the statuses of StackSets and Stacks.
659
func (c *StackSetController) ReconcileStatuses(ctx context.Context, ssc *core.StackSetContainer) error {
×
660
        for _, sc := range ssc.StackContainers {
×
661
                stack := sc.Stack.DeepCopy()
×
662
                status := *sc.GenerateStackStatus()
×
663
                err := retryUpdate(func(retry bool) error {
×
664
                        if retry {
×
665
                                updated, err := c.client.ZalandoV1().Stacks(sc.Namespace()).Get(ctx, stack.Name, metav1.GetOptions{})
×
666
                                if err != nil {
×
667
                                        return err
×
668
                                }
×
669
                                stack = updated
×
670
                        }
671
                        if !equality.Semantic.DeepEqual(status, stack.Status) {
×
672
                                stack.Status = status
×
673
                                _, err := c.client.ZalandoV1().Stacks(sc.Namespace()).UpdateStatus(ctx, stack, metav1.UpdateOptions{})
×
674
                                return err
×
675
                        }
×
676
                        return nil
×
677
                })
678
                if err != nil {
×
679
                        return c.errorEventf(sc.Stack, "FailedUpdateStackStatus", err)
×
680
                }
×
681
        }
682

683
        stackset := ssc.StackSet.DeepCopy()
×
684
        status := *ssc.GenerateStackSetStatus()
×
685
        err := retryUpdate(func(retry bool) error {
×
686
                if retry {
×
687
                        updated, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).Get(ctx, ssc.StackSet.Name, metav1.GetOptions{})
×
688
                        if err != nil {
×
689
                                return err
×
690
                        }
×
691
                        stackset = updated
×
692
                }
693
                if !equality.Semantic.DeepEqual(status, stackset.Status) {
×
694
                        stackset.Status = status
×
695
                        _, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, stackset, metav1.UpdateOptions{})
×
696
                        return err
×
697
                }
×
698
                return nil
×
699
        })
700
        if err != nil {
×
701
                return c.errorEventf(ssc.StackSet, "FailedUpdateStackSetStatus", err)
×
702
        }
×
703
        return nil
×
704
}
705

706
// ReconcileTrafficSegments updates the traffic segments according to the actual
707
// traffic weight of each stack.
708
//
709
// Returns the ordered list of Trafic Segments that need to be updated.
710
func (c *StackSetController) ReconcileTrafficSegments(
711
        ctx context.Context,
712
        ssc *core.StackSetContainer,
713
) ([]types.UID, error) {
×
714
        // Compute segments
×
715
        toUpdate, err := ssc.ComputeTrafficSegments()
×
716
        if err != nil {
×
717
                return nil, c.errorEventf(ssc.StackSet, "FailedManageSegments", err)
×
718
        }
×
719

720
        return toUpdate, nil
×
721
}
722

723
// CreateCurrentStack creates a new Stack object for the current stack, if needed
724
func (c *StackSetController) CreateCurrentStack(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
725
        newStack, newStackVersion := ssc.NewStack()
1✔
726
        if newStack == nil {
2✔
727
                return nil
1✔
728
        }
1✔
729

730
        if c.configMapSupportEnabled || c.secretSupportEnabled {
2✔
731
                // ensure that ConfigurationResources are prefixed by Stack name.
1✔
732
                if err := validateAllConfigurationResourcesNames(newStack.Stack); err != nil {
1✔
733
                        return err
×
734
                }
×
735
        }
736

737
        created, err := c.client.ZalandoV1().Stacks(newStack.Namespace()).Create(ctx, newStack.Stack, metav1.CreateOptions{})
1✔
738
        if err != nil {
1✔
739
                return err
×
740
        }
×
741
        fixupStackTypeMeta(created)
1✔
742

1✔
743
        c.recorder.Eventf(
1✔
744
                ssc.StackSet,
1✔
745
                v1.EventTypeNormal,
1✔
746
                "CreatedStack",
1✔
747
                "Created stack %s",
1✔
748
                newStack.Name(),
1✔
749
        )
1✔
750

1✔
751
        // Persist ObservedStackVersion in the status
1✔
752
        updated := ssc.StackSet.DeepCopy()
1✔
753
        updated.Status.ObservedStackVersion = newStackVersion
1✔
754

1✔
755
        result, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, updated, metav1.UpdateOptions{})
1✔
756
        if err != nil {
1✔
757
                return err
×
758
        }
×
759
        fixupStackSetTypeMeta(result)
1✔
760
        ssc.StackSet = result
1✔
761

1✔
762
        ssc.StackContainers[created.UID] = &core.StackContainer{
1✔
763
                Stack:          created,
1✔
764
                PendingRemoval: false,
1✔
765
                Resources:      core.StackResources{},
1✔
766
        }
1✔
767
        return nil
1✔
768
}
769

770
// CleanupOldStacks deletes stacks that are no longer needed.
771
func (c *StackSetController) CleanupOldStacks(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
772
        for _, sc := range ssc.StackContainers {
2✔
773
                if !sc.PendingRemoval {
2✔
774
                        continue
1✔
775
                }
776

777
                stack := sc.Stack
1✔
778
                err := c.client.ZalandoV1().Stacks(stack.Namespace).Delete(ctx, stack.Name, metav1.DeleteOptions{})
1✔
779
                if err != nil {
1✔
780
                        return c.errorEventf(ssc.StackSet, "FailedDeleteStack", err)
×
781
                }
×
782
                c.recorder.Eventf(
1✔
783
                        ssc.StackSet,
1✔
784
                        v1.EventTypeNormal,
1✔
785
                        "DeletedExcessStack",
1✔
786
                        "Deleted excess stack %s",
1✔
787
                        stack.Name)
1✔
788
        }
789

790
        return nil
1✔
791
}
792

793
// AddUpdateStackSetIngress reconciles the Ingress but never deletes it, it returns the existing/new Ingress
UNCOV
794
func (c *StackSetController) AddUpdateStackSetIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup, ingress *networking.Ingress) (*networking.Ingress, error) {
×
UNCOV
795
        // Ingress removed, handled outside
×
UNCOV
796
        if ingress == nil {
×
UNCOV
797
                return existing, nil
×
UNCOV
798
        }
×
799

UNCOV
800
        if existing == nil {
×
UNCOV
801
                if ingress.Annotations == nil {
×
UNCOV
802
                        ingress.Annotations = make(map[string]string)
×
UNCOV
803
                }
×
UNCOV
804
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
UNCOV
805

×
UNCOV
806
                createdIng, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
×
UNCOV
807
                if err != nil {
×
808
                        return nil, err
×
809
                }
×
UNCOV
810
                c.recorder.Eventf(
×
UNCOV
811
                        stackset,
×
UNCOV
812
                        v1.EventTypeNormal,
×
UNCOV
813
                        "CreatedIngress",
×
UNCOV
814
                        "Created Ingress %s",
×
UNCOV
815
                        ingress.Name)
×
UNCOV
816
                return createdIng, nil
×
817
        }
818

UNCOV
819
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
UNCOV
820
        if existingHaveUpdateTimeStamp {
×
UNCOV
821
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
UNCOV
822
        }
×
823

824
        // Check if we need to update the Ingress
UNCOV
825
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(ingress.Spec, existing.Spec) &&
×
UNCOV
826
                equality.Semantic.DeepEqual(ingress.Annotations, existing.Annotations) &&
×
UNCOV
827
                equality.Semantic.DeepEqual(ingress.Labels, existing.Labels) {
×
UNCOV
828
                // add the annotation back after comparing
×
UNCOV
829
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
UNCOV
830
                return existing, nil
×
UNCOV
831
        }
×
832

UNCOV
833
        updated := existing.DeepCopy()
×
UNCOV
834
        updated.Spec = ingress.Spec
×
UNCOV
835
        if ingress.Annotations != nil {
×
UNCOV
836
                updated.Annotations = ingress.Annotations
×
UNCOV
837
        } else {
×
UNCOV
838
                updated.Annotations = make(map[string]string)
×
UNCOV
839
        }
×
UNCOV
840
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
UNCOV
841

×
UNCOV
842
        updated.Labels = ingress.Labels
×
UNCOV
843

×
UNCOV
844
        createdIngress, err := c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
UNCOV
845
        if err != nil {
×
846
                return nil, err
×
847
        }
×
UNCOV
848
        c.recorder.Eventf(
×
UNCOV
849
                stackset,
×
UNCOV
850
                v1.EventTypeNormal,
×
UNCOV
851
                "UpdatedIngress",
×
UNCOV
852
                "Updated Ingress %s",
×
UNCOV
853
                ingress.Name)
×
UNCOV
854
        return createdIngress, nil
×
855
}
856

857
// AddUpdateStackSetRouteGroup reconciles the RouteGroup but never deletes it, it returns the existing/new RouteGroup
UNCOV
858
func (c *StackSetController) AddUpdateStackSetRouteGroup(ctx context.Context, stackset *zv1.StackSet, existing *rgv1.RouteGroup, ingress *networking.Ingress, rg *rgv1.RouteGroup) (*rgv1.RouteGroup, error) {
×
UNCOV
859
        // RouteGroup removed, handled outside
×
UNCOV
860
        if rg == nil {
×
UNCOV
861
                return existing, nil
×
UNCOV
862
        }
×
863

864
        // Create new RouteGroup
UNCOV
865
        if existing == nil {
×
UNCOV
866
                if rg.Annotations == nil {
×
UNCOV
867
                        rg.Annotations = make(map[string]string)
×
UNCOV
868
                }
×
UNCOV
869
                rg.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
UNCOV
870

×
UNCOV
871
                createdRg, err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Create(ctx, rg, metav1.CreateOptions{})
×
UNCOV
872
                if err != nil {
×
873
                        return nil, err
×
874
                }
×
UNCOV
875
                c.recorder.Eventf(
×
UNCOV
876
                        stackset,
×
UNCOV
877
                        v1.EventTypeNormal,
×
UNCOV
878
                        "CreatedRouteGroup",
×
UNCOV
879
                        "Created RouteGroup %s",
×
UNCOV
880
                        rg.Name)
×
UNCOV
881
                return createdRg, nil
×
882
        }
883

UNCOV
884
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
UNCOV
885
        if existingHaveUpdateTimeStamp {
×
UNCOV
886
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
UNCOV
887
        }
×
888

889
        // Check if we need to update the RouteGroup
UNCOV
890
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(rg.Spec, existing.Spec) &&
×
UNCOV
891
                equality.Semantic.DeepEqual(rg.Annotations, existing.Annotations) &&
×
UNCOV
892
                equality.Semantic.DeepEqual(rg.Labels, existing.Labels) {
×
UNCOV
893
                // add the annotation back after comparing
×
UNCOV
894
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
UNCOV
895
                return existing, nil
×
UNCOV
896
        }
×
897

UNCOV
898
        updated := existing.DeepCopy()
×
UNCOV
899
        updated.Spec = rg.Spec
×
UNCOV
900
        if rg.Annotations != nil {
×
901
                updated.Annotations = rg.Annotations
×
UNCOV
902
        } else {
×
UNCOV
903
                updated.Annotations = make(map[string]string)
×
UNCOV
904
        }
×
UNCOV
905
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
UNCOV
906

×
UNCOV
907
        updated.Labels = rg.Labels
×
UNCOV
908

×
UNCOV
909
        createdRg, err := c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
UNCOV
910
        if err != nil {
×
911
                return nil, err
×
912
        }
×
UNCOV
913
        c.recorder.Eventf(
×
UNCOV
914
                stackset,
×
UNCOV
915
                v1.EventTypeNormal,
×
UNCOV
916
                "UpdatedRouteGroup",
×
UNCOV
917
                "Updated RouteGroup %s",
×
UNCOV
918
                rg.Name)
×
UNCOV
919
        return createdRg, nil
×
920
}
921

922
// RecordTrafficSwitch records an event detailing when switches in traffic to
923
// Stacks, only when there are changes to record.
NEW
924
func (c *StackSetController) RecordTrafficSwitch(ctx context.Context, ssc *core.StackSetContainer) error {
×
925
        trafficChanges := ssc.TrafficChanges()
×
926
        if len(trafficChanges) != 0 {
×
927
                var changeMessages []string
×
928
                for _, change := range trafficChanges {
×
929
                        changeMessages = append(changeMessages, change.String())
×
930
                }
×
931

932
                c.recorder.Eventf(
×
933
                        ssc.StackSet,
×
934
                        v1.EventTypeNormal,
×
935
                        "TrafficSwitched",
×
936
                        "Switched traffic: %s",
×
937
                        strings.Join(changeMessages, ", "))
×
938
        }
939

940
        return nil
×
941
}
942

943
func (c *StackSetController) ReconcileStackSetDesiredTraffic(ctx context.Context, existing *zv1.StackSet, generateUpdated func() []*zv1.DesiredTraffic) error {
1✔
944
        updatedTraffic := generateUpdated()
1✔
945

1✔
946
        if equality.Semantic.DeepEqual(existing.Spec.Traffic, updatedTraffic) {
1✔
947
                return nil
×
948
        }
×
949

950
        updated := existing.DeepCopy()
1✔
951
        updated.Spec.Traffic = updatedTraffic
1✔
952

1✔
953
        _, err := c.client.ZalandoV1().StackSets(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
954
        if err != nil {
1✔
955
                return err
×
956
        }
×
957
        c.recorder.Eventf(
1✔
958
                updated,
1✔
959
                v1.EventTypeNormal,
1✔
960
                "UpdatedStackSet",
1✔
961
                "Updated StackSet %s",
1✔
962
                updated.Name)
1✔
963
        return nil
1✔
964
}
965

966
func (c *StackSetController) ReconcileStackResources(ctx context.Context, ssc *core.StackSetContainer, sc *core.StackContainer) error {
×
967
        err := c.ReconcileStackIngress(ctx, sc.Stack, sc.Resources.Ingress, sc.GenerateIngress)
×
968
        if err != nil {
×
969
                return c.errorEventf(sc.Stack, "FailedManageIngress", err)
×
970
        }
×
971

NEW
972
        err = c.ReconcileStackIngress(
×
NEW
973
                ctx,
×
NEW
974
                sc.Stack,
×
NEW
975
                sc.Resources.IngressSegment,
×
NEW
976
                sc.GenerateIngressSegment,
×
NEW
977
        )
×
NEW
978
        if err != nil {
×
NEW
979
                return c.errorEventf(sc.Stack, "FailedManageIngressSegment", err)
×
UNCOV
980
        }
×
981

982
        if c.routeGroupSupportEnabled {
×
983
                err = c.ReconcileStackRouteGroup(ctx, sc.Stack, sc.Resources.RouteGroup, sc.GenerateRouteGroup)
×
984
                if err != nil {
×
985
                        return c.errorEventf(sc.Stack, "FailedManageRouteGroup", err)
×
986
                }
×
987

NEW
988
                err = c.ReconcileStackRouteGroup(
×
NEW
989
                        ctx,
×
NEW
990
                        sc.Stack,
×
NEW
991
                        sc.Resources.RouteGroupSegment,
×
NEW
992
                        sc.GenerateRouteGroupSegment,
×
NEW
993
                )
×
NEW
994
                if err != nil {
×
NEW
995
                        return c.errorEventf(
×
996
                                sc.Stack,
×
NEW
997
                                "FailedManageRouteGroupSegment",
×
NEW
998
                                err,
×
999
                        )
×
1000
                }
×
1001
        }
1002

1003
        if c.configMapSupportEnabled {
×
1004
                err := c.ReconcileStackConfigMapRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1005
                if err != nil {
×
1006
                        return c.errorEventf(sc.Stack, "FailedManageConfigMapRefs", err)
×
1007
                }
×
1008
        }
1009

1010
        if c.secretSupportEnabled {
×
1011
                err := c.ReconcileStackSecretRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1012
                if err != nil {
×
1013
                        return c.errorEventf(sc.Stack, "FailedManageSecretRefs", err)
×
1014
                }
×
1015
        }
1016

1017
        err = c.ReconcileStackDeployment(ctx, sc.Stack, sc.Resources.Deployment, sc.GenerateDeployment)
×
1018
        if err != nil {
×
1019
                return c.errorEventf(sc.Stack, "FailedManageDeployment", err)
×
1020
        }
×
1021

1022
        hpaGenerator := sc.GenerateHPA
×
1023
        err = c.ReconcileStackHPA(ctx, sc.Stack, sc.Resources.HPA, hpaGenerator)
×
1024
        if err != nil {
×
1025
                return c.errorEventf(sc.Stack, "FailedManageHPA", err)
×
1026
        }
×
1027

1028
        err = c.ReconcileStackService(ctx, sc.Stack, sc.Resources.Service, sc.GenerateService)
×
1029
        if err != nil {
×
1030
                return c.errorEventf(sc.Stack, "FailedManageService", err)
×
1031
        }
×
1032

1033
        return nil
×
1034
}
1035

1036
// ReconcileStackSet reconciles all the things from a stackset
1037
func (c *StackSetController) ReconcileStackSet(ctx context.Context, container *core.StackSetContainer) (err error) {
×
1038
        defer func() {
×
1039
                if r := recover(); r != nil {
×
1040
                        c.metricsReporter.ReportPanic()
×
1041
                        c.stacksetLogger(container).Errorf("Encountered a panic while processing a stackset: %v\n%s", r, debug.Stack())
×
1042
                        err = fmt.Errorf("panic: %v", r)
×
1043
                }
×
1044
        }()
1045

1046
        // Create current stack, if needed. Proceed on errors.
1047
        err = c.CreateCurrentStack(ctx, container)
×
1048
        if err != nil {
×
1049
                err = c.errorEventf(container.StackSet, "FailedCreateStack", err)
×
1050
                c.stacksetLogger(container).Errorf("Unable to create stack: %v", err)
×
1051
        }
×
1052

1053
        // Update statuses from external resources (ingresses, deployments, etc). Abort on errors.
1054
        err = container.UpdateFromResources()
×
1055
        if err != nil {
×
1056
                return err
×
1057
        }
×
1058

1059
        // Update the stacks with the currently selected traffic reconciler. Proceed on errors.
1060
        err = container.ManageTraffic(time.Now())
×
1061
        if err != nil {
×
1062
                c.stacksetLogger(container).Errorf("Traffic reconciliation failed: %v", err)
×
1063
                c.recorder.Eventf(
×
1064
                        container.StackSet,
×
1065
                        v1.EventTypeWarning,
×
1066
                        "TrafficNotSwitched",
×
1067
                        "Failed to switch traffic: "+err.Error())
×
1068
        }
×
1069

1070
        // Mark stacks that should be removed
1071
        container.MarkExpiredStacks()
×
1072

×
NEW
1073
        // Update traffic segments. Proceed on errors.
×
NEW
1074
        segsInOrder, err := c.ReconcileTrafficSegments(ctx, container)
×
NEW
1075
        if err != nil {
×
NEW
1076
                err = c.errorEventf(
×
NEW
1077
                        container.StackSet,
×
NEW
1078
                        reasonFailedManageStackSet,
×
NEW
1079
                        err,
×
NEW
1080
                )
×
NEW
1081
                c.stacksetLogger(container).Errorf(
×
NEW
1082
                        "Unable to reconcile traffic segments: %v",
×
NEW
1083
                        err,
×
NEW
1084
                )
×
UNCOV
1085
        }
×
1086

1087
        // Reconcile stack resources. Proceed on errors.
1088
        reconciledStacks := map[types.UID]bool{}
×
1089
        for _, id := range segsInOrder {
×
1090
                reconciledStacks[id] = true
×
1091
                sc := container.StackContainers[id]
×
1092
                err = c.ReconcileStackResources(ctx, container, sc)
×
1093
                if err != nil {
×
1094
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1095
                        c.stackLogger(container, sc).Errorf(
×
1096
                                "Unable to reconcile stack resources: %v",
×
1097
                                err,
×
1098
                        )
×
1099
                }
×
1100
        }
1101

1102
        for k, sc := range container.StackContainers {
×
1103
                if reconciledStacks[k] {
×
1104
                        continue
×
1105
                }
1106

1107
                err = c.ReconcileStackResources(ctx, container, sc)
×
1108
                if err != nil {
×
1109
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1110
                        c.stackLogger(container, sc).Errorf("Unable to reconcile stack resources: %v", err)
×
1111
                }
×
1112
        }
1113

1114
        // Reconcile stackset resources (update ingress and/or routegroups). Proceed on errors.
NEW
1115
        err = c.RecordTrafficSwitch(ctx, container)
×
1116
        if err != nil {
×
1117
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1118
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset resources: %v", err)
×
1119
        }
×
1120

1121
        // Reconcile desired traffic in the stackset. Proceed on errors.
1122
        err = c.ReconcileStackSetDesiredTraffic(ctx, container.StackSet, container.GenerateStackSetTraffic)
×
1123
        if err != nil {
×
1124
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1125
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset traffic: %v", err)
×
1126
        }
×
1127

1128
        // Delete old stacks. Proceed on errors.
1129
        err = c.CleanupOldStacks(ctx, container)
×
1130
        if err != nil {
×
1131
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1132
                c.stacksetLogger(container).Errorf("Unable to delete old stacks: %v", err)
×
1133
        }
×
1134

1135
        // Update statuses.
1136
        err = c.ReconcileStatuses(ctx, container)
×
1137
        if err != nil {
×
1138
                return err
×
1139
        }
×
1140

1141
        return nil
×
1142
}
1143

1144
// getResetMinReplicasDelay parses and returns the reset delay if set in the
1145
// stackset annotation.
1146
func getResetMinReplicasDelay(annotations map[string]string) (time.Duration, bool) {
1✔
1147
        resetDelayStr, ok := annotations[ResetHPAMinReplicasDelayAnnotationKey]
1✔
1148
        if !ok {
2✔
1149
                return 0, false
1✔
1150
        }
1✔
1151
        resetDelay, err := time.ParseDuration(resetDelayStr)
1✔
1152
        if err != nil {
1✔
1153
                return 0, false
×
1154
        }
×
1155
        return resetDelay, true
1✔
1156
}
1157

1158
func fixupStackSetTypeMeta(stackset *zv1.StackSet) {
1✔
1159
        // set TypeMeta manually because of this bug:
1✔
1160
        // https://github.com/kubernetes/client-go/issues/308
1✔
1161
        stackset.APIVersion = core.APIVersion
1✔
1162
        stackset.Kind = core.KindStackSet
1✔
1163
}
1✔
1164

1165
func fixupStackTypeMeta(stack *zv1.Stack) {
1✔
1166
        // set TypeMeta manually because of this bug:
1✔
1167
        // https://github.com/kubernetes/client-go/issues/308
1✔
1168
        stack.APIVersion = core.APIVersion
1✔
1169
        stack.Kind = core.KindStack
1✔
1170
}
1✔
1171

1172
// validateConfigurationResourcesNames returns an error if any ConfigurationResource
1173
// name is not prefixed by Stack name.
1174
func validateAllConfigurationResourcesNames(stack *zv1.Stack) error {
1✔
1175
        for _, rsc := range stack.Spec.ConfigurationResources {
1✔
1176
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
×
1177
                        return err
×
1178
                }
×
1179
        }
1180
        return nil
1✔
1181
}
1182

1183
// validateConfigurationResourceName returns an error if specific resource
1184
// name is not prefixed by Stack name.
1185
func validateConfigurationResourceName(stack string, rsc string) error {
1✔
1186
        if !strings.HasPrefix(rsc, stack) {
2✔
1187
                return fmt.Errorf(configurationResourceNameError, rsc, stack)
1✔
1188
        }
1✔
1189
        return nil
1✔
1190
}
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