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

zalando-incubator / stackset-controller / 20348603794

18 Dec 2025 07:21PM UTC coverage: 49.888% (-0.2%) from 50.085%
20348603794

Pull #721

github

mikkeloscar
Improve forward feature

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
Pull Request #721: Improve forward feature

14 of 37 new or added lines in 4 files covered. (37.84%)

138 existing lines in 3 files now uncovered.

2662 of 5336 relevant lines covered (49.89%)

0.56 hits per line

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

34.78
/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
        config          StackSetConfig
56
        stacksetEvents  chan stacksetEvent
57
        stacksetStore   map[types.UID]zv1.StackSet
58
        recorder        kube_record.EventRecorder
59
        metricsReporter *core.MetricsReporter
60
        HealthReporter  healthcheck.Handler
61
        now             func() string
62
        sync.Mutex
63
}
64

65
type StackSetConfig struct {
66
        Namespace    string
67
        ControllerID string
68

69
        ClusterDomains              []string
70
        BackendWeightsAnnotationKey string
71
        SyncIngressAnnotations      []string
72

73
        ReconcileWorkers int
74
        Interval         time.Duration
75

76
        RouteGroupSupportEnabled bool
77
        ConfigMapSupportEnabled  bool
78
        SecretSupportEnabled     bool
79
        PcsSupportEnabled        bool
80
}
81

82
type stacksetEvent struct {
83
        Deleted  bool
84
        StackSet *zv1.StackSet
85
}
86

87
// eventedError wraps an error that was already exposed as an event to the user
88
type eventedError struct {
89
        err error
90
}
91

UNCOV
92
func (ee *eventedError) Error() string {
×
93
        return ee.err.Error()
×
94
}
×
95

UNCOV
96
func now() string {
×
97
        return time.Now().Format(time.RFC3339)
×
98
}
×
99

100
// NewStackSetController initializes a new StackSetController.
101
func NewStackSetController(
102
        client clientset.Interface,
103
        registry prometheus.Registerer,
104
        config StackSetConfig,
105
) (*StackSetController, error) {
1✔
106
        metricsReporter, err := core.NewMetricsReporter(registry)
1✔
107
        if err != nil {
1✔
UNCOV
108
                return nil, err
×
109
        }
×
110

111
        logger := log.WithField("controller", "stackset")
1✔
112

1✔
113
        if config.ControllerID != "" {
1✔
UNCOV
114
                logger = logger.WithField("controller_id", config.ControllerID)
×
115
        }
×
116

117
        return &StackSetController{
1✔
118
                logger:          logger,
1✔
119
                client:          client,
1✔
120
                config:          config,
1✔
121
                stacksetEvents:  make(chan stacksetEvent, 1),
1✔
122
                stacksetStore:   make(map[types.UID]zv1.StackSet),
1✔
123
                recorder:        recorder.CreateEventRecorder(client),
1✔
124
                metricsReporter: metricsReporter,
1✔
125
                HealthReporter:  healthcheck.NewHandler(),
1✔
126
                now:             now,
1✔
127
        }, nil
1✔
128
}
129

UNCOV
130
func (c *StackSetController) stacksetLogger(ssc *core.StackSetContainer) *log.Entry {
×
131
        return c.logger.WithFields(map[string]interface{}{
×
132
                "namespace": ssc.StackSet.Namespace,
×
133
                "stackset":  ssc.StackSet.Name,
×
134
        })
×
135
}
×
136

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

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

×
151
        // We're not alive if nextCheck is too far in the past
×
152
        c.HealthReporter.AddLivenessCheck("nextCheck", func() error {
×
153
                if time.Since(nextCheck) > 5*c.config.Interval {
×
154
                        return fmt.Errorf("nextCheck too old")
×
155
                }
×
156
                return nil
×
157
        })
158

UNCOV
159
        err := c.startWatch(ctx)
×
160
        if err != nil {
×
161
                return err
×
162
        }
×
163

UNCOV
164
        http.HandleFunc("/healthz", c.HealthReporter.LiveEndpoint)
×
165

×
166
        nextCheck = time.Now().Add(-c.config.Interval)
×
167

