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

kubevirt / hyperconverged-cluster-operator / 3831084774

03 Jan 2023 04:18PM UTC coverage: 85.542% (-0.07%) from 85.61%
3831084774

Pull #2109

github

Simone Tiraboschi
Consume ConsolePlugin API as v1
Pull Request #2109: Consume ConsolePlugin API as v1

20 of 20 new or added lines in 2 files covered. (100.0%)

4692 of 5485 relevant lines covered (85.54%)

0.94 hits per line

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

51.5
/pkg/util/util.go
1
package util
2

3
import (
4
        "context"
5
        "encoding/json"
6
        "fmt"
7
        "os"
8
        "reflect"
9
        "strings"
10
        "time"
11

12
        "github.com/go-logr/logr"
13
        objectreferencesv1 "github.com/openshift/custom-resource-status/objectreferences/v1"
14
        corev1 "k8s.io/api/core/v1"
15
        apierrors "k8s.io/apimachinery/pkg/api/errors"
16
        "k8s.io/apimachinery/pkg/api/meta"
17
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18
        "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
19
        "k8s.io/apimachinery/pkg/runtime"
20
        "k8s.io/apimachinery/pkg/types"
21
        "k8s.io/client-go/tools/reference"
22
        "sigs.k8s.io/controller-runtime/pkg/client"
23
)
24

25
// ForceRunModeEnv indicates if the operator should be forced to run in either local
26
// or cluster mode (currently only used for local mode)
27
var ForceRunModeEnv = "OSDK_FORCE_RUN_MODE"
28

29
type RunModeType string
30

31
const (
32
        LocalRunMode   RunModeType = "local"
33
        ClusterRunMode RunModeType = "cluster"
34

35
        // WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE
36
        // which is the namespace where the watch activity happens.
37
        // this value is empty if the operator is running with clusterScope.
38
        WatchNamespaceEnvVar = "WATCH_NAMESPACE"
39

40
        // PodNameEnvVar is the constant for env variable POD_NAME
41
        // which is the name of the current pod.
42
        PodNameEnvVar = "POD_NAME"
43
)
44

45
// ErrNoNamespace indicates that a namespace could not be found for the current
46
// environment
47
var ErrNoNamespace = fmt.Errorf("namespace not found for current environment")
48

49
// ErrRunLocal indicates that the operator is set to run in local mode (this error
50
// is returned by functions that only work on operators running in cluster mode)
51
var ErrRunLocal = fmt.Errorf("operator run mode forced to local")
52

53
func GetOperatorNamespaceFromEnv() (string, error) {
1✔
54
        if namespace, ok := os.LookupEnv(OperatorNamespaceEnv); ok {
2✔
55
                return namespace, nil
1✔
56
        }
1✔
57

58
        return "", fmt.Errorf("%s unset or empty in environment", OperatorNamespaceEnv)
1✔
59
}
60

61
func IsRunModeLocal() bool {
1✔
62
        return os.Getenv(ForceRunModeEnv) == string(LocalRunMode)
1✔
63
}
1✔
64

65
// GetOperatorNamespace returns the namespace the operator should be running in.
66
var GetOperatorNamespace = func(logger logr.Logger) (string, error) {
1✔
67
        if IsRunModeLocal() {
1✔
68
                return "", ErrRunLocal
×
69
        }
×
70
        nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
1✔
71
        if err != nil {
2✔
72
                if os.IsNotExist(err) {
2✔
73
                        return "", ErrNoNamespace
1✔
74
                }
1✔
75
                return "", err
×
76
        }
77
        ns := strings.TrimSpace(string(nsBytes))
×
78
        logger.Info("Found namespace", "Namespace", ns)
×
79
        return ns, nil
×
80
}
81

82
// GetWatchNamespace returns the namespace the operator should be watching for changes
83
func GetWatchNamespace() (string, error) {
1✔
84
        ns, found := os.LookupEnv(WatchNamespaceEnvVar)
1✔
85
        if !found {
2✔
86
                return "", fmt.Errorf("%s must be set", WatchNamespaceEnvVar)
1✔
87
        }
1✔
88
        return ns, nil
1✔
89
}
90

91
// ToUnstructured converts an arbitrary object (which MUST obey the
92
// k8s object conventions) to an Unstructured
93
func ToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
1✔
94
        b, err := json.Marshal(obj)
