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

sapcc / vpa_butler / 8571558215

05 Apr 2024 02:42PM UTC coverage: 86.257% (+2.0%) from 84.211%
8571558215

push

github

SuperSandro2000
Fix lints

1 of 2 new or added lines in 1 file covered. (50.0%)

34 existing lines in 3 files now uncovered.

590 of 684 relevant lines covered (86.26%)

18.05 hits per line

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

82.46
/internal/controllers/generic_controller.go
1
// Copyright 2024 SAP SE
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
package controllers
16

17
import (
18
        "context"
19
        "errors"
20
        "fmt"
21
        "reflect"
22
        "strings"
23

24
        "github.com/go-logr/logr"
25
        appsv1 "k8s.io/api/apps/v1"
26
        autoscalingv1 "k8s.io/api/autoscaling/v1"
27
        apierrors "k8s.io/apimachinery/pkg/api/errors"
28
        "k8s.io/apimachinery/pkg/runtime"
29
        "k8s.io/apimachinery/pkg/types"
30
        vpav1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
31
        ctrl "sigs.k8s.io/controller-runtime"
32
        "sigs.k8s.io/controller-runtime/pkg/client"
33
        "sigs.k8s.io/controller-runtime/pkg/controller"
34

35
        "github.com/sapcc/vpa_butler/internal/common"
36
)
37

38
const (
39
        controllerConcurrency = 10
40
        // maxNameLength is the maximum length of a vpa name.
41
        maxNameLength = 63
42
)
43

44
type GenericController struct {
45
        client.Client
46
        typeName string
47
        Log      logr.Logger
48
        Scheme   *runtime.Scheme
49
        instance client.Object
50
}
51

52
func (v *GenericController) SetupWithManager(mgr ctrl.Manager, instance client.Object) error {
3✔
53
        v.typeName = strings.ToLower(reflect.TypeOf(instance).Elem().Name())
3✔
54
        name := v.typeName + "-controller"
3✔
55
        v.Client = mgr.GetClient()
3✔
56
        v.Log = mgr.GetLogger().WithName(name)
3✔
57
        v.Scheme = mgr.GetScheme()
3✔
58
        v.instance = instance
3✔
59
        return ctrl.NewControllerManagedBy(mgr).
3✔
60
                Named(name).
3✔
61
                For(instance).
3✔
62
                WithOptions(controller.Options{MaxConcurrentReconciles: controllerConcurrency}).
3✔
63
                Complete(v)
3✔
64
}
3✔
65

66
func (v *GenericController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
47✔
67
        instance, ok := v.instance.DeepCopyObject().(client.Object)
47✔
68
        if !ok {
47✔
NEW
69
                return ctrl.Result{}, errors.New("failed to cast instance to client.Object")
×
UNCOV
70
        }
×
71
        if err := v.Get(ctx, req.NamespacedName, instance); err != nil {
69✔
72
                return ctrl.Result{}, client.IgnoreNotFound(err)
22✔
73
        }
22✔
74

75
        serve, err := v.shouldServeVpa(ctx, instance)
25✔
76
        if err != nil {
25✔
77
                return ctrl.Result{}, err
×
UNCOV
78
        }
×
79
        if !serve {
28✔
80
                err = v.ensureVpaDeleted(ctx, instance)
3✔
81
                return ctrl.Result{}, err
3✔
82
        }
3✔
83
        v.Log.Info("Serving VPA for", "name", req.Name, "namespace", req.Namespace)
22✔
84
        var vpa = new(vpav1.VerticalPodAutoscaler)
22✔
85
        vpa.Namespace = instance.GetNamespace()
22✔
86
        vpa.Name = getVpaName(instance)
22✔
87
        if err := v.Client.Get(ctx, client.ObjectKeyFromObject(vpa), vpa); err != nil {
41✔
88
                if !apierrors.IsNotFound(err) {
19✔
89
                        return ctrl.Result{}, err
×
UNCOV
90
                }
×
91
                // vpa does not exist so create it
92
                // set off here, as the vpa is to be fully configured by the VpaController
93
                common.ConfigureVpaBaseline(vpa, instance, vpav1.UpdateModeOff)
19✔
94
                return ctrl.Result{}, v.Client.Create(ctx, vpa)
19✔
95
        }
96
        return ctrl.Result{}, nil
3✔
97
}
98