×
168
        for {
×
169
                select {
×
170
                case <-time.After(time.Until(nextCheck)):
×
171

×
172
                        nextCheck = time.Now().Add(c.config.Interval)
×
173

×
174
                        stackSetContainers, err := c.collectResources(ctx)
×
175
                        if err != nil {
×
176
                                c.logger.Errorf("Failed to collect resources: %v", err)
×
177
                                continue
×
178
                        }
179

UNCOV
180
                        var reconcileGroup errgroup.Group
×
181
                        reconcileGroup.SetLimit(c.config.ReconcileWorkers)
×
182
                        for stackset, container := range stackSetContainers {
×
183
                                container := container
×
184
                                stackset := stackset
×
185

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

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

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

217
                                // update stackset entry
UNCOV
218
                                c.stacksetStore[stackset.UID] = stackset
×
219
                                continue
×
220
                        }
221

222
                        // check if stackset should be managed by the controller
UNCOV
223
                        if !c.hasOwnership(&stackset) {
×
224
                                continue
×
225
                        }
226

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

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

1✔
243
                reconciler := core.TrafficReconciler(&core.SimpleTrafficReconciler{})
1✔
244

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

256
                stacksetContainer := core.NewContainer(
1✔
257
                        &stackset,
1✔
258
                        reconciler,
1✔
259
                        c.config.BackendWeightsAnnotationKey,
1✔
260
                        c.config.ClusterDomains,
1✔
261
                        c.config.SyncIngressAnnotations,
1✔
262
                )
1✔
263
                stacksets[uid] = stacksetContainer
1✔
264
        }
265

266
        err := c.collectStacks(ctx, stacksets)
1✔
267
        if err != nil {
1✔
UNCOV
268
                return nil, err
×
269
        }
×
270

271
        err = c.collectIngresses(ctx, stacksets)
1✔
272
        if err != nil {
1✔
UNCOV
273
                return nil, err
×
274
        }
×
275

276
        if c.config.RouteGroupSupportEnabled {
2✔
277
                err = c.collectRouteGroups(ctx, stacksets)
1✔
278
                if err != nil {
1✔
UNCOV
279
                        return nil, err
×
280
                }
×
281
        }
282

283
        err = c.collectDeployments(ctx, stacksets)
1✔
284
        if err != nil {
1✔
UNCOV
285
                return nil, err
×
286
        }
×
287

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

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

298
        if c.config.ConfigMapSupportEnabled {
2✔
299
                err = c.collectConfigMaps(ctx, stacksets)
1✔
300
                if err != nil {
1✔
UNCOV
301
                        return nil, err
×
302
                }
×
303
        }
304

305
        if c.config.SecretSupportEnabled {
2✔
306
                err = c.collectSecrets(ctx, stacksets)
1✔
307
                if err != nil {
1✔
UNCOV
308
                        return nil, err
×
309
                }
×
310
        }
311

312
        if c.config.PcsSupportEnabled {
2✔
313
                err = c.collectPlatformCredentialsSet(ctx, stacksets)
1✔
314
                if err != nil {
1✔
UNCOV
315
                        return nil, err
×
316
                }
×
317
        }
318

319
        return stacksets, nil
1✔
320
}
321

322
func (c *StackSetController) collectIngresses(ctx context.Context, stacksets map[types.UID]*core.StackSetContainer) error {
1✔
323
        ingresses, err := c.client.NetworkingV1().Ingresses(c.config.Namespace).List(ctx, metav1.ListOptions{})
1✔
324

1✔
325
        if err != nil {
1✔
UNCOV
326
                return fmt.Errorf("failed to list Ingresses: %v", err)
×
327
        }
×
328

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

496
func (c *StackSetController) collectConfigMaps(
497
        ctx context.Context,
498
        stacksets map[types.UID]*core.StackSetContainer,
499
) error {
1✔
500
        configMaps, err := c.client.CoreV1().ConfigMaps(c.config.Namespace).List(ctx, metav1.ListOptions{})
1✔
501
        if err != nil {
1✔
UNCOV
502
                return fmt.Errorf("failed to list ConfigMaps: %v", err)
×
503
        }
×
504

505
        for _, cm := range configMaps.Items {
2✔
506
                configMap := cm
1✔
507
                if uid, ok := getOwnerUID(configMap.ObjectMeta); ok {
2✔
508
                        for _, stackset := range stacksets {
2✔
509
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
510
                                        s.Resources.ConfigMaps = append(s.Resources.ConfigMaps, &configMap)
1✔
511
                                        break
1✔
512
                                }
513
                        }
514
                }
515
        }
516
        return nil
1✔
517
}
518

519
func (c *StackSetController) collectSecrets(
520
        ctx context.Context,
521
        stacksets map[types.UID]*core.StackSetContainer,
522
) error {
1✔
523
        secrets, err := c.client.CoreV1().Secrets(c.config.Namespace).List(ctx, metav1.ListOptions{})
1✔
524
        if err != nil {
1✔
UNCOV
525
                return fmt.Errorf("failed to list Secrets: %v", err)
×
526
        }
×
527

528
        for _, sct := range secrets.Items {
2✔
529
                secret := sct
1✔
530
                if uid, ok := getOwnerUID(secret.ObjectMeta); ok {
2✔
531
                        for _, stackset := range stacksets {
2✔
532
                                if s, ok := stackset.StackContainers[uid]; ok {
2✔
533
                                        s.Resources.Secrets = append(s.Resources.Secrets, &secret)
1✔
534
                                        break
1✔
535
                                }
536
                        }
537
                }
538
        }
539
        return nil
1✔
540
}
541

