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

zalando-incubator / stackset-controller / 5968491960

24 Aug 2023 08:16PM UTC coverage: 74.335% (-0.7%) from 75.025%
5968491960

Pull #518

github

gargravarr
Remove debug printfs.

Signed-off-by: Rodrigo Reis <rodrigo.gargravarr@gmail.com>
Pull Request #518: (WIP) Include ingress and/or routegroup definitions in Stack template.

138 of 138 new or added lines in 7 files covered. (100.0%)

2265 of 3047 relevant lines covered (74.34%)

0.83 hits per line

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

93.22
/pkg/core/stackset.go
1
package core
2

3
import (
4
        "encoding/json"
5
        "errors"
6
        "sort"
7

8
        rgv1 "github.com/szuecs/routegroup-client/apis/zalando.org/v1"
9
        zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
10
        corev1 "k8s.io/api/core/v1"
11
        networking "k8s.io/api/networking/v1"
12
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13
)
14

15
const (
16
        StacksetHeritageLabelKey = "stackset"
17
        StackVersionLabelKey     = "stack-version"
18

19
        ingressTrafficAuthoritativeAnnotation = "zalando.org/traffic-authoritative"
20
)
21

22
var (
23
        errNoPaths             = errors.New("invalid ingress, no paths defined")
24
        errNoStacks            = errors.New("no stacks to assign traffic to")
25
        errStackServiceBackend = errors.New("additionalBackends must not reference a Stack Service")
26
)
27

28
func currentStackVersion(stackset *zv1.StackSet) string {
1✔
29
        version := stackset.Spec.StackTemplate.Spec.Version
1✔
30
        if version == "" {
1✔
31
                version = defaultVersion
×
32
        }
×
33
        return version
1✔
34
}
35

36
func generateStackName(stackset *zv1.StackSet, version string) string {
1✔
37
        return stackset.Name + "-" + version
1✔
38
}
1✔
39

40
// sanitizeServicePorts makes sure the ports has the default fields set if not
41
// specified.
42
func sanitizeServicePorts(service *zv1.StackServiceSpec) *zv1.StackServiceSpec {
1✔
43
        for i, port := range service.Ports {
2✔
44
                // set default protocol if not specified
1✔
45
                if port.Protocol == "" {
2✔
46
                        port.Protocol = corev1.ProtocolTCP
1✔
47
                }
1✔
48
                service.Ports[i] = port
1✔
49
        }
50
        return service
1✔
51
}
52

53
// NewStack returns an (optional) stack that should be created
54
func (ssc *StackSetContainer) NewStack() (*StackContainer, string) {
1✔
55
        observedStackVersion := ssc.StackSet.Status.ObservedStackVersion
1✔
56
        stackVersion := currentStackVersion(ssc.StackSet)
1✔
57
        stackName := generateStackName(ssc.StackSet, stackVersion)
1✔
58
        stack := ssc.stackByName(stackName)
1✔
59

1✔
60
        // If the current stack doesn't exist, check that we haven't created it
1✔
61
        // before. We shouldn't recreate it if it was removed for any reason.
1✔
62
        if stack == nil && observedStackVersion != stackVersion {
2✔
63
                spec := &zv1.StackSpecInternal{}
1✔
64

1✔
65
                parentSpec := ssc.StackSet.Spec.StackTemplate.Spec.StackSpec.DeepCopy()
1✔
66
                if parentSpec.Service != nil {
1✔
67
                        parentSpec.Service = sanitizeServicePorts(parentSpec.Service)
×
68
                }
×
69
                spec.StackSpec = *parentSpec
1✔
70

1✔
71
                if ssc.StackSet.Spec.Ingress != nil {
1✔
72
                        spec.Ingress = ssc.StackSet.Spec.Ingress.DeepCopy()
×
73
                }
×
74

75
                if ssc.StackSet.Spec.ExternalIngress != nil {
1✔
76
                        spec.ExternalIngress = ssc.StackSet.Spec.ExternalIngress.DeepCopy()
×
77
                }
×
78

79
                if ssc.StackSet.Spec.RouteGroup != nil {
1✔
80
                        spec.RouteGroup = ssc.StackSet.Spec.RouteGroup.DeepCopy()
×
81
                }
×
82

83
                return &StackContainer{
1✔
84
                        Stack: &zv1.Stack{
1✔
85
                                ObjectMeta: metav1.ObjectMeta{
1✔
86
                                        Name:      stackName,
1✔
87
                                        Namespace: ssc.StackSet.Namespace,
1✔
88
                                        OwnerReferences: []metav1.OwnerReference{
1✔
89
                                                {
1✔
90
                                                        APIVersion: ssc.StackSet.APIVersion,
1✔
91
                                                        Kind:       ssc.StackSet.Kind,
1✔
92
                                                        Name:       ssc.StackSet.Name,
1✔
93
                                                        UID:        ssc.StackSet.UID,
1✔
94
                                                },
1✔
95
                                        },
1✔
96
                                        Labels: mergeLabels(
1✔
97
                                                map[string]string{
1✔
98
                                                        StacksetHeritageLabelKey: ssc.StackSet.Name,
1✔
99
                                                },
1✔
100
                                                ssc.StackSet.Labels,
1✔
101
                                                map[string]string{StackVersionLabelKey: stackVersion},
1✔
102
                                        ),
1✔
103
                                        Annotations: ssc.StackSet.Spec.StackTemplate.Annotations,
1✔
104
                                },
1✔
105
                                Spec: *spec,
1✔
106
                        },
1✔
107
                }, stackVersion
1✔
108
        }
109

110
        return nil, ""
1✔
111
}
112

