1 // Copyright 2018 The Operator-SDK Authors
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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.
21 "github.com/operator-framework/operator-sdk/pkg/k8sutil"
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"
33 var log = logf.Log.WithName("metrics")
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"
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")
50 client, err := crclient.New(cfg, crclient.Options{})
52 return nil, fmt.Errorf("failed to create new client: %v", err)
54 s, err := initOperatorService(ctx, client, servicePorts)
56 if err == k8sutil.ErrNoNamespace {
57 log.Info("Skipping metrics Service creation; not running in a cluster.")
60 return nil, fmt.Errorf("failed to initialize service object for metrics: %v", err)
62 service, err := createOrUpdateService(ctx, client, s)
64 return nil, fmt.Errorf("failed to create or get service for metrics: %v", err)
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) {
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{
80 Namespace: s.Namespace,
83 s.ResourceVersion = existingService.ResourceVersion
84 if existingService.Spec.Type == v1.ServiceTypeClusterIP {
85 s.Spec.ClusterIP = existingService.Spec.ClusterIP
87 err = client.Update(ctx, s)
91 log.V(1).Info("Metrics Service object updated", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
92 return existingService, nil
95 log.Info("Metrics Service object created", "Service.Name", s.Name, "Service.Namespace", s.Namespace)
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()
105 namespace, err := k8sutil.GetOperatorNamespace()
109 label := map[string]string{"name": operatorName}
111 service := &v1.Service{
112 ObjectMeta: metav1.ObjectMeta{
113 Name: fmt.Sprintf("%s-metrics", operatorName),
114 Namespace: namespace,
117 Spec: v1.ServiceSpec{
123 ownRef, err := getPodOwnerRef(ctx, client, namespace)
127 service.SetOwnerReferences([]metav1.OwnerReference{*ownRef})
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)
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)
145 if finalOwnerRef != nil {
146 return finalOwnerRef, nil
149 // Default to returning Pod as the Owner
150 return podOwnerRefs, nil
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) {
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)
166 newOwnerRef := metav1.GetControllerOf(obj)
167 if newOwnerRef != nil {
168 return findFinalOwnerRef(ctx, client, ns, newOwnerRef)
171 log.V(1).Info("Pods owner found", "Kind", ownerRef.Kind, "Name", ownerRef.Name, "Namespace", ns)