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

zalando-incubator / stackset-controller / 5868646333

15 Aug 2023 03:10PM UTC coverage: 74.758% (-0.3%) from 75.025%
5868646333

Pull #518

github

gargravarr
Fix CRD client generation.

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

56 of 56 new or added lines in 6 files covered. (100.0%)

2242 of 2999 relevant lines covered (74.76%)

0.84 hits per line

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

93.79
/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
        stackset := ssc.StackSet
1✔
56

1✔
57
        observedStackVersion := stackset.Status.ObservedStackVersion
1✔
58
        stackVersion := currentStackVersion(stackset)
1✔
59
        stackName := generateStackName(stackset, stackVersion)
1✔
60
        stack := ssc.stackByName(stackName)
1✔
61

1✔
62
        // If the current stack doesn't exist, check that we haven't created it before. We shouldn't recreate
1✔
63
        // it if it was removed for any reason.
1✔
64
        if stack == nil && observedStackVersion != stackVersion {
2✔
65
                internalSpec := &zv1.StackSpecInternal{}
1✔
66
                newSpec := stackset.Spec.StackTemplate.Spec.StackSpec.DeepCopy()
1✔
67
                if newSpec.Service != nil {
1✔
68
                        newSpec.Service = sanitizeServicePorts(newSpec.Service)
×
69
                }
×
70
                internalSpec.StackSpec = *newSpec
1✔
71

1✔
72
                if stackset.Spec.Ingress != nil {
1✔
73
                        internalSpec.Ingress = stackset.Spec.Ingress.DeepCopy()
×
74
                }
×
75

76
                if stackset.Spec.RouteGroup != nil {
1✔
77
                        internalSpec.RouteGroup = stackset.Spec.RouteGroup.DeepCopy()
×
78
                }
×
79

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

104
        return nil, ""
1✔
105
}
106

107
// MarkExpiredStacks marks stacks that should be deleted
108
func (ssc *StackSetContainer) MarkExpiredStacks() {
1✔
109
        historyLimit := defaultStackLifecycleLimit
1✔
110
        if ssc.StackSet.Spec.StackLifecycle.Limit != nil {
2✔
111
                historyLimit = int(*ssc.StackSet.Spec.StackLifecycle.Limit)
1✔
112
        }
1✔
113

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

1✔
116
        for _, sc := range ssc.StackContainers {
2✔
117
                // 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✔
118
                hasIngress := sc.routeGroupSpec != nil || sc.ingressSpec != nil || ssc.StackSet.Spec.ExternalIngress != nil
1✔
119
                if !hasIngress || sc.ScaledDown() {
2✔
120
                        gcCandidates = append(gcCandidates, sc)
1✔
121
                }
1✔
122
        }
123

124
        // only garbage collect if history limit is reached
125
        if len(gcCandidates) <= historyLimit {
2✔
126
                return
1✔
127
        }
1✔
128

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

137
                jTime := gcCandidates[j].noTrafficSince
1✔
138
                if jTime.IsZero() {
2✔
139
                        jTime = gcCandidates[j].Stack.CreationTimestamp.Time
1✔
140
                }
1✔
141
                return iTime.Before(jTime)
1✔
142
        })
143

144
        excessStacks := len(gcCandidates) - historyLimit
1✔
145
        for _, sc := range gcCandidates[:excessStacks] {
2✔
146
                sc.PendingRemoval = true
1✔
147
        }
1✔
148
}
149

150
func (ssc *StackSetContainer) GenerateRouteGroup() (*rgv1.RouteGroup, error) {
1✔
151
        stackset := ssc.StackSet
1✔
152
        if stackset.Spec.RouteGroup == nil {
1✔
153
                return nil, nil
×
154
        }
×
155

156
        labels := mergeLabels(
1✔
157
                map[string]string{StacksetHeritageLabelKey: stackset.Name},
1✔
158
                stackset.Labels,
1✔
159
        )
1✔
160

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

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

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

214
        // sort backends/defaultBackends to ensure have a consistent generated RoutGroup resource
215
        sort.Slice(result.Spec.Backends, func(i, j int) bool {
2✔
216
                return result.Spec.Backends[i].Name < result.Spec.Backends[j].Name
1✔
217
        })
1✔
218
        sort.Slice(result.Spec.DefaultBackends, func(i, j int) bool {
2✔
219
                return result.Spec.DefaultBackends[i].BackendName < result.Spec.DefaultBackends[j].BackendName
1✔
220
        })
1✔
221

222
        return result, nil
1✔
223
}
224