113
// MarkExpiredStacks marks stacks that should be deleted
114
func (ssc *StackSetContainer) MarkExpiredStacks() {
1✔
115
        historyLimit := defaultStackLifecycleLimit
1✔
116
        if ssc.StackSet.Spec.StackLifecycle.Limit != nil {
2✔
117
                historyLimit = int(*ssc.StackSet.Spec.StackLifecycle.Limit)
1✔
118
        }
1✔
119

120
        gcCandidates := make([]*StackContainer, 0, len(ssc.StackContainers))
1✔
121

1✔
122
        for _, sc := range ssc.StackContainers {
2✔
123
                // Stacks are considered for cleanup if we don't have RouteGroup nor an ingress or if the stack is scaled down because of inactivity
1✔
124
                hasIngress := sc.routeGroupSpec != nil || sc.ingressSpec != nil || ssc.StackSet.Spec.ExternalIngress != nil
1✔
125
                if !hasIngress || sc.ScaledDown() {
2✔
126
                        gcCandidates = append(gcCandidates, sc)
1✔
127
                }
1✔
128
        }
129

130
        // only garbage collect if history limit is reached
131
        if len(gcCandidates) <= historyLimit {
2✔
132
                return
1✔
133
        }
1✔
134

135
        // sort candidates by when they last had traffic.
136
        sort.Slice(gcCandidates, func(i, j int) bool {
2✔
137
                // First check if NoTrafficSince is set. If not, fall back to the creation timestamp
1✔
138
                iTime := gcCandidates[i].noTrafficSince
1✔
139
                if iTime.IsZero() {
2✔
140
                        iTime = gcCandidates[i].Stack.CreationTimestamp.Time
1✔
141
                }
1✔
142

143
                jTime := gcCandidates[j].noTrafficSince
1✔
144
                if jTime.IsZero() {
2✔
145
                        jTime = gcCandidates[j].Stack.CreationTimestamp.Time
1✔
146
                }
1✔
147
                return iTime.Before(jTime)
1✔
148
        })
149

150
        excessStacks := len(gcCandidates) - historyLimit
1✔
151
        for _, sc := range gcCandidates[:excessStacks] {
2✔
152
                sc.PendingRemoval = true
1✔
153
        }
1✔
154
}
155