542
func (c *StackSetController) collectPlatformCredentialsSet(
543
        ctx context.Context,
544
        stacksets map[types.UID]*core.StackSetContainer,
545
) error {
1✔
546
        platformCredentialsSets, err := c.client.ZalandoV1().PlatformCredentialsSets(c.config.Namespace).
1✔
547
                List(ctx, metav1.ListOptions{})
1✔
548
        if err != nil {
1✔
UNCOV
549
                return fmt.Errorf("failed to list PlatformCredentialsSet: %v", err)
×
550
        }
×
551

552
        for _, platformCredentialsSet := range platformCredentialsSets.Items {
1✔
UNCOV
553
                pcs := platformCredentialsSet
×
554
                if uid, ok := getOwnerUID(platformCredentialsSet.ObjectMeta); ok {
×
555
                        for _, stackset := range stacksets {
×
556
                                if s, ok := stackset.StackContainers[uid]; ok {
×
557
                                        s.Resources.PlatformCredentialsSets = append(
×
558
                                                s.Resources.PlatformCredentialsSets,
×
559
                                                &pcs,
×
560
                                        )
×
561
                                        break
×
562
                                }
563
                        }
564
                }
565
        }
566
        return nil
1✔
567
}
568

569
func getOwnerUID(objectMeta metav1.ObjectMeta) (types.UID, bool) {
1✔
570
        if len(objectMeta.OwnerReferences) == 1 {
2✔
571
                return objectMeta.OwnerReferences[0].UID, true
1✔
572
        }
1✔
573
        return "", false
1✔
574
}
575

UNCOV
576
func (c *StackSetController) errorEventf(object runtime.Object, reason string, err error) error {
×
577
        switch err.(type) {
×
578
        case *eventedError:
×
579
                // already notified
×
580
                return err
×
581
        default:
×
582
                c.recorder.Eventf(
×
583
                        object,
×
584
                        v1.EventTypeWarning,
×
585
                        reason,
×
586
                        err.Error())
×
587
                return &eventedError{err: err}
×
588
        }
589
}
590

591
// hasOwnership returns true if the controller is the "owner" of the stackset.
592
// Whether it's owner is determined by the value of the
593
// 'stackset-controller.zalando.org/controller' annotation. If the value
594
// matches the controllerID then it owns it, or if the controllerID is
595
// "" and there's no annotation set.
UNCOV
596
func (c *StackSetController) hasOwnership(stackset *zv1.StackSet) bool {
×
597
        if stackset.Annotations != nil {
×
598
                if owner, ok := stackset.Annotations[StacksetControllerControllerAnnotationKey]; ok {
×
599
                        return owner == c.config.ControllerID
×
600
                }
×
601
        }
UNCOV
602
        return c.config.ControllerID == ""
×
603
}
604

UNCOV
605
func (c *StackSetController) startWatch(ctx context.Context) error {
×
606
        informer := cache.NewSharedIndexInformer(
×
607
                cache.NewListWatchFromClient(c.client.ZalandoV1().RESTClient(), "stacksets", c.config.Namespace, fields.Everything()),
×
608
                &zv1.StackSet{},
×
609
                0, // skip resync
×
610
                cache.Indexers{},
×
611
        )
×
612

×
613
        _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
×
614
                AddFunc:    c.add,
×
615
                UpdateFunc: c.update,
×
616
                DeleteFunc: c.del,
×
617
        })
×
618
        if err != nil {
×
619
                return fmt.Errorf("failed to add event handler: %w", err)
×
620
        }
×
621

UNCOV
622
        go informer.Run(ctx.Done())
×
623
        if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
×
624
                return fmt.Errorf("timed out waiting for caches to sync")
×
625
        }
×
626
        c.logger.Info("Synced StackSet watcher")
×
627

×
628
        return nil
×
629
}
630

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

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

UNCOV
643
func (c *StackSetController) update(oldObj, newObj interface{}) {
×
644
        newStackset, ok := newObj.(*zv1.StackSet)
×
645
        if !ok {
×
646
                return
×
647
        }
×
648

UNCOV
649
        oldStackset, ok := oldObj.(*zv1.StackSet)
×
650
        if !ok {
×
651
                return
×
652
        }
×
653

UNCOV
654
        c.logger.Debugf("StackSet %s/%s changed: %s",
×
655
                newStackset.Namespace,
×
656
                newStackset.Name,
×
657
                cmp.Diff(oldStackset, newStackset, cmpopts.IgnoreUnexported(resource.Quantity{})),
×
658
        )
×
659

×
660
        c.logger.Infof("StackSet updated %s/%s", newStackset.Namespace, newStackset.Name)
×
661
        c.stacksetEvents <- stacksetEvent{
×
662
                StackSet: newStackset.DeepCopy(),
×
663
        }
×
664
}
665

UNCOV
666
func (c *StackSetController) del(obj interface{}) {
×
667
        stackset, ok := obj.(*zv1.StackSet)
×
668
        if !ok {
×
669
                return
×
670
        }
×
671

UNCOV
672
        c.logger.Infof("StackSet deleted %s/%s", stackset.Namespace, stackset.Name)
×
673
        c.stacksetEvents <- stacksetEvent{
×
674
                StackSet: stackset.DeepCopy(),
×
675
                Deleted:  true,
×
676
        }
×
677
}
678