1✔
95
        if err != nil {
1✔
96
                return nil, err
×
97
        }
×
98
        u := &unstructured.Unstructured{}
1✔
99
        if err := json.Unmarshal(b, u); err != nil {
1✔
100
                return nil, err
×
101
        }
×
102
        return u, nil
1✔
103
}
104

105
// GetRuntimeObject will query the apiserver for the object
106
func GetRuntimeObject(ctx context.Context, c client.Client, obj client.Object, logger logr.Logger) error {
1✔
107
        key := client.ObjectKeyFromObject(obj)
1✔
108
        return c.Get(ctx, key, obj)
1✔
109
}
1✔
110

111
// ComponentResourceRemoval removes the resource `obj` if it exists and belongs to the HCO
112
// with wait=true it will wait, (util ctx timeout, please set it!) for the resource to be effectively deleted
113
func ComponentResourceRemoval(ctx context.Context, c client.Client, obj interface{}, hcoName string, logger logr.Logger, dryRun bool, wait bool, protectNonHCOObjects bool) (bool, error) {
1✔
114
        resource, err := ToUnstructured(obj)
1✔
115
        if err != nil {
1✔
116
                logger.Error(err, "Failed to convert object to Unstructured")
×
117
                return false, err
×
118
        }
×
119

120
        logger.Info("Removing resource", "name", resource.GetName(), "namespace", resource.GetNamespace(), "GVK", resource.GetObjectKind().GroupVersionKind(), "dryRun", dryRun)
1✔
121

1✔
122
        ok, err := getResourceForDeletion(ctx, c, resource, logger)
1✔
123
        if !ok {
1✔
124
                return false, err
×
125
        }
×
126

127
        if protectNonHCOObjects && !shouldDeleteResource(resource, hcoName, logger) {
1✔
128
                return false, nil
×
129
        }
×
130

131
        opts := getDeletionOption(dryRun, wait)
1✔
132

1✔
133
        if deleted, err := doDeleteResource(ctx, c, resource, opts); !deleted {
1✔
134
                return deleted, err
×
135
        }
×
136

137
        if !wait || dryRun { // no need to wait
1✔
138
                return !dryRun, nil
×
139
        }
×
140

141
        return validateDeletion(ctx, c, resource)
1✔
142
}
143

144
func doDeleteResource(ctx context.Context, c client.Client, resource *unstructured.Unstructured, opts *client.DeleteOptions) (bool, error) {
1✔
145
        err := c.Delete(ctx, resource, opts)
1✔
146
        if err != nil {
1✔
147
                if apierrors.IsNotFound(err) {
×
148
                        // to be idempotent if called on a object that was
×
149
                        // already marked for deletion in a previous reconciliation loop
×
150
                        return false, nil
×
151
                }
×
152
                // failure
153
                return false, err
×
154
        }
155
        return true, err
1✔
156
}
157

158
func shouldDeleteResource(resource *unstructured.Unstructured, hcoName string, logger logr.Logger) bool {
1✔
159
        labels := resource.GetLabels()
1✔
160
        if app, labelExists := labels[AppLabel]; !labelExists || app != hcoName {
1✔
161
                logger.Info("Existing resource wasn't deployed by HCO, ignoring", "Kind", resource.GetObjectKind())
×
162
                return false
×
163
        }
×
164
        return true
1✔
165
}
166

167
func getDeletionOption(dryRun bool, wait bool) *client.DeleteOptions {
1✔
168
        opts := &client.DeleteOptions{}
1✔
169
        if dryRun {
1✔
170
                opts.DryRun = []string{metav1.DryRunAll}
×
171
        }
×
172
        if wait {
2✔
173
                foreground := metav1.DeletePropagationForeground
1✔
174
                opts.PropagationPolicy = &foreground
1✔
175
        }
1✔
176
        return opts
1✔
177
}
178

179
func getResourceForDeletion(ctx context.Context, c client.Client, resource *unstructured.Unstructured, logger logr.Logger) (bool, error) {
1✔
180
        err := c.Get(ctx, types.NamespacedName{Name: resource.GetName(), Namespace: resource.GetNamespace()}, resource)
1✔
181
        if err != nil {
1✔
182
                if apierrors.IsNotFound(err) {
×
183
                        logger.Info("Resource doesn't exist, there is nothing to remove", "Kind", resource.GetObjectKind())
×
184
                        return false, nil
×
185
                }
×
186
                return false, err
×
187
        }
188
        return true, nil
1✔
189
}
190