99
func (v *GenericController) shouldServeVpa(ctx context.Context, vpaOwner client.Object) (bool, error) {
25✔
100
        ownerRefs := []autoscalingv1.CrossVersionObjectReference{{
25✔
101
                Name:       vpaOwner.GetName(),
25✔
102
                Kind:       vpaOwner.GetObjectKind().GroupVersionKind().Kind,
25✔
103
                APIVersion: vpaOwner.GetObjectKind().GroupVersionKind().GroupVersion().String(),
25✔
104
        }}
25✔
105
        owners := vpaOwner.GetOwnerReferences()
25✔
106
        for _, owner := range owners {
27✔
107
                ownerRefs = append(ownerRefs, autoscalingv1.CrossVersionObjectReference{
2✔
108
                        Kind:       owner.Kind,
2✔
109
                        Name:       owner.Name,
2✔
110
                        APIVersion: owner.APIVersion,
2✔
111
                })
2✔
112
        }
2✔
113

114
        var vpas vpav1.VerticalPodAutoscalerList
25✔
115
        err := v.Client.List(ctx, &vpas, client.InNamespace(vpaOwner.GetNamespace()))
25✔
116
        if err != nil {
25✔
117
                return false, fmt.Errorf("failed to list vpas: %w", err)
×
UNCOV
118
        }
×
119
        for i := range vpas.Items {
32✔
120
                vpa := vpas.Items[i]
7✔
121
                if vpa.Spec.TargetRef == nil || common.ManagedByButler(&vpa) {
11✔
122
                        continue
4✔
123
                }
124
                for _, owner := range ownerRefs {
7✔
125
                        // vpa matches the vpa owner
4✔
126
                        if vpa.Spec.TargetRef.Name == owner.Name &&
4✔
127
                                vpa.Spec.TargetRef.Kind == owner.Kind &&
4✔
128
                                vpa.Spec.TargetRef.APIVersion == owner.APIVersion {
7✔
129
                                // there is a hand-crafted vpa targeting a resource the butler cares about
3✔
130
                                // so the served vpa needs to be deleted
3✔
131
                                return false, nil
3✔
132
                        }
3✔
133
                }
134
        }
135
        // no vpa found or vpa managed by butler, so handle it
136
        return true, nil
22✔
137
}
138

139
func (v *GenericController) ensureVpaDeleted(ctx context.Context, vpaOwner client.Object) error {
3✔
140
        var vpa vpav1.VerticalPodAutoscaler
3✔
141
        ref := types.NamespacedName{Namespace: vpaOwner.GetNamespace(), Name: getVpaName(vpaOwner)}
3✔
142
        err := v.Client.Get(ctx, ref, &vpa)
3✔
143
        if apierrors.IsNotFound(err) {
6✔
144
                return nil
3✔
145
        } else if err != nil {
3✔
146
                return err
×
147
        }
×
148
        v.Log.Info("Deleting vpa as a hand-crafted vpa is already in place", "namespace", vpa.Namespace, "name", vpa.Name)
×
UNCOV
149
        return v.Client.Delete(ctx, &vpa)
×
150
}
151

152
func getVpaName(vpaOwner client.Object) string {
71✔
153
        name := vpaOwner.GetName()
71✔
154
        kind := strings.ToLower(vpaOwner.GetObjectKind().GroupVersionKind().Kind)
71✔
155
        if len(name)+len(kind) > maxNameLength {
71✔
156
                name = name[0 : len(name)-len(kind)-1]
×
UNCOV
157
        }
×
158
        return fmt.Sprintf("%s-%s", name, kind)
71✔
159
}
160

161
func SetupForAppsV1(mgr ctrl.Manager) error {
1✔
162
        deploymentController := GenericController{
1✔
163
                Client: mgr.GetClient(),
1✔
164
        }
1✔
165
        err := deploymentController.SetupWithManager(mgr, &appsv1.Deployment{})
1✔
166
        if err != nil {
1✔
167
                return fmt.Errorf("unable to setup deployment controller: %w", err)
×
UNCOV
168
        }
×
169
        daemonsetController := GenericController{
1✔
170
                Client: mgr.GetClient(),
1✔
171
        }
1✔
172
        err = daemonsetController.SetupWithManager(mgr, &appsv1.DaemonSet{})
1✔
173
        if err != nil {
1✔
174
                return fmt.Errorf("unable to setup daemonset controller: %w", err)
×
UNCOV
175
        }
×
176
        statefulSetController := GenericController{
1✔
177
                Client: mgr.GetClient(),
1✔
178
        }
1✔
179
        err = statefulSetController.SetupWithManager(mgr, &appsv1.StatefulSet{})
1✔
180
        if err != nil {
1✔
181
                return fmt.Errorf("unable to setup statefulset controller: %w", err)
×
UNCOV
182
        }
×
183
        return nil
1✔
184
}
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

© 2026 Coveralls, Inc