UNCOV
679
func retryUpdate(updateFn func(retry bool) error) error {
×
680
        retry := false
×
681
        for {
×
682
                err := updateFn(retry)
×
683
                if err != nil {
×
684
                        if errors.IsConflict(err) {
×
685
                                retry = true
×
686
                                continue
×
687
                        }
UNCOV
688
                        return err
×
689
                }
UNCOV
690
                return nil
×
691
        }
692
}
693

694
// ReconcileStatuses reconciles the statuses of StackSets and Stacks.
UNCOV
695
func (c *StackSetController) ReconcileStatuses(ctx context.Context, ssc *core.StackSetContainer) error {
×
696
        for _, sc := range ssc.StackContainers {
×
697
                stack := sc.Stack.DeepCopy()
×
698
                status := *sc.GenerateStackStatus()
×
699
                err := retryUpdate(func(retry bool) error {
×
700
                        if retry {
×
701
                                updated, err := c.client.ZalandoV1().Stacks(sc.Namespace()).Get(ctx, stack.Name, metav1.GetOptions{})
×
702
                                if err != nil {
×
703
                                        return err
×
704
                                }
×
705
                                stack = updated
×
706
                        }
UNCOV
707
                        if !equality.Semantic.DeepEqual(status, stack.Status) {
×
708
                                stack.Status = status
×
709
                                _, err := c.client.ZalandoV1().Stacks(sc.Namespace()).UpdateStatus(ctx, stack, metav1.UpdateOptions{})
×
710
                                return err
×
711
                        }
×
712
                        return nil
×
713
                })
UNCOV
714
                if err != nil {
×
715
                        return c.errorEventf(sc.Stack, "FailedUpdateStackStatus", err)
×
716
                }
×
717
        }
718

UNCOV
719
        stackset := ssc.StackSet.DeepCopy()
×
720
        status := *ssc.GenerateStackSetStatus()
×
721
        err := retryUpdate(func(retry bool) error {
×
722
                if retry {
×
723
                        updated, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).Get(ctx, ssc.StackSet.Name, metav1.GetOptions{})
×
724
                        if err != nil {
×
725
                                return err
×
726
                        }
×
727
                        stackset = updated
×
728
                }
UNCOV
729
                if !equality.Semantic.DeepEqual(status, stackset.Status) {
×
730
                        stackset.Status = status
×
731
                        _, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, stackset, metav1.UpdateOptions{})
×
732
                        return err
×
733
                }
×
734
                return nil
×
735
        })
UNCOV
736
        if err != nil {
×
737
                return c.errorEventf(ssc.StackSet, "FailedUpdateStackSetStatus", err)
×
738
        }
×
739
        return nil
×
740
}
741

742
// ReconcileTrafficSegments updates the traffic segments according to the actual
743
// traffic weight of each stack.
744
//
745
// Returns the ordered list of Trafic Segments that need to be updated.
746
func (c *StackSetController) ReconcileTrafficSegments(
747
        ctx context.Context,
748
        ssc *core.StackSetContainer,
UNCOV
749
) ([]types.UID, error) {
×
750
        // Compute segments
×
751
        toUpdate, err := ssc.ComputeTrafficSegments()
×
752
        if err != nil {
×
753
                return nil, c.errorEventf(ssc.StackSet, "FailedManageSegments", err)
×
754
        }
×
755

UNCOV
756
        return toUpdate, nil
×
757
}
758

759
// CreateCurrentStack creates a new Stack object for the current stack, if needed
760
func (c *StackSetController) CreateCurrentStack(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
761
        newStack, newStackVersion := ssc.NewStack()
1✔
762
        if newStack == nil {
2✔
763
                return nil
1✔
764
        }
1✔
765

766
        if c.config.ConfigMapSupportEnabled || c.config.SecretSupportEnabled {
2✔
767
                // ensure that ConfigurationResources are prefixed by Stack name.
1✔
768
                if err := validateAllConfigurationResourcesNames(newStack.Stack); err != nil {
1✔
UNCOV
769
                        return err
×
770
                }
×
771
        }
772

773
        created, err := c.client.ZalandoV1().Stacks(newStack.Namespace()).Create(ctx, newStack.Stack, metav1.CreateOptions{})
1✔
774
        if err != nil {
1✔
UNCOV
775
                return err
×
776
        }
×
777
        fixupStackTypeMeta(created)
1✔
778

1✔
779
        c.recorder.Eventf(
1✔
780
                ssc.StackSet,
1✔
781
                v1.EventTypeNormal,
1✔
782
                "CreatedStack",
1✔
783
                "Created stack %s",
1✔
784
                newStack.Name(),
1✔
785
        )
1✔
786

1✔
787
        // Persist ObservedStackVersion in the status
1✔
788
        updated := ssc.StackSet.DeepCopy()
1✔
789
        updated.Status.ObservedStackVersion = newStackVersion
1✔
790

1✔
791
        result, err := c.client.ZalandoV1().StackSets(ssc.StackSet.Namespace).UpdateStatus(ctx, updated, metav1.UpdateOptions{})
1✔
792
        if err != nil {
1✔
UNCOV
793
                return err
×
794
        }
×
795
        fixupStackSetTypeMeta(result)
1✔
796
        ssc.StackSet = result
1✔
797

1✔
798
        ssc.StackContainers[created.UID] = &core.StackContainer{
1✔
799
                Stack:          created,
1✔
800
                PendingRemoval: false,
1✔
801
                Resources:      core.StackResources{},
1✔
802
        }
1✔
803
        return nil
1✔
804
}
805