156
func (ssc *StackSetContainer) GenerateRouteGroup() (*rgv1.RouteGroup, error) {
1✔
157
        stackset := ssc.StackSet
1✔
158
        if stackset.Spec.RouteGroup == nil {
1✔
159
                return nil, nil
×
160
        }
×
161

162
        labels := mergeLabels(
1✔
163
                map[string]string{StacksetHeritageLabelKey: stackset.Name},
1✔
164
                stackset.Labels,
1✔
165
        )
1✔
166

1✔
167
        result := &rgv1.RouteGroup{
1✔
168
                ObjectMeta: metav1.ObjectMeta{
1✔
169
                        Name:        stackset.Name,
1✔
170
                        Namespace:   stackset.Namespace,
1✔
171
                        Labels:      labels,
1✔
172
                        Annotations: stackset.Spec.RouteGroup.Annotations,
1✔
173
                        OwnerReferences: []metav1.OwnerReference{
1✔
174
                                {
1✔
175
                                        APIVersion: stackset.APIVersion,
1✔
176
                                        Kind:       stackset.Kind,
1✔
177
                                        Name:       stackset.Name,
1✔
178
                                        UID:        stackset.UID,
1✔
179
                                },
1✔
180
                        },
1✔
181
                },
1✔
182
                Spec: rgv1.RouteGroupSpec{
1✔
183
                        Hosts:    stackset.Spec.RouteGroup.Hosts,
1✔
184
                        Backends: make([]rgv1.RouteGroupBackend, 0, len(ssc.StackContainers)),
1✔
185
                        Routes:   stackset.Spec.RouteGroup.Routes,
1✔
186
                },
1✔
187
        }
1✔
188

1✔
189
        // Generate backends
1✔
190
        stacks := make(map[string]struct{}, len(ssc.StackContainers))
1✔
191
        for _, sc := range ssc.StackContainers {
2✔
192
                stacks[sc.Name()] = struct{}{}
1✔
193
                result.Spec.Backends = append(result.Spec.Backends, rgv1.RouteGroupBackend{
1✔
194
                        Name:        sc.Name(),
1✔
195
                        Type:        rgv1.ServiceRouteGroupBackend,
1✔
196
                        ServiceName: sc.Name(),
1✔
197
                        ServicePort: stackset.Spec.RouteGroup.BackendPort,
1✔
198
                        Algorithm:   stackset.Spec.RouteGroup.LBAlgorithm,
1✔
199
                })
1✔
200
                if sc.actualTrafficWeight > 0 {
2✔
201
                        result.Spec.DefaultBackends = append(result.Spec.DefaultBackends, rgv1.RouteGroupBackendReference{
1✔
202
                                BackendName: sc.Name(),
1✔
203
                                Weight:      int(sc.actualTrafficWeight),
1✔
204
                        })
1✔
205
                }
1✔
206
        }
207

208
        // validate that additional backends don't overlap with the generated
209
        // backends.
210
        for _, additionalBackend := range stackset.Spec.RouteGroup.AdditionalBackends {
2✔
211
                if _, ok := stacks[additionalBackend.Name]; ok {
1✔
212
                        return nil, errStackServiceBackend
×
213
                }
×
214
                if _, ok := stacks[additionalBackend.ServiceName]; ok {
1✔
215
                        return nil, errStackServiceBackend
×
216
                }
×
217
                result.Spec.Backends = append(result.Spec.Backends, additionalBackend)
1✔
218
        }
219

220
        // sort backends/defaultBackends to ensure have a consistent generated RoutGroup resource
221
        sort.Slice(result.Spec.Backends, func(i, j int) bool {
2✔
222
                return result.Spec.Backends[i].Name < result.Spec.Backends[j].Name
1✔
223
        })
1✔
224
        sort.Slice(result.Spec.DefaultBackends, func(i, j int) bool {
2✔
225
                return result.Spec.DefaultBackends[i].BackendName < result.Spec.DefaultBackends[j].BackendName
1✔
226
        })
1✔
227

228
        return result, nil
1✔
229
}
230

231
func (ssc *StackSetContainer) GenerateIngress() (*networking.Ingress, error) {
1✔
232
        stackset := ssc.StackSet
1✔
233
        if stackset.Spec.Ingress == nil {
2✔
234
                return nil, nil
1✔
235
        }
1✔
236

237
        labels := mergeLabels(
1✔
238
                map[string]string{StacksetHeritageLabelKey: stackset.Name},
1✔
239
                stackset.Labels,
1✔
240
        )
1✔
241

1✔
242
        trafficAuthoritative := map[string]string{
1✔
243
                ingressTrafficAuthoritativeAnnotation: "false",
1✔
244
        }
1✔
245

1✔
246
        result := &networking.Ingress{
1✔
247
                ObjectMeta: metav1.ObjectMeta{
1✔
248
                        Name:        stackset.Name,
1✔
249
                        Namespace:   stackset.Namespace,
1✔
250
                        Labels:      labels,
1✔
251
                        Annotations: mergeLabels(stackset.Spec.Ingress.Annotations, trafficAuthoritative),
1✔
252
                        OwnerReferences: []metav1.OwnerReference{
1✔
253
                                {
1✔
254
                                        APIVersion: stackset.APIVersion,
1✔
255
                                        Kind:       stackset.Kind,
1✔
256
                                        Name:       stackset.Name,
1✔
257
                                        UID:        stackset.UID,
1✔
258
                                },
1✔
259
                        },
1✔
260
                },
1✔
261
                Spec: networking.IngressSpec{
1✔
262
                        Rules: make([]networking.IngressRule, 0),
1✔
263
                },
1✔
264
        }
1✔
265

1✔
266
        rule := networking.IngressRule{
1✔
267
                IngressRuleValue: networking.IngressRuleValue{
1✔
268
                        HTTP: &networking.HTTPIngressRuleValue{
1✔
269
                                Paths: make([]networking.HTTPIngressPath, 0),
1✔
270
                        },
1✔
271
                },
1✔
272
        }
1✔
273

1✔
274
        actualWeights := make(map[string]float64)
1✔
275

1✔
276
        for _, sc := range ssc.StackContainers {
2✔
277
                if sc.actualTrafficWeight > 0 {
2✔
278
                        actualWeights[sc.Name()] = sc.actualTrafficWeight
1✔
279

1✔
280
                        rule.IngressRuleValue.HTTP.Paths = append(rule.IngressRuleValue.HTTP.Paths, networking.HTTPIngressPath{
1✔
281
                                Path:     stackset.Spec.Ingress.Path,
1✔
282
                                PathType: &PathTypeImplementationSpecific,
1✔
283
                                Backend: networking.IngressBackend{
1✔
284
                                        Service: &networking.IngressServiceBackend{
1✔
285
                                                Name: sc.Name(),
1✔
286
                                                Port: networking.ServiceBackendPort{
1✔
287
                                                        Number: stackset.Spec.Ingress.BackendPort.IntVal,
1✔
288
                                                        Name:   stackset.Spec.Ingress.BackendPort.StrVal,
1✔
289
                                                },
1✔
290
                                        },
1✔
291
                                },
1✔
292
                        })
1✔
293
                }
1✔
294
        }
295

296
        if len(rule.IngressRuleValue.HTTP.Paths) == 0 {
1✔
297
                return nil, errNoPaths
×
298
        }
×
299

300
        // sort backends by name to have a consistent generated ingress resource.
301
        sort.Slice(rule.IngressRuleValue.HTTP.Paths, func(i, j int) bool {
2✔
302
                return rule.IngressRuleValue.HTTP.Paths[i].Backend.Service.Name < rule.IngressRuleValue.HTTP.Paths[j].Backend.Service.Name
1✔
303
        })
1✔
304

305
        // create rule per hostname
306
        for _, host := range stackset.Spec.Ingress.Hosts {
2✔
307
                r := rule
1✔
308
                r.Host = host
1✔
309
                result.Spec.Rules = append(result.Spec.Rules, r)
1✔
310
        }
1✔
311

312
        // sort rules by host to have a consistent generated ingress resource.
313
        sort.Slice(result.Spec.Rules, func(i, j int) bool {
2✔
314
                return result.Spec.Rules[i].Host < result.Spec.Rules[j].Host
1✔
315
        })
1✔
316

317
        actualWeightsData, err := json.Marshal(&actualWeights)
1✔
318
        if err != nil {
1✔
319
                return nil, err
×
320
        }
×
321

322
        result.Annotations[ssc.backendWeightsAnnotationKey] = string(actualWeightsData)
1✔
323
        return result, nil
1✔
324
}
325

