From 1778e9c9e9dca04b463480cb6d0067dcb3181e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Sun, 26 Jan 2025 01:10:10 +0000 Subject: [PATCH 1/5] feat - pd,svc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hélia Barroso --- cmd/analyze.go | 3 + internal/analyzers/overlapping.go | 128 +++++++++++++++++++ internal/analyzers/overlapping_test.go | 162 +++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 internal/analyzers/overlapping.go create mode 100644 internal/analyzers/overlapping_test.go diff --git a/cmd/analyze.go b/cmd/analyze.go index 4d598c7..a137c15 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -33,6 +33,7 @@ const ( Prometheus AnalyzeKind = "prometheus" Alertmanager AnalyzeKind = "alertmanager" PrometheusAgent AnalyzeKind = "prometheusagent" + Overlapping AnalyzeKind = "overlapping" ) type AnalyzeFlags struct { @@ -87,6 +88,8 @@ func run(cmd *cobra.Command, _ []string) error { return analyzers.RunAlertmanagerAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace) case PrometheusAgent: return analyzers.RunPrometheusAgentAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace) + case Overlapping: + return analyzers.RunOverlappingAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace) default: return fmt.Errorf("kind %s not supported", analyzerFlags.Kind) } diff --git a/internal/analyzers/overlapping.go b/internal/analyzers/overlapping.go new file mode 100644 index 0000000..699e344 --- /dev/null +++ b/internal/analyzers/overlapping.go @@ -0,0 +1,128 @@ +// Copyright 2024 The prometheus-operator Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "context" + "fmt" + "log/slog" + "strings" + + "github.com/prometheus-operator/poctl/internal/k8sutil" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func RunOverlappingAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, _, namespace string) error { + var monitorsListErrs []string + + serviceMonitors, err := clientSets.MClient.MonitoringV1().ServiceMonitors(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + if errors.IsNotFound(err) { + monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No ServiceMonitors found in namespace %s", namespace)) + } + monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No ServiceMonitors found in namespace %s", namespace)) + } + + podMonitors, err := clientSets.MClient.MonitoringV1().PodMonitors(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + if errors.IsNotFound(err) { + monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No PodMonitors found in namespace %s", namespace)) + } + monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No PodMonitors found in namespace %s", namespace)) + } + + if len(monitorsListErrs) > 0 { + return fmt.Errorf("errors listing Pod/Service Monitors") + } + + serviceOverlaps := make(map[string][]string) + podOverlaps := make(map[string][]string) + var overlapErrs []string + + for _, servicemonitor := range serviceMonitors.Items { + if err := checkOverlappingServiceMonitors(ctx, clientSets, servicemonitor, serviceOverlaps); err != nil { + overlapErrs = append(overlapErrs, err.Error()) + } + } + for _, podmonitor := range podMonitors.Items { + if err := checkOverlappingPodMonitors(ctx, clientSets, podmonitor, podOverlaps); err != nil { + overlapErrs = append(overlapErrs, err.Error()) + } + } + + for key, svcMonitors := range serviceOverlaps { + if len(svcMonitors) > 1 { + overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping ServiceMonitors found for service/port %s: %v", key, svcMonitors)) + } + } + + for key, pdMonitors := range podOverlaps { + if len(pdMonitors) > 1 { + overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping PodMonitors found for pod/port %s: %v", key, pdMonitors)) + } + } + + if len(overlapErrs) > 0 { + return fmt.Errorf("multiple issues found:\n%s", strings.Join(overlapErrs, "\n")) + } + + slog.Info("no overlapping monitoring configurations found in", "namespace", namespace) + return nil +} + +func checkOverlappingServiceMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, servicemonitor *monitoringv1.ServiceMonitor, serviceOverlaps map[string][]string) error { + selector, err := metav1.LabelSelectorAsSelector(&servicemonitor.Spec.Selector) + if err != nil { + return fmt.Errorf("invalid selector in ServiceMonitor %s/%s: %v", servicemonitor.Namespace, servicemonitor.Name, err) + } + + services, err := clientSets.KClient.CoreV1().Services(servicemonitor.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return fmt.Errorf("error listing services for ServiceMonitor %s/%s: %v", servicemonitor.Namespace, servicemonitor.Name, err) + } + + for _, service := range services.Items { + for _, scvPort := range service.Spec.Ports { + servicekey := fmt.Sprintf("%s/%s:%d", service.Namespace, service.Name, scvPort.Port) + serviceOverlaps[servicekey] = append(serviceOverlaps[servicekey], servicemonitor.Name) + + } + } + + return nil +} + +func checkOverlappingPodMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, podmonitor *monitoringv1.PodMonitor, podOverlaps map[string][]string) error { + selector, err := metav1.LabelSelectorAsSelector(&podmonitor.Spec.Selector) + if err != nil { + return fmt.Errorf("invalid selector in PodMonitor %s/%s: %v", podmonitor.Namespace, podmonitor.Name, err) + } + + pods, err := clientSets.KClient.CoreV1().Pods(podmonitor.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return fmt.Errorf("error listing pods for PodMonitor %s/%s: %v", podmonitor.Namespace, podmonitor.Name, err) + } + + for _, pod := range pods.Items { + for _, podPort := range podmonitor.Spec.PodMetricsEndpoints { + podKey := fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, podPort.Port) + podOverlaps[podKey] = append(podOverlaps[podKey], podmonitor.Name) + } + } + + return nil +} diff --git a/internal/analyzers/overlapping_test.go b/internal/analyzers/overlapping_test.go new file mode 100644 index 0000000..ae728e4 --- /dev/null +++ b/internal/analyzers/overlapping_test.go @@ -0,0 +1,162 @@ +// Copyright 2024 The prometheus-operator Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// Copyright 2024 The prometheus-operator Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package analyzers + +import ( + "context" + "testing" + + "github.com/prometheus-operator/poctl/internal/k8sutil" + monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" +) + +func TestOverlappingAnalyzer(t *testing.T) { + type testCase struct { + name string + namespace string + getMockedClientSets func(tc testCase) k8sutil.ClientSets + shouldFail bool + } + tests := []testCase{ + { + name: "ErrorListingServiceMonitor", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.ServiceMonitorList{}) + mClient.PrependReactor("list", "servicemonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.NewNotFound(monitoringv1.Resource("servicemonitors"), tc.name) + }) + + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "ErrorListingPodMonitor", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PodMonitorList{}) + mClient.PrependReactor("list", "podmonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, nil, errors.NewNotFound(monitoringv1.Resource("podmonitors"), tc.name) + }) + + return k8sutil.ClientSets{ + MClient: mClient, + } + }, + }, + { + name: "OverlapingPodMonitor", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PodMonitorList{}) + mClient.PrependReactor("list", "podmonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.PodMonitorList{ + Items: []*monitoringv1.PodMonitor{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podmonitor-1", + Namespace: "test", + }, + Spec: monitoringv1.PodMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "overlapping-app", + }, + }, + PodMetricsEndpoints: []monitoringv1.PodMetricsEndpoint{ + {Port: "http-metrics"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "podmonitor-2", + Namespace: "test", + }, + Spec: monitoringv1.PodMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "overlapping-app", + }, + }, + PodMetricsEndpoints: []monitoringv1.PodMetricsEndpoint{ + {Port: "http-metrics"}, + }, + }, + }, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&corev1.PodList{}) + kClient.PrependReactor("list", "pods", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &corev1.PodList{ + Items: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "overlapping-pod", + Namespace: "test", + Labels: map[string]string{ + "app": "overlapping-app", + }, + }, + }, + }, + }, nil + }) + + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + clientSets := tc.getMockedClientSets(tc) + err := RunOverlappingAnalyzer(context.Background(), &clientSets, tc.name, tc.namespace) + if tc.shouldFail { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} From 7ebfaf24f7c2f0f0a4d92c43e16020a69e573668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Sun, 26 Jan 2025 01:28:45 +0000 Subject: [PATCH 2/5] feat - tst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hélia Barroso --- internal/analyzers/overlapping_test.go | 72 +++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/internal/analyzers/overlapping_test.go b/internal/analyzers/overlapping_test.go index ae728e4..d6692da 100644 --- a/internal/analyzers/overlapping_test.go +++ b/internal/analyzers/overlapping_test.go @@ -91,7 +91,7 @@ func TestOverlappingAnalyzer(t *testing.T) { { ObjectMeta: metav1.ObjectMeta{ Name: "podmonitor-1", - Namespace: "test", + Namespace: tc.namespace, }, Spec: monitoringv1.PodMonitorSpec{ Selector: metav1.LabelSelector{ @@ -107,7 +107,7 @@ func TestOverlappingAnalyzer(t *testing.T) { { ObjectMeta: metav1.ObjectMeta{ Name: "podmonitor-2", - Namespace: "test", + Namespace: tc.namespace, }, Spec: monitoringv1.PodMonitorSpec{ Selector: metav1.LabelSelector{ @@ -141,6 +141,74 @@ func TestOverlappingAnalyzer(t *testing.T) { }, nil }) + return k8sutil.ClientSets{ + MClient: mClient, + KClient: kClient, + } + }, + }, + { + name: "OverlapingServiceMonitor", + namespace: "test", + shouldFail: true, + getMockedClientSets: func(tc testCase) k8sutil.ClientSets { + mClient := monitoringclient.NewSimpleClientset(&monitoringv1.ServiceMonitorList{}) + mClient.PrependReactor("list", "servicemonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &monitoringv1.ServiceMonitorList{ + Items: []*monitoringv1.ServiceMonitor{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "servicemonitor-1", + Namespace: tc.namespace, + }, + Spec: monitoringv1.ServiceMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "overlapping-app", + }, + }, + Endpoints: []monitoringv1.Endpoint{ + {Port: "http-metrics"}, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "servicemonitor-2", + Namespace: tc.namespace, + }, + Spec: monitoringv1.ServiceMonitorSpec{ + Selector: metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "overlapping-app", + }, + }, + Endpoints: []monitoringv1.Endpoint{ + {Port: "http-metrics"}, + }, + }, + }, + }, + }, nil + }) + + kClient := fake.NewSimpleClientset(&corev1.PodList{}) + kClient.PrependReactor("list", "services", func(_ clienttesting.Action) (bool, runtime.Object, error) { + return true, &corev1.ServiceList{ + Items: []corev1.Service{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "overlapping-pod", + Namespace: "test", + Labels: map[string]string{ + "app": "overlapping-app", + }, + }, + }, + }, + }, nil + }) + return k8sutil.ClientSets{ MClient: mClient, KClient: kClient, From 8c2669377ac087feeaada806b73e4327d0ddd499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Fri, 7 Feb 2025 21:24:38 +0000 Subject: [PATCH 3/5] fix - errs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hélia Barroso --- internal/analyzers/overlapping.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/analyzers/overlapping.go b/internal/analyzers/overlapping.go index 699e344..67e824a 100644 --- a/internal/analyzers/overlapping.go +++ b/internal/analyzers/overlapping.go @@ -22,7 +22,6 @@ import ( "github.com/prometheus-operator/poctl/internal/k8sutil" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" - "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -31,22 +30,20 @@ func RunOverlappingAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, serviceMonitors, err := clientSets.MClient.MonitoringV1().ServiceMonitors(namespace).List(ctx, metav1.ListOptions{}) if err != nil { - if errors.IsNotFound(err) { - monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No ServiceMonitors found in namespace %s", namespace)) - } - monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No ServiceMonitors found in namespace %s", namespace)) + monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("Error while getting ServiceMonitors %v", err)) } podMonitors, err := clientSets.MClient.MonitoringV1().PodMonitors(namespace).List(ctx, metav1.ListOptions{}) if err != nil { - if errors.IsNotFound(err) { - monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No PodMonitors found in namespace %s", namespace)) - } - monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No PodMonitors found in namespace %s", namespace)) + monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("Error while getting No PodMonitors %v", err)) + } + + if (serviceMonitors == nil || len(serviceMonitors.Items) == 0) && (podMonitors == nil || len(podMonitors.Items) == 0) { + return nil } if len(monitorsListErrs) > 0 { - return fmt.Errorf("errors listing Pod/Service Monitors") + return fmt.Errorf("errors getting Pod/Service Monitors") } serviceOverlaps := make(map[string][]string) From 25cd04c278dd6b46c25124c464919dc449f03304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Mon, 10 Feb 2025 21:52:33 +0000 Subject: [PATCH 4/5] fix- err MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hélia Barroso --- internal/analyzers/overlapping.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/internal/analyzers/overlapping.go b/internal/analyzers/overlapping.go index 67e824a..415f550 100644 --- a/internal/analyzers/overlapping.go +++ b/internal/analyzers/overlapping.go @@ -22,30 +22,25 @@ import ( "github.com/prometheus-operator/poctl/internal/k8sutil" monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func RunOverlappingAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, _, namespace string) error { - var monitorsListErrs []string - serviceMonitors, err := clientSets.MClient.MonitoringV1().ServiceMonitors(namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("Error while getting ServiceMonitors %v", err)) + if err != nil && !errors.IsNotFound(err) { + return err } podMonitors, err := clientSets.MClient.MonitoringV1().PodMonitors(namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("Error while getting No PodMonitors %v", err)) + if err != nil && !errors.IsNotFound(err) { + return err } if (serviceMonitors == nil || len(serviceMonitors.Items) == 0) && (podMonitors == nil || len(podMonitors.Items) == 0) { return nil } - if len(monitorsListErrs) > 0 { - return fmt.Errorf("errors getting Pod/Service Monitors") - } - serviceOverlaps := make(map[string][]string) podOverlaps := make(map[string][]string) var overlapErrs []string From 79b98a4e66b9f94673551a2700b1a8af80e39ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lia=20Barroso?= Date: Thu, 20 Feb 2025 11:24:44 +0000 Subject: [PATCH 5/5] fix - error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hélia Barroso --- internal/analyzers/overlapping.go | 47 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/internal/analyzers/overlapping.go b/internal/analyzers/overlapping.go index 415f550..e9d7f10 100644 --- a/internal/analyzers/overlapping.go +++ b/internal/analyzers/overlapping.go @@ -41,33 +41,19 @@ func RunOverlappingAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, return nil } - serviceOverlaps := make(map[string][]string) - podOverlaps := make(map[string][]string) var overlapErrs []string for _, servicemonitor := range serviceMonitors.Items { - if err := checkOverlappingServiceMonitors(ctx, clientSets, servicemonitor, serviceOverlaps); err != nil { + if err := checkOverlappingServiceMonitors(ctx, clientSets, servicemonitor); err != nil { overlapErrs = append(overlapErrs, err.Error()) } } for _, podmonitor := range podMonitors.Items { - if err := checkOverlappingPodMonitors(ctx, clientSets, podmonitor, podOverlaps); err != nil { + if err := checkOverlappingPodMonitors(ctx, clientSets, podmonitor); err != nil { overlapErrs = append(overlapErrs, err.Error()) } } - for key, svcMonitors := range serviceOverlaps { - if len(svcMonitors) > 1 { - overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping ServiceMonitors found for service/port %s: %v", key, svcMonitors)) - } - } - - for key, pdMonitors := range podOverlaps { - if len(pdMonitors) > 1 { - overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping PodMonitors found for pod/port %s: %v", key, pdMonitors)) - } - } - if len(overlapErrs) > 0 { return fmt.Errorf("multiple issues found:\n%s", strings.Join(overlapErrs, "\n")) } @@ -76,7 +62,7 @@ func RunOverlappingAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, return nil } -func checkOverlappingServiceMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, servicemonitor *monitoringv1.ServiceMonitor, serviceOverlaps map[string][]string) error { +func checkOverlappingServiceMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, servicemonitor *monitoringv1.ServiceMonitor) error { selector, err := metav1.LabelSelectorAsSelector(&servicemonitor.Spec.Selector) if err != nil { return fmt.Errorf("invalid selector in ServiceMonitor %s/%s: %v", servicemonitor.Namespace, servicemonitor.Name, err) @@ -87,18 +73,27 @@ func checkOverlappingServiceMonitors(ctx context.Context, clientSets *k8sutil.Cl return fmt.Errorf("error listing services for ServiceMonitor %s/%s: %v", servicemonitor.Namespace, servicemonitor.Name, err) } + serviceOverlaps := make(map[string][]string) + var overlapErrs []string for _, service := range services.Items { - for _, scvPort := range service.Spec.Ports { - servicekey := fmt.Sprintf("%s/%s:%d", service.Namespace, service.Name, scvPort.Port) - serviceOverlaps[servicekey] = append(serviceOverlaps[servicekey], servicemonitor.Name) + for _, svcPort := range service.Spec.Ports { + serviceKey := fmt.Sprintf("%s/%s:%d", service.Namespace, service.Name, svcPort.Port) + serviceOverlaps[serviceKey] = append(serviceOverlaps[serviceKey], servicemonitor.Name) + if len(serviceOverlaps[serviceKey]) > 1 { + overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping ServiceMonitors found for service/port %s: %v", serviceKey, serviceOverlaps[serviceKey])) + } } } + if len(overlapErrs) > 0 { + return fmt.Errorf("%s", strings.Join(overlapErrs, "\n")) + } + return nil } -func checkOverlappingPodMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, podmonitor *monitoringv1.PodMonitor, podOverlaps map[string][]string) error { +func checkOverlappingPodMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, podmonitor *monitoringv1.PodMonitor) error { selector, err := metav1.LabelSelectorAsSelector(&podmonitor.Spec.Selector) if err != nil { return fmt.Errorf("invalid selector in PodMonitor %s/%s: %v", podmonitor.Namespace, podmonitor.Name, err) @@ -109,12 +104,22 @@ func checkOverlappingPodMonitors(ctx context.Context, clientSets *k8sutil.Client return fmt.Errorf("error listing pods for PodMonitor %s/%s: %v", podmonitor.Namespace, podmonitor.Name, err) } + podOverlaps := make(map[string][]string) + var overlapErrs []string for _, pod := range pods.Items { for _, podPort := range podmonitor.Spec.PodMetricsEndpoints { podKey := fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, podPort.Port) podOverlaps[podKey] = append(podOverlaps[podKey], podmonitor.Name) + + if len(podOverlaps[podKey]) > 1 { + overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping ServiceMonitors found for service/port %s: %v", podKey, podOverlaps[podKey])) + } } } + if len(overlapErrs) > 0 { + return fmt.Errorf("%s", strings.Join(overlapErrs, "\n")) + } + return nil }