806
// CleanupOldStacks deletes stacks that are no longer needed.
807
func (c *StackSetController) CleanupOldStacks(ctx context.Context, ssc *core.StackSetContainer) error {
1✔
808
        for _, sc := range ssc.StackContainers {
2✔
809
                if !sc.PendingRemoval {
2✔
810
                        continue
1✔
811
                }
812

813
                stack := sc.Stack
1✔
814
                err := c.client.ZalandoV1().Stacks(stack.Namespace).Delete(ctx, stack.Name, metav1.DeleteOptions{})
1✔
815
                if err != nil {
1✔
UNCOV
816
                        return c.errorEventf(ssc.StackSet, "FailedDeleteStack", err)
×
817
                }
×
818
                c.recorder.Eventf(
1✔
819
                        ssc.StackSet,
1✔
820
                        v1.EventTypeNormal,
1✔
821
                        "DeletedExcessStack",
1✔
822
                        "Deleted excess stack %s",
1✔
823
                        stack.Name)
1✔
824
        }
825

826
        return nil
1✔
827
}
828

829
// AddUpdateStackSetIngress reconciles the Ingress but never deletes it, it returns the existing/new Ingress
UNCOV
830
func (c *StackSetController) AddUpdateStackSetIngress(ctx context.Context, stackset *zv1.StackSet, existing *networking.Ingress, routegroup *rgv1.RouteGroup, ingress *networking.Ingress) (*networking.Ingress, error) {
×
831
        // Ingress removed, handled outside
×
832
        if ingress == nil {
×
833
                return existing, nil
×
834
        }
×
835

UNCOV
836
        if existing == nil {
×
837
                if ingress.Annotations == nil {
×
838
                        ingress.Annotations = make(map[string]string)
×
839
                }
×
840
                ingress.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
841

×
842
                createdIng, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
×
843
                if err != nil {
×
844
                        return nil, err
×
845
                }
×
846
                c.recorder.Eventf(
×
847
                        stackset,
×
848
                        v1.EventTypeNormal,
×
849
                        "CreatedIngress",
×
850
                        "Created Ingress %s",
×
851
                        ingress.Name)
×
852
                return createdIng, nil
×
853
        }
854

UNCOV
855
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
856
        if existingHaveUpdateTimeStamp {
×
857
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
858
        }
×
859

860
        // Check if we need to update the Ingress
UNCOV
861
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(ingress.Spec, existing.Spec) &&
×
862
                equality.Semantic.DeepEqual(ingress.Annotations, existing.Annotations) &&
×
863
                equality.Semantic.DeepEqual(ingress.Labels, existing.Labels) {
×
864
                // add the annotation back after comparing
×
865
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
866
                return existing, nil
×
867
        }
×
868

UNCOV
869
        updated := existing.DeepCopy()
×
870
        updated.Spec = ingress.Spec
×
871
        if ingress.Annotations != nil {
×
872
                updated.Annotations = ingress.Annotations
×
873
        } else {
×
874
                updated.Annotations = make(map[string]string)
×
875
        }
×
876
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
877

×
878
        updated.Labels = ingress.Labels
×
879

×
880
        createdIngress, err := c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
881
        if err != nil {
×
882
                return nil, err
×
883
        }
×
884
        c.recorder.Eventf(
×
885
                stackset,
×
886
                v1.EventTypeNormal,
×
887
                "UpdatedIngress",
×
888
                "Updated Ingress %s",
×
889
                ingress.Name)
×
890
        return createdIngress, nil
×
891
}
892