326
func (ssc *StackSetContainer) GenerateStackSetStatus() *zv1.StackSetStatus {
1✔
327
        result := &zv1.StackSetStatus{
1✔
328
                Stacks:               0,
1✔
329
                ReadyStacks:          0,
1✔
330
                StacksWithTraffic:    0,
1✔
331
                ObservedStackVersion: ssc.StackSet.Status.ObservedStackVersion,
1✔
332
        }
1✔
333
        var traffic []*zv1.ActualTraffic
1✔
334

1✔
335
        for _, sc := range ssc.StackContainers {
2✔
336
                if sc.PendingRemoval {
2✔
337
                        continue
1✔
338
                }
339
                if sc.HasBackendPort() {
2✔
340
                        t := &zv1.ActualTraffic{
1✔
341
                                StackName:   sc.Name(),
1✔
342
                                ServiceName: sc.Name(),
1✔
343
                                ServicePort: *sc.backendPort,
1✔
344
                                Weight:      sc.actualTrafficWeight,
1✔
345
                        }
1✔
346
                        traffic = append(traffic, t)
1✔
347
                }
1✔
348

349
                result.Stacks += 1
1✔
350
                if sc.HasTraffic() {
2✔
351
                        result.StacksWithTraffic += 1
1✔
352
                }
1✔
353
                if sc.IsReady() {
2✔
354
                        result.ReadyStacks += 1
1✔
355
                }
1✔
356
        }
357
        sort.Slice(traffic, func(i, j int) bool {
2✔
358
                return traffic[i].StackName < traffic[j].StackName
1✔
359
        })
1✔
360
        result.Traffic = traffic
1✔
361
        return result
1✔
362
}
363

364
func (ssc *StackSetContainer) GenerateStackSetTraffic() []*zv1.DesiredTraffic {
1✔
365
        var traffic []*zv1.DesiredTraffic
1✔
366
        for _, sc := range ssc.StackContainers {
2✔
367
                if sc.PendingRemoval {
2✔
368
                        continue
1✔
369
                }
370
                if sc.HasBackendPort() && sc.desiredTrafficWeight > 0 {
2✔
371
                        t := &zv1.DesiredTraffic{
1✔
372
                                StackName: sc.Name(),
1✔
373
                                Weight:    sc.desiredTrafficWeight,
1✔
374
                        }
1✔
375
                        traffic = append(traffic, t)
1✔
376
                }
1✔
377
        }
378
        sort.Slice(traffic, func(i, j int) bool {
2✔
379
                return traffic[i].StackName < traffic[j].StackName
1✔
380
        })
1✔
381
        return traffic
1✔
382
}
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