1 package prometheusctl
2
3 import (
4 "context"
5 _ "embed"
6 "fmt"
7 "os"
8 "strings"
9 "testing"
10 "time"
11
12 monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/suite"
15 corev1 "k8s.io/api/core/v1"
16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17 ctrl "sigs.k8s.io/controller-runtime"
18
19 "edge-infra.dev/pkg/edge/constants/api/cluster"
20 "edge-infra.dev/pkg/f8n/warehouse/pallet"
21 "edge-infra.dev/pkg/k8s/decoder"
22 "edge-infra.dev/pkg/k8s/meta"
23 eclient "edge-infra.dev/pkg/k8s/runtime/client"
24 "edge-infra.dev/pkg/k8s/runtime/controller"
25 "edge-infra.dev/pkg/k8s/unstructured"
26 "edge-infra.dev/pkg/lib/logging"
27 "edge-infra.dev/test"
28 "edge-infra.dev/test/framework"
29 "edge-infra.dev/test/framework/k8s"
30 "edge-infra.dev/test/framework/k8s/envtest"
31 )
32
33 var (
34
35 testEnv *envtest.Environment
36 monitors []*unstructured.Unstructured
37
38
39 expectedMetricsData string
40
41 monitorsData []byte
42 )
43
44 type Suite struct {
45 *framework.Framework
46 *k8s.K8s
47
48 ctx context.Context
49 timeout time.Duration
50 tick time.Duration
51
52
53 cfg config
54
55 expectedMetrics []string
56 }
57
58 func TestMain(m *testing.M) {
59 ctrl.SetLogger(logging.NewLogger().Logger)
60
61
62 framework.HandleFlags()
63
64 os.Exit(m.Run())
65 }
66
67 func TestController(t *testing.T) {
68
69 testEnv = envtest.Setup()
70
71
72 var err error
73 monitors, err = decoder.DecodeYAML(monitorsData)
74 test.NoError(err)
75
76
77 t.Run(cluster.GKE, func(t *testing.T) {
78 s := buildSuite(cluster.GKE)
79 suite.Run(t, s)
80 })
81
82 t.Run(cluster.Generic, func(t *testing.T) {
83 s := buildSuite(cluster.Generic)
84 suite.Run(t, s)
85 })
86
87 t.Cleanup(func() {
88 test.NoError(testEnv.Stop())
89 })
90 }
91
92 func (s *Suite) TestPrometheusPatch() {
93
94
95 palletAnnos := map[string]string{
96 pallet.KnnotationName: "prometheus",
97 pallet.KnnotationTeam: "ncrvoyix-swt-retail/edge-o11y",
98 pallet.KnnotationRevision: "c3918f",
99 pallet.KnnotationVersion: "0.3.7",
100 }
101 p := s.prometheus(monitoringv1.PrometheusSpec{})
102 p.Annotations = palletAnnos
103 p.Spec.ExternalLabels = map[string]string{}
104 s.NoError(s.Client.Create(s.ctx, p))
105 s.Log("created prometheus", eclient.ObjectKeyFromObject(p))
106 s.testContainer(false)
107 s.testVolumes()
108
109
110 s.NoError(s.Client.Delete(s.ctx, p))
111 p = s.prometheus(monitoringv1.PrometheusSpec{CommonPrometheusFields: monitoringv1.CommonPrometheusFields{
112 Containers: []corev1.Container{{
113 Name: "prometheus",
114 }},
115 }})
116 p.Annotations = palletAnnos
117 p.Spec.ExternalLabels = map[string]string{}
118 s.NoError(s.Client.Create(s.ctx, p))
119 s.testContainer(false)
120 s.testVolumes()
121
122
123 s.NoError(s.Client.Delete(s.ctx, p))
124 }
125
126 func (s *Suite) testContainer(legacy bool) {
127 var pcontainer corev1.Container
128 s.Eventually(func() bool {
129 p := &monitoringv1.Prometheus{}
130 if err := s.Client.Get(s.ctx, eclient.ObjectKeyFromRef(s.cfg.prometheus), p); err != nil {
131 return false
132 }
133
134 var err error
135 pcontainer, err = prometheusContainer(p.Spec.Containers)
136
137 return err == nil && len(pcontainer.Args) > 0
138 }, s.timeout, s.tick)
139
140 found := make(map[string]bool, len(s.expectedMetrics))
141 for _, metric := range s.expectedMetrics {
142 found[metric] = false
143 for _, arg := range pcontainer.Args {
144 if arg == metricArg(metric) {
145 found[metric] = true
146 }
147 }
148 }
149
150 for k, v := range found {
151 s.True(v, "expected metric %s not found", k)
152 }
153
154 s.Equal(dedupe(pcontainer.Args), pcontainer.Args)
155
156 var (
157 webEnableLifecycle = false
158 configFile = false
159 )
160 for _, a := range pcontainer.Args {
161 switch a {
162 case "--config.file=/etc/prometheus/config_out/prometheus.env.yaml":
163 configFile = true
164 case "--web.enable-lifecycle":
165 webEnableLifecycle = true
166 }
167 }
168
169 s.True(webEnableLifecycle, "lifecycle argument not present")
170 s.True(configFile, "config file argument not present")
171
172 if legacy || s.cfg.clusterProvider != cluster.GKE {
173 s.Equal(envVars(), pcontainer.Env)
174 }
175 }
176
177 func (s *Suite) testVolumes() {
178 if s.cfg.clusterProvider == cluster.GKE {
179 return
180 }
181
182 var p monitoringv1.Prometheus
183 s.Eventually(func() bool {
184 if err := s.Client.Get(s.ctx, eclient.ObjectKeyFromRef(s.cfg.prometheus), &p); err != nil {
185 return false
186 }
187 return len(p.Spec.Volumes) > 0 && len(p.Spec.VolumeMounts) > 0
188 }, s.timeout, s.tick)
189
190 vols, mounts := volumeCfg()
191 s.Equal(vols, p.Spec.Volumes)
192 s.Equal(mounts, p.Spec.VolumeMounts)
193 }
194
195 func (s *Suite) prometheus(spec monitoringv1.PrometheusSpec) *monitoringv1.Prometheus {
196 return &monitoringv1.Prometheus{
197 ObjectMeta: metav1.ObjectMeta{
198 Name: s.cfg.prometheus.Name,
199 Namespace: s.cfg.prometheus.Namespace,
200 },
201 Spec: spec,
202 }
203 }
204
205 func (s *Suite) setup(_ *framework.Framework) {
206
207 s.Require().NoError(s.Client.Create(s.ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
208 Name: s.cfg.prometheus.Namespace,
209 }}))
210 s.Log("created namespace", s.cfg.prometheus.Namespace)
211
212 for _, o := range monitors {
213 c := o.DeepCopy()
214 c.SetNamespace(s.cfg.prometheus.Namespace)
215 s.Require().NoError(s.Client.Create(s.ctx, c))
216 s.Log("created monitor", c.GetName(), c.GetNamespace())
217 }
218 }
219
220 func (s *Suite) teardown(_ *framework.Framework) {
221 s.Require().NoError(s.Client.Delete(s.ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{
222 Name: s.cfg.prometheus.Namespace,
223 }}))
224 }
225
226
227
228 func buildSuite(provider cluster.Type) *Suite {
229 cfg := &config{
230 reconcileInterval: time.Millisecond * 5,
231 prometheus: meta.NamespacedObjectReference{
232 Name: fmt.Sprintf("prometheus-%s", provider),
233 Namespace: fmt.Sprintf("prometheus-%s", provider),
234 },
235 clusterProvider: string(provider),
236 }
237 mgr, err := create(
238 cfg,
239 controller.WithCfg(testEnv.Config),
240 controller.WithMetricsAddress("0"),
241 )
242 test.NoError(err)
243
244
245 k := k8s.New(testEnv.Config, k8s.WithCtrlManager(mgr))
246 f := framework.New("prometheusctl").
247 Component("prometheusctl").
248 Register(k)
249
250
251 s := &Suite{
252 Framework: f,
253 K8s: k,
254 ctx: context.Background(),
255 timeout: 1 * time.Second,
256 tick: 10 * time.Millisecond,
257 expectedMetrics: strings.Split(strings.TrimSpace(expectedMetricsData), "\n"),
258 cfg: *cfg,
259 }
260
261
262 f.Setup(s.setup)
263 f.Teardown(s.teardown)
264
265 return s
266 }
267
268 func TestParseMetricsAnnotations(t *testing.T) {
269 tcs := map[string]struct {
270 meta metav1.ObjectMeta
271 expected map[string]bool
272 }{
273 "simple": {
274 meta: objMeta(map[string]string{allowedMetrics: `
275 kube_node_info
276 kube_node_labels
277 {__name__=~"kube_pod_.*"}
278 `,
279 }),
280 expected: map[string]bool{"kube_node_info": true, "kube_node_labels": true, `{__name__=~"kube_pod_.*"}`: true},
281 },
282 "empty annotation": {
283 meta: objMeta(map[string]string{allowedMetrics: ""}),
284 expected: map[string]bool{},
285 },
286 "no annotation": {
287 meta: objMeta(map[string]string{}),
288 expected: map[string]bool{},
289 },
290 }
291
292 for name, tc := range tcs {
293 t.Run(name, func(t *testing.T) {
294 r := &PrometheusStackdriverReconciler{
295 metricsList: make(map[string]bool),
296 }
297 r.fetchMetricsAnnotations(tc.meta)
298 assert.Equal(t, tc.expected, r.metricsList)
299 })
300 }
301 }
302
303 func objMeta(annos map[string]string) metav1.ObjectMeta {
304 return metav1.ObjectMeta{
305 Annotations: annos,
306 }
307 }
308
View as plain text