893
// AddUpdateStackSetRouteGroup reconciles the RouteGroup but never deletes it, it returns the existing/new RouteGroup
UNCOV
894
func (c *StackSetController) AddUpdateStackSetRouteGroup(ctx context.Context, stackset *zv1.StackSet, existing *rgv1.RouteGroup, ingress *networking.Ingress, rg *rgv1.RouteGroup) (*rgv1.RouteGroup, error) {
×
895
        // RouteGroup removed, handled outside
×
896
        if rg == nil {
×
897
                return existing, nil
×
898
        }
×
899

900
        // Create new RouteGroup
UNCOV
901
        if existing == nil {
×
902
                if rg.Annotations == nil {
×
903
                        rg.Annotations = make(map[string]string)
×
904
                }
×
905
                rg.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
906

×
907
                createdRg, err := c.client.RouteGroupV1().RouteGroups(rg.Namespace).Create(ctx, rg, metav1.CreateOptions{})
×
908
                if err != nil {
×
909
                        return nil, err
×
910
                }
×
911
                c.recorder.Eventf(
×
912
                        stackset,
×
913
                        v1.EventTypeNormal,
×
914
                        "CreatedRouteGroup",
×
915
                        "Created RouteGroup %s",
×
916
                        rg.Name)
×
917
                return createdRg, nil
×
918
        }
919

UNCOV
920
        lastUpdateValue, existingHaveUpdateTimeStamp := existing.Annotations[ControllerLastUpdatedAnnotationKey]
×
921
        if existingHaveUpdateTimeStamp {
×
922
                delete(existing.Annotations, ControllerLastUpdatedAnnotationKey)
×
923
        }
×
924

925
        // Check if we need to update the RouteGroup
UNCOV
926
        if existingHaveUpdateTimeStamp && equality.Semantic.DeepDerivative(rg.Spec, existing.Spec) &&
×
927
                equality.Semantic.DeepEqual(rg.Annotations, existing.Annotations) &&
×
928
                equality.Semantic.DeepEqual(rg.Labels, existing.Labels) {
×
929
                // add the annotation back after comparing
×
930
                existing.Annotations[ControllerLastUpdatedAnnotationKey] = lastUpdateValue
×
931
                return existing, nil
×
932
        }
×
933

UNCOV
934
        updated := existing.DeepCopy()
×
935
        updated.Spec = rg.Spec
×
936
        if rg.Annotations != nil {
×
937
                updated.Annotations = rg.Annotations
×
938
        } else {
×
939
                updated.Annotations = make(map[string]string)
×
940
        }
×
941
        updated.Annotations[ControllerLastUpdatedAnnotationKey] = c.now()
×
942

×
943
        updated.Labels = rg.Labels
×
944

×
945
        createdRg, err := c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
×
946
        if err != nil {
×
947
                return nil, err
×
948
        }
×
949
        c.recorder.Eventf(
×
950
                stackset,
×
951
                v1.EventTypeNormal,
×
952
                "UpdatedRouteGroup",
×
953
                "Updated RouteGroup %s",
×
954
                rg.Name)
×
955
        return createdRg, nil
×
956
}
957

958
// RecordTrafficSwitch records an event detailing when switches in traffic to
959
// Stacks, only when there are changes to record.
UNCOV
960
func (c *StackSetController) RecordTrafficSwitch(ctx context.Context, ssc *core.StackSetContainer) error {
×
961
        trafficChanges := ssc.TrafficChanges()
×
962
        if len(trafficChanges) != 0 {
×
963
                var changeMessages []string
×
964
                for _, change := range trafficChanges {
×
965
                        changeMessages = append(changeMessages, change.String())
×
966
                }
×
967

UNCOV
968
                c.recorder.Eventf(
×
969
                        ssc.StackSet,
×
970
                        v1.EventTypeNormal,
×
971
                        "TrafficSwitched",
×
972
                        "Switched traffic: %s",
×
973
                        strings.Join(changeMessages, ", "))
×
974
        }
975

UNCOV
976
        return nil
×
977
}
978

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

1✔
982
        if equality.Semantic.DeepEqual(existing.Spec.Traffic, updatedTraffic) {
1✔
UNCOV
983
                return nil
×
984
        }
×
985

986
        updated := existing.DeepCopy()
1✔
987
        updated.Spec.Traffic = updatedTraffic
1✔
988

1✔
989
        _, err := c.client.ZalandoV1().StackSets(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
990
        if err != nil {
1✔
UNCOV
991
                return err
×
992
        }
×
993
        c.recorder.Eventf(
1✔
994
                updated,
1✔
995
                v1.EventTypeNormal,
1✔
996
                "UpdatedStackSet",
1✔
997
                "Updated StackSet %s",
1✔
998
                updated.Name)
1✔
999
        return nil
1✔
1000
}
1001

