Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / github.com / operator-framework / operator-sdk / pkg / metrics / metrics.go
1 // Copyright 2018 The Operator-SDK Authors
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 metrics
16
17 import (
18         "context"
19         "fmt"
20
21         "github.com/operator-framework/operator-sdk/pkg/k8sutil"
22
23         v1 "k8s.io/api/core/v1"
24         apierrors "k8s.io/apimachinery/pkg/api/errors"
25         metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26         "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27         "k8s.io/apimachinery/pkg/types"
28         "k8s.io/client-go/rest"
29         crclient "sigs.k8s.io/controller-runtime/pkg/client"
30         logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
31 )
32
33 var log = logf.Log.WithName("metrics")
34
35 var trueVar = true
36
37 const (
38         // OperatorPortName defines the default operator metrics port name used in the metrics Service.
39         OperatorPortName = "http-metrics"
40         // CRPortName defines the custom resource specific metrics' port name used in the metrics Service.
41         CRPortName = "cr-metrics"
42 )
43
44 // CreateMetricsService creates a Kubernetes Service to expose the passed metrics
45 // port(s) with the given name(s).
46 func CreateMetricsService(ctx context.Context, cfg *rest.Config, servicePorts []v1.ServicePort) (*v1.Service, error) {
47         if len(servicePorts) < 1 {
48                 return nil, fmt.Errorf("failed to create metrics Serice; service ports were empty")
49         }
50         client, err := crclient.New(cfg, crclient.Options{})
51         if err != nil {
52                 return nil, fmt.Errorf("failed to create new client: %v", err)
53         }
54         s, err := initOperatorService(ctx, client, servicePorts)
55         if err != nil {
56                 if err == k8sutil.ErrNoNamespace {
57                         log.Info("Skipping metrics Service creation; not running in a cluster.")
58                         return nil, nil
59                 }
60                 return nil, fmt.Errorf("failed to initialize service object for metrics: %v", err)
61         }
62         service, err := createOrUpdateService(ctx, client, s)
63         if err != nil {
64                 return nil, fmt.Errorf("failed to create or get service for metrics: %v", err)
65         }
66
67         return service, nil
68 }
69
70 func createOrUpdateService(ctx context.Context, client crclient.Client, s *v1.Service) (*v1.Service, error) {
71         if err := client.Create(ctx, s); err != nil {
72                 if !apierrors.IsAlreadyExists(err) {
73                         return nil, err
74                 }
75                 // Service already exists, we want to update it
76                 // as we do not know if any fields might have changed.
77                 existingService := &v1.Service{}
78                 err := client.Get(ctx, types.NamespacedName{
79                         Name:      s.Name,
80                         Namespace: s.Namespace,
81                 }, existingService)
82
83                 s.ResourceVersion = existingService.ResourceVersion
84                 if existingService.Spec.Type == v1.ServiceTypeClusterIP {
85                         s.Spec.ClusterIP = existingService.Spec.ClusterIP
86                 }
87                 err = client.Update(ctx, s)
88                 if err != nil {
89                         return nil, err
90                 }
91                 log.V(1).Info("Metrics Service object updated", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
92                 return existingService, nil
93         }
94
95         log.Info("Metrics Service object created", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
96         return s, nil
97 }
98
99 // initOperatorService returns the static service which exposes specified port(s).
100 func initOperatorService(ctx context.Context, client crclient.Client, sp []v1.ServicePort) (*v1.Service, error) {
101         operatorName, err := k8sutil.GetOperatorName()
102         if err != nil {
103                 return nil, err
104         }
105         namespace, err := k8sutil.GetOperatorNamespace()
106         if err != nil {
107                 return nil, err
108         }
109         label := map[string]string{"name": operatorName}
110
111         service := &v1.Service{
112                 ObjectMeta: metav1.ObjectMeta{
113                         Name:      fmt.Sprintf("%s-metrics", operatorName),
114                         Namespace: namespace,
115                         Labels:    label,
116                 },
117                 Spec: v1.ServiceSpec{
118                         Ports:    sp,
119                         Selector: label,
120                 },
121         }
122
123         ownRef, err := getPodOwnerRef(ctx, client, namespace)
124         if err != nil {
125                 return nil, err
126         }
127         service.SetOwnerReferences([]metav1.OwnerReference{*ownRef})
128
129         return service, nil
130 }
131
132 func getPodOwnerRef(ctx context.Context, client crclient.Client, ns string) (*metav1.OwnerReference, error) {
133         // Get current Pod the operator is running in
134         pod, err := k8sutil.GetPod(ctx, client, ns)
135         if err != nil {
136                 return nil, err
137         }
138         podOwnerRefs := metav1.NewControllerRef(pod, pod.GroupVersionKind())
139         // Get Owner that the Pod belongs to
140         ownerRef := metav1.GetControllerOf(pod)
141         finalOwnerRef, err := findFinalOwnerRef(ctx, client, ns, ownerRef)
142         if err != nil {
143                 return nil, err
144         }
145         if finalOwnerRef != nil {
146                 return finalOwnerRef, nil
147         }
148
149         // Default to returning Pod as the Owner
150         return podOwnerRefs, nil
151 }
152
153 // findFinalOwnerRef tries to locate the final controller/owner based on the owner reference provided.
154 func findFinalOwnerRef(ctx context.Context, client crclient.Client, ns string, ownerRef *metav1.OwnerReference) (*metav1.OwnerReference, error) {
155         if ownerRef == nil {
156                 return nil, nil
157         }
158
159         obj := &unstructured.Unstructured{}
160         obj.SetAPIVersion(ownerRef.APIVersion)
161         obj.SetKind(ownerRef.Kind)
162         err := client.Get(ctx, types.NamespacedName{Namespace: ns, Name: ownerRef.Name}, obj)
163         if err != nil {
164                 return nil, err
165         }
166         newOwnerRef := metav1.GetControllerOf(obj)
167         if newOwnerRef != nil {
168                 return findFinalOwnerRef(ctx, client, ns, newOwnerRef)
169         }
170
171         log.V(1).Info("Pods owner found", "Kind", ownerRef.Kind, "Name", ownerRef.Name, "Namespace", ns)
172         return ownerRef, nil
173 }