Skip to content

Commit 1778e9c

Browse files
committed
feat - pd,svc
Signed-off-by: Hélia Barroso <helia_barroso@hotmail.com>
1 parent 07e44b9 commit 1778e9c

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed

cmd/analyze.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const (
3333
Prometheus AnalyzeKind = "prometheus"
3434
Alertmanager AnalyzeKind = "alertmanager"
3535
PrometheusAgent AnalyzeKind = "prometheusagent"
36+
Overlapping AnalyzeKind = "overlapping"
3637
)
3738

3839
type AnalyzeFlags struct {
@@ -87,6 +88,8 @@ func run(cmd *cobra.Command, _ []string) error {
8788
return analyzers.RunAlertmanagerAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace)
8889
case PrometheusAgent:
8990
return analyzers.RunPrometheusAgentAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace)
91+
case Overlapping:
92+
return analyzers.RunOverlappingAnalyzer(cmd.Context(), clientSets, analyzerFlags.Name, analyzerFlags.Namespace)
9093
default:
9194
return fmt.Errorf("kind %s not supported", analyzerFlags.Kind)
9295
}

internal/analyzers/overlapping.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2024 The prometheus-operator 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 analyzers
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"log/slog"
21+
"strings"
22+
23+
"github.com/prometheus-operator/poctl/internal/k8sutil"
24+
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
25+
"k8s.io/apimachinery/pkg/api/errors"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
)
28+
29+
func RunOverlappingAnalyzer(ctx context.Context, clientSets *k8sutil.ClientSets, _, namespace string) error {
30+
var monitorsListErrs []string
31+
32+
serviceMonitors, err := clientSets.MClient.MonitoringV1().ServiceMonitors(namespace).List(ctx, metav1.ListOptions{})
33+
if err != nil {
34+
if errors.IsNotFound(err) {
35+
monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No ServiceMonitors found in namespace %s", namespace))
36+
}
37+
monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No ServiceMonitors found in namespace %s", namespace))
38+
}
39+
40+
podMonitors, err := clientSets.MClient.MonitoringV1().PodMonitors(namespace).List(ctx, metav1.ListOptions{})
41+
if err != nil {
42+
if errors.IsNotFound(err) {
43+
monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No PodMonitors found in namespace %s", namespace))
44+
}
45+
monitorsListErrs = append(monitorsListErrs, fmt.Sprintf("No PodMonitors found in namespace %s", namespace))
46+
}
47+
48+
if len(monitorsListErrs) > 0 {
49+
return fmt.Errorf("errors listing Pod/Service Monitors")
50+
}
51+
52+
serviceOverlaps := make(map[string][]string)
53+
podOverlaps := make(map[string][]string)
54+
var overlapErrs []string
55+
56+
for _, servicemonitor := range serviceMonitors.Items {
57+
if err := checkOverlappingServiceMonitors(ctx, clientSets, servicemonitor, serviceOverlaps); err != nil {
58+
overlapErrs = append(overlapErrs, err.Error())
59+
}
60+
}
61+
for _, podmonitor := range podMonitors.Items {
62+
if err := checkOverlappingPodMonitors(ctx, clientSets, podmonitor, podOverlaps); err != nil {
63+
overlapErrs = append(overlapErrs, err.Error())
64+
}
65+
}
66+
67+
for key, svcMonitors := range serviceOverlaps {
68+
if len(svcMonitors) > 1 {
69+
overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping ServiceMonitors found for service/port %s: %v", key, svcMonitors))
70+
}
71+
}
72+
73+
for key, pdMonitors := range podOverlaps {
74+
if len(pdMonitors) > 1 {
75+
overlapErrs = append(overlapErrs, fmt.Sprintf("Overlapping PodMonitors found for pod/port %s: %v", key, pdMonitors))
76+
}
77+
}
78+
79+
if len(overlapErrs) > 0 {
80+
return fmt.Errorf("multiple issues found:\n%s", strings.Join(overlapErrs, "\n"))
81+
}
82+
83+
slog.Info("no overlapping monitoring configurations found in", "namespace", namespace)
84+
return nil
85+
}
86+
87+
func checkOverlappingServiceMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, servicemonitor *monitoringv1.ServiceMonitor, serviceOverlaps map[string][]string) error {
88+
selector, err := metav1.LabelSelectorAsSelector(&servicemonitor.Spec.Selector)
89+
if err != nil {
90+
return fmt.Errorf("invalid selector in ServiceMonitor %s/%s: %v", servicemonitor.Namespace, servicemonitor.Name, err)
91+
}
92+
93+
services, err := clientSets.KClient.CoreV1().Services(servicemonitor.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
94+
if err != nil {
95+
return fmt.Errorf("error listing services for ServiceMonitor %s/%s: %v", servicemonitor.Namespace, servicemonitor.Name, err)
96+
}
97+
98+
for _, service := range services.Items {
99+
for _, scvPort := range service.Spec.Ports {
100+
servicekey := fmt.Sprintf("%s/%s:%d", service.Namespace, service.Name, scvPort.Port)
101+
serviceOverlaps[servicekey] = append(serviceOverlaps[servicekey], servicemonitor.Name)
102+
103+
}
104+
}
105+
106+
return nil
107+
}
108+
109+
func checkOverlappingPodMonitors(ctx context.Context, clientSets *k8sutil.ClientSets, podmonitor *monitoringv1.PodMonitor, podOverlaps map[string][]string) error {
110+
selector, err := metav1.LabelSelectorAsSelector(&podmonitor.Spec.Selector)
111+
if err != nil {
112+
return fmt.Errorf("invalid selector in PodMonitor %s/%s: %v", podmonitor.Namespace, podmonitor.Name, err)
113+
}
114+
115+
pods, err := clientSets.KClient.CoreV1().Pods(podmonitor.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
116+
if err != nil {
117+
return fmt.Errorf("error listing pods for PodMonitor %s/%s: %v", podmonitor.Namespace, podmonitor.Name, err)
118+
}
119+
120+
for _, pod := range pods.Items {
121+
for _, podPort := range podmonitor.Spec.PodMetricsEndpoints {
122+
podKey := fmt.Sprintf("%s/%s:%s", pod.Namespace, pod.Name, podPort.Port)
123+
podOverlaps[podKey] = append(podOverlaps[podKey], podmonitor.Name)
124+
}
125+
}
126+
127+
return nil
128+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright 2024 The prometheus-operator 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+
// Copyright 2024 The prometheus-operator Authors
13+
//
14+
// Licensed under the Apache License, Version 2.0 (the "License");
15+
// you may not use this file except in compliance with the License.
16+
// You may obtain a copy of the License at
17+
//
18+
// http://www.apache.org/licenses/LICENSE-2.0
19+
//
20+
// Unless required by applicable law or agreed to in writing, software
21+
// distributed under the License is distributed on an "AS IS" BASIS,
22+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23+
// See the License for the specific language governing permissions and
24+
// limitations under the License.
25+
26+
package analyzers
27+
28+
import (
29+
"context"
30+
"testing"
31+
32+
"github.com/prometheus-operator/poctl/internal/k8sutil"
33+
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
34+
monitoringclient "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned/fake"
35+
"github.com/stretchr/testify/assert"
36+
corev1 "k8s.io/api/core/v1"
37+
"k8s.io/apimachinery/pkg/api/errors"
38+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39+
"k8s.io/apimachinery/pkg/runtime"
40+
"k8s.io/client-go/kubernetes/fake"
41+
clienttesting "k8s.io/client-go/testing"
42+
)
43+
44+
func TestOverlappingAnalyzer(t *testing.T) {
45+
type testCase struct {
46+
name string
47+
namespace string
48+
getMockedClientSets func(tc testCase) k8sutil.ClientSets
49+
shouldFail bool
50+
}
51+
tests := []testCase{
52+
{
53+
name: "ErrorListingServiceMonitor",
54+
namespace: "test",
55+
shouldFail: true,
56+
getMockedClientSets: func(tc testCase) k8sutil.ClientSets {
57+
mClient := monitoringclient.NewSimpleClientset(&monitoringv1.ServiceMonitorList{})
58+
mClient.PrependReactor("list", "servicemonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) {
59+
return true, nil, errors.NewNotFound(monitoringv1.Resource("servicemonitors"), tc.name)
60+
})
61+
62+
return k8sutil.ClientSets{
63+
MClient: mClient,
64+
}
65+
},
66+
},
67+
{
68+
name: "ErrorListingPodMonitor",
69+
namespace: "test",
70+
shouldFail: true,
71+
getMockedClientSets: func(tc testCase) k8sutil.ClientSets {
72+
mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PodMonitorList{})
73+
mClient.PrependReactor("list", "podmonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) {
74+
return true, nil, errors.NewNotFound(monitoringv1.Resource("podmonitors"), tc.name)
75+
})
76+
77+
return k8sutil.ClientSets{
78+
MClient: mClient,
79+
}
80+
},
81+
},
82+
{
83+
name: "OverlapingPodMonitor",
84+
namespace: "test",
85+
shouldFail: true,
86+
getMockedClientSets: func(tc testCase) k8sutil.ClientSets {
87+
mClient := monitoringclient.NewSimpleClientset(&monitoringv1.PodMonitorList{})
88+
mClient.PrependReactor("list", "podmonitors", func(_ clienttesting.Action) (bool, runtime.Object, error) {
89+
return true, &monitoringv1.PodMonitorList{
90+
Items: []*monitoringv1.PodMonitor{
91+
{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: "podmonitor-1",
94+
Namespace: "test",
95+
},
96+
Spec: monitoringv1.PodMonitorSpec{
97+
Selector: metav1.LabelSelector{
98+
MatchLabels: map[string]string{
99+
"app": "overlapping-app",
100+
},
101+
},
102+
PodMetricsEndpoints: []monitoringv1.PodMetricsEndpoint{
103+
{Port: "http-metrics"},
104+
},
105+
},
106+
},
107+
{
108+
ObjectMeta: metav1.ObjectMeta{
109+
Name: "podmonitor-2",
110+
Namespace: "test",
111+
},
112+
Spec: monitoringv1.PodMonitorSpec{
113+
Selector: metav1.LabelSelector{
114+
MatchLabels: map[string]string{
115+
"app": "overlapping-app",
116+
},
117+
},
118+
PodMetricsEndpoints: []monitoringv1.PodMetricsEndpoint{
119+
{Port: "http-metrics"},
120+
},
121+
},
122+
},
123+
},
124+
}, nil
125+
})
126+
127+
kClient := fake.NewSimpleClientset(&corev1.PodList{})
128+
kClient.PrependReactor("list", "pods", func(_ clienttesting.Action) (bool, runtime.Object, error) {
129+
return true, &corev1.PodList{
130+
Items: []corev1.Pod{
131+
{
132+
ObjectMeta: metav1.ObjectMeta{
133+
Name: "overlapping-pod",
134+
Namespace: "test",
135+
Labels: map[string]string{
136+
"app": "overlapping-app",
137+
},
138+
},
139+
},
140+
},
141+
}, nil
142+
})
143+
144+
return k8sutil.ClientSets{
145+
MClient: mClient,
146+
KClient: kClient,
147+
}
148+
},
149+
},
150+
}
151+
for _, tc := range tests {
152+
t.Run(tc.name, func(t *testing.T) {
153+
clientSets := tc.getMockedClientSets(tc)
154+
err := RunOverlappingAnalyzer(context.Background(), &clientSets, tc.name, tc.namespace)
155+
if tc.shouldFail {
156+
assert.Error(t, err)
157+
} else {
158+
assert.NoError(t, err)
159+
}
160+
})
161+
}
162+
}

0 commit comments

Comments
 (0)