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

zalando-incubator / stackset-controller / 16171330742

09 Jul 2025 01:56PM UTC coverage: 49.506%. Remained the same
16171330742

Pull #699

github

demonCoder95
add controller id to the logger correctly
Pull Request #699: add controller id to the logger correctly

3 of 5 new or added lines in 1 file covered. (60.0%)

98 existing lines in 1 file now uncovered.

2605 of 5262 relevant lines covered (49.51%)

0.56 hits per line

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

34.7
/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

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

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✔
108
                return nil, err
×
109
        }
×
110

111
        controllerID := "stackset"
1✔
112
        if config.ControllerID != "" {
1✔
NEW
113
                controllerID = config.ControllerID
×
NEW
114
        }
×
115

116
        return &StackSetController{
1✔
117
                logger:          log.WithFields(log.Fields{"controller": controllerID}),
1✔
118
                client:          client,
1✔
119
                config:          config,
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
                now:             now,
1✔
126
        }, nil
1✔
127
}
128

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

318
        return stacksets, nil
1✔
319
}
320

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
627
        return nil
×
628
}
629

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
755
        return toUpdate, nil
×
756
}
757

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

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

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

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

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

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

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

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

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

825
        return nil
1✔
826
}
827

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

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

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

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

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

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

×
877
        updated.Labels = ingress.Labels
×
878

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

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

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

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

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

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

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

×
942
        updated.Labels = rg.Labels
×
943

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

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

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

UNCOV
975
        return nil
×
976
}
977

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
1080
        return nil
×
1081
}
1082

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
1188
        return nil
×
1189
}
1190

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

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

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

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

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