UNCOV
1002
func (c *StackSetController) ReconcileStackResources(ctx context.Context, ssc *core.StackSetContainer, sc *core.StackContainer) error {
×
1003
        err := c.ReconcileStackIngress(ctx, sc.Stack, sc.Resources.Ingress, sc.GenerateIngress)
×
1004
        if err != nil {
×
1005
                return c.errorEventf(sc.Stack, "FailedManageIngress", err)
×
1006
        }
×
1007

UNCOV
1008
        err = c.ReconcileStackIngress(
×
1009
                ctx,
×
1010
                sc.Stack,
×
1011
                sc.Resources.IngressSegment,
×
1012
                sc.GenerateIngressSegment,
×
1013
        )
×
1014
        if err != nil {
×
1015
                return c.errorEventf(sc.Stack, "FailedManageIngressSegment", err)
×
1016
        }
×
1017

UNCOV
1018
        if c.config.RouteGroupSupportEnabled {
×
1019
                err = c.ReconcileStackRouteGroup(ctx, sc.Stack, sc.Resources.RouteGroup, sc.GenerateRouteGroup)
×
1020
                if err != nil {
×
1021
                        return c.errorEventf(sc.Stack, "FailedManageRouteGroup", err)
×
1022
                }
×
1023

UNCOV
1024
                err = c.ReconcileStackRouteGroup(
×
1025
                        ctx,
×
1026
                        sc.Stack,
×
1027
                        sc.Resources.RouteGroupSegment,
×
1028
                        sc.GenerateRouteGroupSegment,
×
1029
                )
×
1030
                if err != nil {
×
1031
                        return c.errorEventf(
×
1032
                                sc.Stack,
×
1033
                                "FailedManageRouteGroupSegment",
×
1034
                                err,
×
1035
                        )
×
1036
                }
×
1037
        }
1038

UNCOV
1039
        if c.config.ConfigMapSupportEnabled {
×
1040
                err := c.ReconcileStackConfigMapRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1041
                if err != nil {
×
1042
                        return c.errorEventf(sc.Stack, "FailedManageConfigMapRefs", err)
×
1043
                }
×
1044
        }
1045

UNCOV
1046
        if c.config.SecretSupportEnabled {
×
1047
                err := c.ReconcileStackSecretRefs(ctx, sc.Stack, sc.UpdateObjectMeta)
×
1048
                if err != nil {
×
1049
                        return c.errorEventf(sc.Stack, "FailedManageSecretRefs", err)
×
1050
                }
×
1051
        }
1052

UNCOV
1053
        if c.config.PcsSupportEnabled {
×
1054
                err = c.ReconcileStackPlatformCredentialsSets(
×
1055
                        ctx,
×
1056
                        sc.Stack,
×
1057
                        sc.Resources.PlatformCredentialsSets,
×
1058
                        sc.GeneratePlatformCredentialsSet,
×
1059
                )
×
1060
                if err != nil {
×
1061
                        return c.errorEventf(sc.Stack, "FailedManagePlatformCredentialsSet", err)
×
1062
                }
×
1063
        }
1064

UNCOV
1065
        err = c.ReconcileStackDeployment(ctx, sc.Stack, sc.Resources.Deployment, sc.GenerateDeployment)
×
1066
        if err != nil {
×
1067
                return c.errorEventf(sc.Stack, "FailedManageDeployment", err)
×
1068
        }
×
1069

UNCOV
1070
        hpaGenerator := sc.GenerateHPA
×
1071
        err = c.ReconcileStackHPA(ctx, sc.Stack, sc.Resources.HPA, hpaGenerator)
×
1072
        if err != nil {
×
1073
                return c.errorEventf(sc.Stack, "FailedManageHPA", err)
×
1074
        }
×
1075

UNCOV
1076
        err = c.ReconcileStackService(ctx, sc.Stack, sc.Resources.Service, sc.GenerateService)
×
1077
        if err != nil {
×
1078
                return c.errorEventf(sc.Stack, "FailedManageService", err)
×
1079
        }
×
1080

UNCOV
1081
        return nil
×
1082
}
1083