225
func (ssc *StackSetContainer) GenerateIngress() (*networking.Ingress, error) {
1✔
226
        stackset := ssc.StackSet
1✔
227
        if stackset.Spec.Ingress == nil {
2✔
228
                return nil, nil
1✔
229
        }
1✔
230

231
        labels := mergeLabels(
1✔
232
                map[string]string{StacksetHeritageLabelKey: stackset.Name},
1✔
233
                stackset.Labels,
1✔
234
        )
1✔
235

1✔
236
        trafficAuthoritative := map[string]string{
1✔
237
                ingressTrafficAuthoritativeAnnotation: "false",
1✔
238
        }
1✔
239

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

1✔
260
        rule := networking.IngressRule{
1✔
261
                IngressRuleValue: networking.IngressRuleValue{
1✔
262
                        HTTP: &networking.HTTPIngressRuleValue{
1✔
263
                                Paths: make([]networking.HTTPIngressPath, 0),
1✔
264
                        },
1✔
265
                },
1✔
266
        }
1✔
267

1✔
268
        actualWeights := make(map[string]float64)
1✔
269

1✔
270
        for _, sc := range ssc.StackContainers {
2✔
271
                if sc.actualTrafficWeight > 0 {
2✔
272
                        actualWeights[sc.Name()] = sc.actualTrafficWeight
1✔
273

1✔
274
                        rule.IngressRuleValue.HTTP.Paths = append(rule.IngressRuleValue.HTTP.Paths, networking.HTTPIngressPath{
1✔
275
                                Path:     stackset.Spec.Ingress.Path,
1✔
276
                                PathType: &PathTypeImplementationSpecific,
1✔
277
                                Backend: networking.IngressBackend{
1✔
278
                                        Service: &networking.IngressServiceBackend{
1✔
279
                                                Name: sc.Name(),
1✔
280
                                                Port: networking.ServiceBackendPort{
1✔
281
                                                        Number: stackset.Spec.Ingress.BackendPort.IntVal,
1✔
282
                                                        Name:   stackset.Spec.Ingress.BackendPort.StrVal,
1✔
283
                                                },
1✔
284
                                        },
1✔
285
                                },
1✔
286
                        })
1✔
287
                }
1✔
288
        }
289

290
        if len(rule.IngressRuleValue.HTTP.Paths) == 0 {
1✔
291
                return nil, errNoPaths
×
292
        }
×
293

294
        // sort backends by name to have a consistent generated ingress resource.
295
        sort.Slice(rule.IngressRuleValue.HTTP.Paths, func(i, j int) bool {
2✔
296
                return rule.IngressRuleValue.HTTP.Paths[i].Backend.Service.Name < rule.IngressRuleValue.HTTP.Paths[j].Backend.Service.Name
1✔
297
        })
1✔
298

299
        // create rule per hostname
300
        for _, host := range stackset.Spec.Ingress.Hosts {
2✔
301
                r := rule
1✔
302
                r.Host = host
1✔
303
                result.Spec.Rules = append(result.Spec.Rules, r)
1✔
304
        }
1✔
305

306
        // sort rules by host to have a consistent generated ingress resource.
307
        sort.Slice(result.Spec.Rules, func(i, j int) bool {
2✔
308
                return result.Spec.Rules[i].Host < result.Spec.Rules[j].Host
1✔
309
        })
1✔
310

311
        actualWeightsData, err := json.Marshal(&actualWeights)
1✔
312
        if err != nil {
1✔
313
                return nil, err
×
314
        }
×
315

316
        result.Annotations[ssc.backendWeightsAnnotationKey] = string(actualWeightsData)
1✔
317
        return result, nil
1✔
318
}
319

320
func (ssc *StackSetContainer) GenerateStackSetStatus() *zv1.StackSetStatus {
1✔
321
        result := &zv1.StackSetStatus{
1✔
322
                Stacks:               0,
1✔
323
                ReadyStacks:          0,
1✔
324
                StacksWithTraffic:    0,
1✔
325
                ObservedStackVersion: ssc.StackSet.Status.ObservedStackVersion,
1✔
326
        }
1✔
327
        var traffic []*zv1.ActualTraffic
1✔
328

1✔
329
        for _, sc := range ssc.StackContainers {
2✔
330
                if sc.PendingRemoval {
2✔
331
                        continue
1✔
332
                }
333
                if sc.HasBackendPort() {
2✔
334
                        t := &zv1.ActualTraffic{
1✔
335
                                StackName:   sc.Name(),
1✔
336
                                ServiceName: sc.Name(),
1✔
337
                                ServicePort: *sc.backendPort,
1✔
338
                                Weight:      sc.actualTrafficWeight,
1✔
339
                        }
1✔
340
                        traffic = append(traffic, t)
1✔
341
                }
1✔
342

343
                result.Stacks += 1
1✔
344
                if sc.HasTraffic() {
2✔
345
                        result.StacksWithTraffic += 1
1✔
346
                }
1✔
347
                if sc.IsReady() {
2✔
348
                        result.ReadyStacks += 1
1✔
349
                }
1✔
350
        }
351
        sort.Slice(traffic, func(i, j int) bool {
2✔
352
                return traffic[i].StackName < traffic[j].StackName
1✔
353
        })
1✔
354
        result.Traffic = traffic
1✔
355
        return result
1✔
356
}
357

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