191
func validateDeletion(ctx context.Context, c client.Client, resource *unstructured.Unstructured) (bool, error) {
1✔
192
        for {
2✔
193
                err := c.Get(ctx, types.NamespacedName{Name: resource.GetName(), Namespace: resource.GetNamespace()}, resource)
1✔
194
                if apierrors.IsNotFound(err) {
2✔
195
                        // success!
1✔
196
                        return true, nil
1✔
197
                }
1✔
198
                select {
×
199
                case <-ctx.Done():
×
200
                        // failed to delete in time
×
201
                        return false, fmt.Errorf("timed out waiting for %q - %q to be deleted", resource.GetObjectKind(), resource.GetName())
×
202
                case <-time.After(100 * time.Millisecond):
×
203
                        // do nothing, try again
204
                }
205
        }
206
}
207

208
// EnsureDeleted calls ComponentResourceRemoval if the runtime object exists
209
// with wait=true it will wait, (util ctx timeout, please set it!) for the resource to be effectively deleted
210
func EnsureDeleted(ctx context.Context, c client.Client, obj client.Object, hcoName string, logger logr.Logger, dryRun bool, wait bool, protectNonHCOObjects bool) (bool, error) {
1✔
211
        err := GetRuntimeObject(ctx, c, obj, logger)
1✔
212

1✔
213
        if err != nil {
2✔
214
                if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) {
2✔
215
                        logger.Info("Resource doesn't exist, there is nothing to remove", "Kind", obj.GetObjectKind())
1✔
216
                        return false, nil
1✔
217
                }
1✔
218

219
                logger.Error(err, "failed to get object from kubernetes", "Kind", obj.GetObjectKind())
×
220
                return false, err
×
221
        }
222

223
        return ComponentResourceRemoval(ctx, c, obj, hcoName, logger, dryRun, wait, protectNonHCOObjects)
1✔
224
}
225

226
func ContainsString(s []string, word string) bool {
1✔
227
        for _, w := range s {
2✔
228
                if w == word {
2✔
229
                        return true
1✔
230
                }
1✔
231
        }
232
        return false
1✔
233
}
234

235
var hcoKvIoVersion string
236

237
func GetHcoKvIoVersion() string {
×
238
        if hcoKvIoVersion == "" {
×
239
                hcoKvIoVersion = os.Getenv(HcoKvIoVersionName)
×
240
        }
×
241
        return hcoKvIoVersion
×
242
}
243

244
func AddCrToTheRelatedObjectList(relatedObjects *[]corev1.ObjectReference, found client.Object, scheme *runtime.Scheme) (bool, error) {
×
245
        // Add it to the list of RelatedObjects if found
×
246
        objectRef, err := reference.GetReference(scheme, found)
×
247
        if err != nil {
×
248
                return false, err
×
249
        }
×
250

251
        existingRef, err := objectreferencesv1.FindObjectReference(*relatedObjects, *objectRef)
×
252
        if err != nil {
×
253
                return false, err
×
254
        }
×
255
        if existingRef == nil || !reflect.DeepEqual(existingRef, objectRef) {
×
256
                err = objectreferencesv1.SetObjectReference(relatedObjects, *objectRef)
×
257
                if err != nil {
×
258
                        return false, err
×
259
                }
×
260
                // Eventually remove outdated reference with a different APIVersion
261
                for _, ref := range *relatedObjects {
×
262
                        if ref.Kind == objectRef.Kind && ref.Namespace == objectRef.Namespace && ref.Name == objectRef.Name && ref.APIVersion != objectRef.APIVersion {
×
263
                                err = objectreferencesv1.RemoveObjectReference(relatedObjects, ref)
×
264
                                if err != nil {
×
265
                                        return false, err
×
266
                                }
×
267
                        }
268
                }
269
                return true, nil
×
270
        }
271

272
        return false, nil
×
273
}
274

275
func GetLabels(hcName string, component AppComponent) map[string]string {
×
276
        return map[string]string{
×
277
                AppLabel:          hcName,
×
278
                AppLabelManagedBy: OperatorName,
×
279
                AppLabelVersion:   GetHcoKvIoVersion(),
×
280
                AppLabelPartOf:    HyperConvergedCluster,
×
281
                AppLabelComponent: string(component),
×
282
        }
×
283
}
×
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