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

zalando-incubator / stackset-controller / 6721420047

01 Nov 2023 03:07PM UTC coverage: 73.518% (+0.3%) from 73.218%
6721420047

Pull #520

github

katyanna
Check ownerReference on reconcile tests
Pull Request #520: Reference ConfigMap for per stack versioning

137 of 137 new or added lines in 4 files covered. (100.0%)

2357 of 3206 relevant lines covered (73.52%)

0.83 hits per line

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

83.38
/controller/stack_resources.go
1
package controller
2

3
import (
4
        "context"
5

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

16
func pint32Equal(p1, p2 *int32) bool {
1✔
17
        if p1 == nil && p2 == nil {
1✔
18
                return true
×
19
        }
×
20
        if p1 != nil && p2 != nil {
2✔
21
                return *p1 == *p2
1✔
22
        }
1✔
23
        return false
×
24
}
25

26
// There are HPA metrics that depend on annotations to work properly,
27
// e.g. External RPS metric, this verification provides a way to verify
28
// all relevant annotations are actually up to date.
29
func areHPAAnnotationsUpToDate(updated, existing *v2.HorizontalPodAutoscaler) bool {
1✔
30
        if len(updated.Annotations) != len(existing.Annotations) {
1✔
31
                return false
×
32
        }
×
33

34
        for k, v := range updated.Annotations {
2✔
35
                if k == "stackset-controller.zalando.org/stack-generation" {
2✔
36
                        continue
1✔
37
                }
38

39
                existingValue, ok := existing.Annotations[k]
1✔
40
                if ok && existingValue == v {
2✔
41
                        continue
1✔
42
                }
43

44
                return false
1✔
45
        }
46

47
        return true
1✔
48
}
49

50
// syncObjectMeta copies metadata elements such as labels or annotations from source to target
51
func syncObjectMeta(target, source metav1.Object) {
1✔
52
        target.SetLabels(source.GetLabels())
1✔
53
        target.SetAnnotations(source.GetAnnotations())
1✔
54
}
1✔
55

56
func generateConfigMapName(stack *zv1.Stack, templateName string) string {
1✔
57
        return stack.Name + "-" + templateName
1✔
58
}
1✔
59

60
func (c *StackSetController) ReconcileStackDeployment(ctx context.Context, stack *zv1.Stack, existing *apps.Deployment, generateUpdated func() *apps.Deployment) error {
1✔
61
        deployment := generateUpdated()
1✔
62

1✔
63
        // Create new deployment
1✔
64
        if existing == nil {
2✔
65
                _, err := c.client.AppsV1().Deployments(deployment.Namespace).Create(ctx, deployment, metav1.CreateOptions{})
1✔
66
                if err != nil {
1✔
67
                        return err
×
68
                }
×
69
                c.recorder.Eventf(
1✔
70
                        stack,
1✔
71
                        apiv1.EventTypeNormal,
1✔
72
                        "CreatedDeployment",
1✔
73
                        "Created Deployment %s",
1✔
74
                        deployment.Name)
1✔
75
                return nil
1✔
76
        }
77

78
        // Check if we need to update the deployment
79
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) && pint32Equal(existing.Spec.Replicas, deployment.Spec.Replicas) {
2✔
80
                return nil
1✔
81
        }
1✔
82

83
        updated := existing.DeepCopy()
1✔
84
        syncObjectMeta(updated, deployment)
1✔
85
        updated.Spec = deployment.Spec
1✔
86
        updated.Spec.Selector = existing.Spec.Selector
1✔
87

1✔
88
        _, err := c.client.AppsV1().Deployments(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
89
        if err != nil {
1✔
90
                return err
×
91
        }
×
92
        c.recorder.Eventf(
1✔
93
                stack,
1✔
94
                apiv1.EventTypeNormal,
1✔
95
                "UpdatedDeployment",
1✔
96
                "Updated Deployment %s",
1✔
97
                deployment.Name)
1✔
98
        return nil
1✔
99
}
100

101
func (c *StackSetController) ReconcileStackHPA(ctx context.Context, stack *zv1.Stack, existing *v2.HorizontalPodAutoscaler, generateUpdated func() (*v2.HorizontalPodAutoscaler, error)) error {
1✔
102
        hpa, err := generateUpdated()
1✔
103
        if err != nil {
1✔
104
                return err
×
105
        }
×
106

107
        // HPA removed
108
        if hpa == nil {
2✔
109
                if existing != nil {
2✔
110
                        err := c.client.AutoscalingV2().HorizontalPodAutoscalers(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
111
                        if err != nil {
1✔
112
                                return err
×
113
                        }
×
114
                        c.recorder.Eventf(
1✔
115
                                stack,
1✔
116
                                apiv1.EventTypeNormal,
1✔
117
                                "DeletedHPA",
1✔
118
                                "Deleted HPA %s",
1✔
119
                                existing.Namespace)
1✔
120
                }
121
                return nil
1✔
122
        }
123

124
        // Create new HPA
125
        if existing == nil {
2✔
126
                _, err := c.client.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Create(ctx, hpa, metav1.CreateOptions{})
1✔
127
                if err != nil {
1✔
128
                        return err
×
129
                }
×
130
                c.recorder.Eventf(
1✔
131
                        stack,
1✔
132
                        apiv1.EventTypeNormal,
1✔
133
                        "CreatedHPA",
1✔
134
                        "Created HPA %s",
1✔
135
                        hpa.Name)
1✔
136
                return nil
1✔
137
        }
138

139
        // Check if we need to update the HPA
140
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) &&
1✔
141
                pint32Equal(existing.Spec.MinReplicas, hpa.Spec.MinReplicas) &&
1✔
142
                areHPAAnnotationsUpToDate(hpa, existing) {
2✔
143
                return nil
1✔
144
        }
1✔
145

146
        updated := existing.DeepCopy()
1✔
147
        syncObjectMeta(updated, hpa)
1✔
148
        updated.Spec = hpa.Spec
1✔
149

1✔
150
        _, err = c.client.AutoscalingV2().HorizontalPodAutoscalers(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
151
        if err != nil {
1✔
152
                return err
×
153
        }
×
154
        c.recorder.Eventf(
1✔
155
                stack,
1✔
156
                apiv1.EventTypeNormal,
1✔
157
                "UpdatedHPA",
1✔
158
                "Updated HPA %s",
1✔
159
                hpa.Name)
1✔
160
        return nil
1✔
161
}
162

163
func (c *StackSetController) ReconcileStackService(ctx context.Context, stack *zv1.Stack, existing *apiv1.Service, generateUpdated func() (*apiv1.Service, error)) error {
1✔
164
        service, err := generateUpdated()
1✔
165
        if err != nil {
1✔
166
                return err
×
167
        }
×
168

169
        // Create new service
170
        if existing == nil {
2✔
171
                _, err := c.client.CoreV1().Services(service.Namespace).Create(ctx, service, metav1.CreateOptions{})
1✔
172
                if err != nil {
1✔
173
                        return err
×
174
                }
×
175
                c.recorder.Eventf(
1✔
176
                        stack,
1✔
177
                        apiv1.EventTypeNormal,
1✔
178
                        "CreatedService",
1✔
179
                        "Created Service %s",
1✔
180
                        service.Name)
1✔
181
                return nil
1✔
182
        }
183

184
        // Check if we need to update the service
185
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
186
                return nil
1✔
187
        }
1✔
188

189
        updated := existing.DeepCopy()
1✔
190
        syncObjectMeta(updated, service)
1✔
191
        updated.Spec = service.Spec
1✔
192
        updated.Spec.ClusterIP = existing.Spec.ClusterIP // ClusterIP is immutable
1✔
193

1✔
194
        _, err = c.client.CoreV1().Services(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
195
        if err != nil {
1✔
196
                return err
×
197
        }
×
198
        c.recorder.Eventf(
1✔
199
                stack,
1✔
200
                apiv1.EventTypeNormal,
1✔
201
                "UpdatedService",
1✔
202
                "Updated Service %s",
1✔
203
                service.Name)
1✔
204
        return nil
1✔
205
}
206

207
func (c *StackSetController) ReconcileStackIngress(ctx context.Context, stack *zv1.Stack, existing *networking.Ingress, generateUpdated func() (*networking.Ingress, error)) error {
1✔
208
        ingress, err := generateUpdated()
1✔
209
        if err != nil {
1✔
210
                return err
×
211
        }
×
212

213
        // Ingress removed
214
        if ingress == nil {
2✔
215
                if existing != nil {
2✔
216
                        err := c.client.NetworkingV1().Ingresses(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
217
                        if err != nil {
1✔
218
                                return err
×
219
                        }
×
220
                        c.recorder.Eventf(
1✔
221
                                stack,
1✔
222
                                apiv1.EventTypeNormal,
1✔
223
                                "DeletedIngress",
1✔
224
                                "Deleted Ingress %s",
1✔
225
                                existing.Namespace)
1✔
226
                }
227
                return nil
1✔
228
        }
229

230
        // Create new Ingress
231
        if existing == nil {
2✔
232
                _, err := c.client.NetworkingV1().Ingresses(ingress.Namespace).Create(ctx, ingress, metav1.CreateOptions{})
1✔
233
                if err != nil {
1✔
234
                        return err
×
235
                }
×
236
                c.recorder.Eventf(
1✔
237
                        stack,
1✔
238
                        apiv1.EventTypeNormal,
1✔
239
                        "CreatedIngress",
1✔
240
                        "Created Ingress %s",
1✔
241
                        ingress.Name)
1✔
242
                return nil
1✔
243
        }
244

245
        // Check if we need to update the Ingress
246
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
247
                return nil
1✔
248
        }
1✔
249

250
        updated := existing.DeepCopy()
1✔
251
        syncObjectMeta(updated, ingress)
1✔
252
        updated.Spec = ingress.Spec
1✔
253

1✔
254
        _, err = c.client.NetworkingV1().Ingresses(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
255
        if err != nil {
1✔
256
                return err
×
257
        }
×
258
        c.recorder.Eventf(
1✔
259
                stack,
1✔
260
                apiv1.EventTypeNormal,
1✔
261
                "UpdatedIngress",
1✔
262
                "Updated Ingress %s",
1✔
263
                ingress.Name)
1✔
264
        return nil
1✔
265
}
266

267
func (c *StackSetController) ReconcileStackRouteGroup(ctx context.Context, stack *zv1.Stack, existing *rgv1.RouteGroup, generateUpdated func() (*rgv1.RouteGroup, error)) error {
1✔
268
        routegroup, err := generateUpdated()
1✔
269
        if err != nil {
1✔
270
                return err
×
271
        }
×
272

273
        // RouteGroup removed
274
        if routegroup == nil {
2✔
275
                if existing != nil {
2✔
276
                        err := c.client.RouteGroupV1().RouteGroups(existing.Namespace).Delete(ctx, existing.Name, metav1.DeleteOptions{})
1✔
277
                        if err != nil {
1✔
278
                                return err
×
279
                        }
×
280
                        c.recorder.Eventf(
1✔
281
                                stack,
1✔
282
                                apiv1.EventTypeNormal,
1✔
283
                                "DeletedRouteGroup",
1✔
284
                                "Deleted RouteGroup %s",
1✔
285
                                existing.Namespace)
1✔
286
                }
287
                return nil
1✔
288
        }
289

290
        // Create new RouteGroup
291
        if existing == nil {
2✔
292
                _, err := c.client.RouteGroupV1().RouteGroups(routegroup.Namespace).Create(ctx, routegroup, metav1.CreateOptions{})
1✔
293
                if err != nil {
1✔
294
                        return err
×
295
                }
×
296
                c.recorder.Eventf(
1✔
297
                        stack,
1✔
298
                        apiv1.EventTypeNormal,
1✔
299
                        "CreatedRouteGroup",
1✔
300
                        "Created RouteGroup %s",
1✔
301
                        routegroup.Name)
1✔
302
                return nil
1✔
303
        }
304

305
        // Check if we need to update the RouteGroup
306
        if core.IsResourceUpToDate(stack, existing.ObjectMeta) {
2✔
307
                return nil
1✔
308
        }
1✔
309

310
        updated := existing.DeepCopy()
1✔
311
        syncObjectMeta(updated, routegroup)
1✔
312
        updated.Spec = routegroup.Spec
1✔
313

1✔
314
        _, err = c.client.RouteGroupV1().RouteGroups(updated.Namespace).Update(ctx, updated, metav1.UpdateOptions{})
1✔
315
        if err != nil {
1✔
316
                return err
×
317
        }
×
318
        c.recorder.Eventf(
1✔
319
                stack,
1✔
320
                apiv1.EventTypeNormal,
1✔
321
                "UpdatedRouteGroup",
1✔
322
                "Updated RouteGroup %s",
1✔
323
                routegroup.Name)
1✔
324
        return nil
1✔
325
}
326

327
func (c *StackSetController) deleteConfigMapTemplate(ctx context.Context, stack *zv1.Stack, configmap string) error {
1✔
328
        err := c.client.CoreV1().ConfigMaps(stack.Namespace).Delete(ctx, configmap, metav1.DeleteOptions{})
1✔
329
        if err != nil {
1✔
330
                return err
×
331
        }
×
332
        c.recorder.Eventf(
1✔
333
                stack,
1✔
334
                apiv1.EventTypeNormal,
1✔
335
                "DeleteConfigMap",
1✔
336
                "Delete ConfigMap %s",
1✔
337
                configmap,
1✔
338
        )
1✔
339

1✔
340
        return nil
1✔
341
}
342

343
// Update referencings of the ConfigMap on the PodTemplate
344
// The ConfigMap name is updated to the expected versioned name in the Stack.PodTemplate
345
// to ensure Pods rely on the Stack owned resource.
346
func (c *StackSetController) updateStackConfigMap(
347
        ctx context.Context,
348
        stack *zv1.Stack,
349
        configMapNames map[string]string,
350
) error {
1✔
351
        for templateName, versionedName := range configMapNames {
2✔
352
                // Change ConfigMap reference on Stack's Volumes
1✔
353
                for _, volume := range stack.Spec.PodTemplate.Spec.Volumes {
2✔
354
                        if volume.ConfigMap != nil && volume.ConfigMap.Name == templateName {
2✔
355
                                volume.ConfigMap.Name = versionedName
1✔
356
                        }
1✔
357
                }
358

359
                // Change ConfigMap reference on Stack's EnvFrom
360
                for _, container := range stack.Spec.PodTemplate.Spec.Containers {
2✔
361
                        for _, envFrom := range container.EnvFrom {
2✔
362
                                if envFrom.ConfigMapRef != nil && envFrom.ConfigMapRef.Name == templateName {
2✔
363
                                        envFrom.ConfigMapRef.Name = versionedName
1✔
364
                                }
1✔
365
                        }
366
                }
367

368
                // Change ConfigMap reference on Stack's EnvValue
369
                for _, container := range stack.Spec.PodTemplate.Spec.Containers {
2✔
370
                        for _, env := range container.Env {
2✔
371
                                if env.ValueFrom != nil && env.ValueFrom.ConfigMapKeyRef != nil {
2✔
372
                                        if env.ValueFrom.ConfigMapKeyRef.Name == templateName {
2✔
373
                                                env.ValueFrom.ConfigMapKeyRef.Name = versionedName
1✔
374
                                        }
1✔
375
                                }
376
                        }
377
                }
378
        }
379

380
        _, err := c.client.ZalandoV1().Stacks(stack.Namespace).Update(ctx, stack, metav1.UpdateOptions{})
1✔
381
        if err != nil {
1✔
382
                return err
×
383
        }
×
384
        c.recorder.Eventf(
1✔
385
                stack,
1✔
386
                apiv1.EventTypeNormal,
1✔
387
                "UpdatedStack",
1✔
388
                "Updated Stack %s",
1✔
389
                stack.Name,
1✔
390
        )
1✔
391

1✔
392
        return nil
1✔
393
}
394

395
// Given the definition of ConfigurationResources on the StackTemplate, the Stack has
396
// its mentions of ConfigMap names updated for the expected versioned ConfigMap names,
397
// ensuring it always points to the expected resources, even if they don't exist yet.
398
//
399
// Then the Stackset Controller will search the referenced ConfigMap templates and
400
// create their versions owned by the Stack, deleting the template used after each
401
// version creation.
402
//
403
// If the Stack already has the same amount of versioned ConfigMaps as defined in the
404
// StackTemplate, the templates are deleted and the Reconcile method is exited before
405
// the version creation loop.
406
//
407
// Update of the versioned ConfigMaps is not allowed, any change to the resource must
408
// implicate the deployment of a new Stack.
409
//
410
// The deletion of the templates intends to keep a clean environment with no unused
411
// resouces, while ensuring that it'll will be used only for its intended Stack.
412
func (c *StackSetController) ReconcileStackConfigMap(
413
        ctx context.Context,
414
        stack *zv1.Stack,
415
        existing []*apiv1.ConfigMap,
416
        generateUpdated func(*apiv1.ConfigMap, string) (*apiv1.ConfigMap, error),
417
) error {
1✔
418
        if stack.Spec.ConfigurationResources == nil {
1✔
419
                return nil
×
420
        }
×
421

422
        configMaps := make(map[string]string)
1✔
423
        for _, configMap := range stack.Spec.ConfigurationResources {
2✔
424
                templateName := configMap.ConfigMapRef.Name
1✔
425
                configMaps[templateName] = generateConfigMapName(stack, templateName)
1✔
426
        }
1✔
427

428
        if len(existing) >= len(configMaps) {
2✔
429
                return nil
1✔
430
        }
1✔
431

432
        err := c.updateStackConfigMap(ctx, stack, configMaps)
1✔
433
        if err != nil {
1✔
434
                return err
×
435
        }
×
436

437
        for templateName, versionedName := range configMaps {
2✔
438
                template, err := c.client.CoreV1().ConfigMaps(stack.Namespace).Get(ctx, templateName, metav1.GetOptions{})
1✔
439
                if err != nil {
1✔
440
                        c.logger.Error(err)
×
441
                        continue
×
442
                }
443

444
                configMap, err := generateUpdated(template, versionedName)
1✔
445
                if err != nil {
1✔
446
                        return err
×
447
                }
×
448

449
                _, err = c.client.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, configMap, metav1.CreateOptions{})
1✔
450
                if err != nil {
1✔
451
                        return err
×
452
                }
×
453
                c.recorder.Eventf(
1✔
454
                        stack,
1✔
455
                        apiv1.EventTypeNormal,
1✔
456
                        "CreatedConfigMap",
1✔
457
                        "Created ConfigMap %s",
1✔
458
                        configMap.Name,
1✔
459
                )
1✔
460

1✔
461
                err = c.deleteConfigMapTemplate(ctx, stack, templateName)
1✔
462
                if err != nil {
1✔
463
                        return err
×
464
                }
×
465
        }
466

467
        return nil
1✔
468
}
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