1084
// ReconcileStackSet reconciles all the things from a stackset
UNCOV
1085
func (c *StackSetController) ReconcileStackSet(ctx context.Context, container *core.StackSetContainer) (err error) {
×
1086
        defer func() {
×
1087
                if r := recover(); r != nil {
×
1088
                        c.metricsReporter.ReportPanic()
×
1089
                        c.stacksetLogger(container).Errorf("Encountered a panic while processing a stackset: %v\n%s", r, debug.Stack())
×
1090
                        err = fmt.Errorf("panic: %v", r)
×
1091
                }
×
1092
        }()
1093

1094
        // Create current stack, if needed. Proceed on errors.
UNCOV
1095
        err = c.CreateCurrentStack(ctx, container)
×
1096
        if err != nil {
×
1097
                err = c.errorEventf(container.StackSet, "FailedCreateStack", err)
×
1098
                c.stacksetLogger(container).Errorf("Unable to create stack: %v", err)
×
1099
        }
×
1100

1101
        // Update statuses from external resources (ingresses, deployments, etc). Abort on errors.
UNCOV
1102
        err = container.UpdateFromResources()
×
1103
        if err != nil {
×
1104
                return err
×
1105
        }
×
1106

1107
        // Update the stacks with the currently selected traffic reconciler. Proceed on errors.
UNCOV
1108
        err = container.ManageTraffic(time.Now())
×
1109
        if err != nil {
×
1110
                c.stacksetLogger(container).Errorf("Traffic reconciliation failed: %v", err)
×
1111
                c.recorder.Eventf(
×
1112
                        container.StackSet,
×
1113
                        v1.EventTypeWarning,
×
1114
                        "TrafficNotSwitched",
×
1115
                        "Failed to switch traffic: "+err.Error())
×
1116
        }
×
1117

1118
        // Mark stacks that should be removed
UNCOV
1119
        container.MarkExpiredStacks()
×
1120

×
1121
        // Update traffic segments. Proceed on errors.
×
1122
        segsInOrder, err := c.ReconcileTrafficSegments(ctx, container)
×
1123
        if err != nil {
×
1124
                err = c.errorEventf(
×
1125
                        container.StackSet,
×
1126
                        reasonFailedManageStackSet,
×
1127
                        err,
×
1128
                )
×
1129
                c.stacksetLogger(container).Errorf(
×
1130
                        "Unable to reconcile traffic segments: %v",
×
1131
                        err,
×
1132
                )
×
1133
        }
×
1134

1135
        // Reconcile stack resources. Proceed on errors.
UNCOV
1136
        reconciledStacks := map[types.UID]bool{}
×
1137
        for _, id := range segsInOrder {
×
1138
                reconciledStacks[id] = true
×
1139
                sc := container.StackContainers[id]
×
1140
                err = c.ReconcileStackResources(ctx, container, sc)
×
1141
                if err != nil {
×
1142
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1143
                        c.stackLogger(container, sc).Errorf(
×
1144
                                "Unable to reconcile stack resources: %v",
×
1145
                                err,
×
1146
                        )
×
1147
                }
×
1148
        }
1149

UNCOV
1150
        for k, sc := range container.StackContainers {
×
1151
                if reconciledStacks[k] {
×
1152
                        continue
×
1153
                }
1154

UNCOV
1155
                err = c.ReconcileStackResources(ctx, container, sc)
×
1156
                if err != nil {
×
1157
                        err = c.errorEventf(sc.Stack, "FailedManageStack", err)
×
1158
                        c.stackLogger(container, sc).Errorf("Unable to reconcile stack resources: %v", err)
×
1159
                }
×
1160
        }
1161

1162
        // Reconcile stackset resources (update ingress and/or routegroups). Proceed on errors.
UNCOV
1163
        err = c.RecordTrafficSwitch(ctx, container)
×
1164
        if err != nil {
×
1165
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1166
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset resources: %v", err)
×
1167
        }
×
1168

1169
        // Reconcile desired traffic in the stackset. Proceed on errors.
UNCOV
1170
        err = c.ReconcileStackSetDesiredTraffic(ctx, container.StackSet, container.GenerateStackSetTraffic)
×
1171
        if err != nil {
×
1172
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1173
                c.stacksetLogger(container).Errorf("Unable to reconcile stackset traffic: %v", err)
×
1174
        }
×
1175

1176
        // Delete old stacks. Proceed on errors.
UNCOV
1177
        err = c.CleanupOldStacks(ctx, container)
×
1178
        if err != nil {
×
1179
                err = c.errorEventf(container.StackSet, reasonFailedManageStackSet, err)
×
1180
                c.stacksetLogger(container).Errorf("Unable to delete old stacks: %v", err)
×
1181
        }
×
1182

1183
        // Update statuses.
UNCOV
1184
        err = c.ReconcileStatuses(ctx, container)
×
1185
        if err != nil {
×
1186
                return err
×
1187
        }
×
1188

UNCOV
1189
        return nil
×
1190
}
1191

1192
// getResetMinReplicasDelay parses and returns the reset delay if set in the
1193
// stackset annotation.
1194
func getResetMinReplicasDelay(annotations map[string]string) (time.Duration, bool) {
1✔
1195
        resetDelayStr, ok := annotations[ResetHPAMinReplicasDelayAnnotationKey]
1✔
1196
        if !ok {
2✔
1197
                return 0, false
1✔
1198
        }
1✔
1199
        resetDelay, err := time.ParseDuration(resetDelayStr)
1✔
1200
        if err != nil {
1✔
UNCOV
1201
                return 0, false
×
1202
        }
×
1203
        return resetDelay, true
1✔
1204
}
1205

1206
func fixupStackSetTypeMeta(stackset *zv1.StackSet) {
1✔
1207
        // set TypeMeta manually because of this bug:
1✔
1208
        // https://github.com/kubernetes/client-go/issues/308
1✔
1209
        stackset.APIVersion = core.APIVersion
1✔
1210
        stackset.Kind = core.KindStackSet
1✔
1211
}
1✔
1212

1213
func fixupStackTypeMeta(stack *zv1.Stack) {
1✔
1214
        // set TypeMeta manually because of this bug:
1✔
1215
        // https://github.com/kubernetes/client-go/issues/308
1✔
1216
        stack.APIVersion = core.APIVersion
1✔
1217
        stack.Kind = core.KindStack
1✔
1218
}
1✔
1219

1220
// validateConfigurationResourcesNames returns an error if any ConfigurationResource
1221
// name is not prefixed by Stack name.
1222
func validateAllConfigurationResourcesNames(stack *zv1.Stack) error {
1✔
1223
        for _, rsc := range stack.Spec.ConfigurationResources {
1✔
UNCOV
1224
                if err := validateConfigurationResourceName(stack.Name, rsc.GetName()); err != nil {
×
1225
                        return err
×
1226
                }
×
1227
        }
1228
        return nil
1✔
1229
}
1230

1231
// validateConfigurationResourceName returns an error if specific resource
1232
// name is not prefixed by Stack name.
1233
func validateConfigurationResourceName(stack string, rsc string) error {
1✔
1234
        if !strings.HasPrefix(rsc, stack) {
2✔
1235
                return fmt.Errorf(configurationResourceNameError, rsc, stack)
1✔
1236
        }
1✔
1237
        return nil
1✔
1238
}
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