github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/resource-recommend/datasource/prometheus/prometheus_provider_test.go (about) 1 /* 2 Copyright 2022 The Katalyst Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package prometheus 18 19 import ( 20 "context" 21 "fmt" 22 "reflect" 23 "testing" 24 "time" 25 26 promapiv1 "github.com/prometheus/client_golang/api/prometheus/v1" 27 v1 "github.com/prometheus/client_golang/api/prometheus/v1" 28 "github.com/prometheus/common/model" 29 corev1 "k8s.io/api/core/v1" 30 31 datasourcetypes "github.com/kubewharf/katalyst-core/pkg/util/resource-recommend/types/datasource" 32 ) 33 34 func Test_prometheus_ConvertMetricToQuery(t *testing.T) { 35 p := &prometheus{ 36 config: &PromConfig{}, 37 } 38 tests := []struct { 39 name string 40 metric datasourcetypes.Metric 41 expectedQuery *datasourcetypes.Query 42 expectedError error 43 }{ 44 { 45 name: "CPU metric", 46 metric: datasourcetypes.Metric{ 47 Resource: corev1.ResourceCPU, 48 Namespace: "my-namespace", 49 WorkloadName: "my-workload", 50 Kind: "Deployment", 51 ContainerName: "my-container", 52 }, 53 expectedQuery: &datasourcetypes.Query{ 54 Prometheus: &datasourcetypes.PrometheusQuery{ 55 Query: GetContainerCpuUsageQueryExp("my-namespace", "my-workload", "Deployment", "my-container", ""), 56 }, 57 }, 58 expectedError: nil, 59 }, 60 { 61 name: "Memory metric", 62 metric: datasourcetypes.Metric{ 63 Resource: corev1.ResourceMemory, 64 Namespace: "my-namespace", 65 WorkloadName: "my-workload", 66 Kind: "Deployment", 67 ContainerName: "my-container", 68 }, 69 expectedQuery: &datasourcetypes.Query{ 70 Prometheus: &datasourcetypes.PrometheusQuery{ 71 Query: GetContainerMemUsageQueryExp("my-namespace", "my-workload", "Deployment", "my-container", ""), 72 }, 73 }, 74 expectedError: nil, 75 }, 76 { 77 name: "Unsupported metric", 78 metric: datasourcetypes.Metric{ 79 Resource: "Unsupported", 80 Namespace: "my-namespace", 81 WorkloadName: "my-workload", 82 Kind: "Deployment", 83 ContainerName: "my-container", 84 }, 85 expectedQuery: nil, 86 expectedError: fmt.Errorf("query for resource type Unsupported is not supported"), 87 }, 88 } 89 for _, tt := range tests { 90 t.Run(tt.name, func(t *testing.T) { 91 gotQuery, gotErr := p.ConvertMetricToQuery(tt.metric) 92 93 if !reflect.DeepEqual(gotQuery, tt.expectedQuery) { 94 t.Errorf("ConvertMetricToQuery() gotQuery = %v, want %v", gotQuery, tt.expectedQuery) 95 } 96 97 if (gotErr == nil && tt.expectedError != nil) || (gotErr != nil && tt.expectedError == nil) || (gotErr != nil && tt.expectedError != nil && gotErr.Error() != tt.expectedError.Error()) { 98 t.Errorf("ConvertMetricToQuery() gotErr = %v, wantErr %v", gotErr, tt.expectedError) 99 } 100 }) 101 } 102 } 103 104 func Test_prometheus_QueryTimeSeries(t *testing.T) { 105 type fields struct { 106 promAPIClient v1.API 107 config *PromConfig 108 } 109 type args struct { 110 query *datasourcetypes.Query 111 start time.Time 112 end time.Time 113 step time.Duration 114 } 115 tests := []struct { 116 name string 117 fields fields 118 args args 119 want *datasourcetypes.TimeSeries 120 wantErr bool 121 }{ 122 { 123 name: "Successful query time series", 124 fields: fields{ 125 promAPIClient: &mockPromAPIClient{ 126 QueryRangeFunc: func(ctx context.Context, query string, r promapiv1.Range) (model.Value, promapiv1.Warnings, error) { 127 matrix := model.Matrix{ 128 &model.SampleStream{ 129 Metric: model.Metric{ 130 "label1": "value1", 131 "label2": "value2", 132 }, 133 Values: []model.SamplePair{ 134 { 135 Timestamp: 1500000000, 136 Value: 10, 137 }, 138 { 139 Timestamp: 1500001000, 140 Value: 20, 141 }, 142 }, 143 }, 144 } 145 return matrix, nil, nil 146 }, 147 }, 148 config: &PromConfig{ 149 Timeout: time.Second * 5, 150 }, 151 }, 152 args: args{ 153 query: &datasourcetypes.Query{ 154 Prometheus: &datasourcetypes.PrometheusQuery{ 155 Query: "my_query", 156 }, 157 }, 158 start: time.Unix(1500000000, 0), 159 end: time.Unix(1500002000, 0), 160 step: time.Second, 161 }, 162 want: &datasourcetypes.TimeSeries{ 163 Samples: []datasourcetypes.Sample{ 164 { 165 Value: 10, 166 Timestamp: 1500000, 167 }, 168 { 169 Value: 20, 170 Timestamp: 1500001, 171 }, 172 }, 173 Labels: map[string]string{ 174 "label1": "value1", 175 "label2": "value2", 176 }, 177 }, 178 wantErr: false, 179 }, 180 { 181 name: "Query time series error", 182 fields: fields{ 183 promAPIClient: &mockPromAPIClient{ 184 QueryRangeFunc: func(ctx context.Context, query string, r promapiv1.Range) (model.Value, promapiv1.Warnings, error) { 185 return nil, nil, fmt.Errorf("query error") 186 }, 187 }, 188 config: &PromConfig{ 189 Timeout: time.Second * 5, 190 }, 191 }, 192 args: args{ 193 query: &datasourcetypes.Query{ 194 Prometheus: &datasourcetypes.PrometheusQuery{ 195 Query: "my_query", 196 }, 197 }, 198 start: time.Unix(1500000000, 0), 199 end: time.Unix(1500002000, 0), 200 step: time.Second, 201 }, 202 want: nil, 203 wantErr: true, 204 }, 205 } 206 for _, tt := range tests { 207 t.Run(tt.name, func(t *testing.T) { 208 p := &prometheus{ 209 promAPIClient: tt.fields.promAPIClient, 210 config: tt.fields.config, 211 } 212 got, err := p.QueryTimeSeries(tt.args.query, tt.args.start, tt.args.end, tt.args.step) 213 if (err != nil) != tt.wantErr { 214 t.Errorf("QueryTimeSeries() error = %v, wantErr %v", err, tt.wantErr) 215 return 216 } 217 if !reflect.DeepEqual(got, tt.want) { 218 t.Errorf("QueryTimeSeries() got = %v, want %v", got, tt.want) 219 } 220 }) 221 } 222 } 223 224 func Test_prometheus_convertPromResultsToTimeSeries(t *testing.T) { 225 type fields struct { 226 promAPIClient v1.API 227 config *PromConfig 228 } 229 type args struct { 230 value model.Value 231 } 232 tests := []struct { 233 name string 234 fields fields 235 args args 236 want *datasourcetypes.TimeSeries 237 wantErr bool 238 }{ 239 { 240 name: "Matrix value", 241 fields: fields{ 242 promAPIClient: nil, 243 config: nil, 244 }, 245 args: args{ 246 value: model.Matrix{ 247 &model.SampleStream{ 248 Metric: model.Metric{ 249 "label1": "value1", 250 "label2": "value2", 251 }, 252 Values: []model.SamplePair{ 253 { 254 Timestamp: 1500000000, 255 Value: 10, 256 }, 257 { 258 Timestamp: 1500001000, 259 Value: 20, 260 }, 261 }, 262 }, 263 }, 264 }, 265 want: &datasourcetypes.TimeSeries{ 266 Samples: []datasourcetypes.Sample{ 267 { 268 Value: 10, 269 Timestamp: 1500000, 270 }, 271 { 272 Value: 20, 273 Timestamp: 1500001, 274 }, 275 }, 276 Labels: map[string]string{ 277 "label1": "value1", 278 "label2": "value2", 279 }, 280 }, 281 wantErr: false, 282 }, 283 { 284 name: "Vector value", 285 fields: fields{ 286 promAPIClient: nil, 287 config: nil, 288 }, 289 args: args{ 290 value: model.Vector{ 291 &model.Sample{ 292 Metric: model.Metric{ 293 "label1": "value1", 294 "label2": "value2", 295 }, 296 Timestamp: 1500000000, 297 Value: 10, 298 }, 299 }, 300 }, 301 want: &datasourcetypes.TimeSeries{ 302 Samples: []datasourcetypes.Sample{ 303 { 304 Value: 10, 305 Timestamp: 1500000, 306 }, 307 }, 308 Labels: map[string]string{ 309 "label1": "value1", 310 "label2": "value2", 311 }, 312 }, 313 wantErr: false, 314 }, 315 { 316 name: "Unsupported value type", 317 fields: fields{ 318 promAPIClient: nil, 319 config: nil, 320 }, 321 args: args{ 322 value: &model.Scalar{}, 323 }, 324 want: datasourcetypes.NewTimeSeries(), 325 wantErr: true, 326 }, 327 } 328 for _, tt := range tests { 329 t.Run(tt.name, func(t *testing.T) { 330 p := &prometheus{ 331 promAPIClient: tt.fields.promAPIClient, 332 config: tt.fields.config, 333 } 334 got, err := p.convertPromResultsToTimeSeries(tt.args.value) 335 if (err != nil) != tt.wantErr { 336 t.Errorf("convertPromResultsToTimeSeries() error = %v, wantErr %v", err, tt.wantErr) 337 return 338 } 339 if !reflect.DeepEqual(got, tt.want) { 340 t.Errorf("convertPromResultsToTimeSeries() got = %v, want %v", got, tt.want) 341 } 342 }) 343 } 344 } 345 346 func Test_prometheus_queryRangeSync(t *testing.T) { 347 type fields struct { 348 promAPIClient v1.API 349 config *PromConfig 350 } 351 type args struct { 352 ctx context.Context 353 query string 354 start time.Time 355 end time.Time 356 step time.Duration 357 } 358 tests := []struct { 359 name string 360 fields fields 361 args args 362 want *datasourcetypes.TimeSeries 363 wantErr bool 364 }{ 365 { 366 name: "Successful query range", 367 fields: fields{ 368 promAPIClient: &mockPromAPIClient{ 369 QueryRangeFunc: func(ctx context.Context, query string, r promapiv1.Range) (model.Value, promapiv1.Warnings, error) { 370 matrix := model.Matrix{ 371 &model.SampleStream{ 372 Metric: model.Metric{ 373 "label1": "value1", 374 "label2": "value2", 375 }, 376 Values: []model.SamplePair{ 377 { 378 Timestamp: 1500000000, 379 Value: 10, 380 }, 381 { 382 Timestamp: 1500001000, 383 Value: 20, 384 }, 385 }, 386 }, 387 } 388 return matrix, nil, nil 389 }, 390 }, 391 config: nil, 392 }, 393 args: args{ 394 ctx: context.TODO(), 395 query: "my_query", 396 start: time.Unix(1500000000, 0), 397 end: time.Unix(1500002000, 0), 398 step: time.Second, 399 }, 400 want: &datasourcetypes.TimeSeries{ 401 Samples: []datasourcetypes.Sample{ 402 { 403 Value: 10, 404 Timestamp: 1500000, 405 }, 406 { 407 Value: 20, 408 Timestamp: 1500001, 409 }, 410 }, 411 Labels: map[string]string{ 412 "label1": "value1", 413 "label2": "value2", 414 }, 415 }, 416 wantErr: false, 417 }, 418 { 419 name: "Error in query range", 420 fields: fields{ 421 promAPIClient: &mockPromAPIClient{ 422 QueryRangeFunc: func(ctx context.Context, query string, r promapiv1.Range) (model.Value, promapiv1.Warnings, error) { 423 return nil, nil, fmt.Errorf("query error") 424 }, 425 }, 426 config: nil, 427 }, 428 args: args{ 429 ctx: context.TODO(), 430 query: "my_query", 431 start: time.Unix(1500000000, 0), 432 end: time.Unix(1500002000, 0), 433 step: time.Second, 434 }, 435 want: nil, 436 wantErr: true, 437 }, 438 } 439 for _, tt := range tests { 440 t.Run(tt.name, func(t *testing.T) { 441 p := &prometheus{ 442 promAPIClient: tt.fields.promAPIClient, 443 config: tt.fields.config, 444 } 445 got, err := p.queryRangeSync(tt.args.ctx, tt.args.query, tt.args.start, tt.args.end, tt.args.step) 446 if (err != nil) != tt.wantErr { 447 t.Errorf("queryRangeSync() error = %v, wantErr %v", err, tt.wantErr) 448 return 449 } 450 if !reflect.DeepEqual(got, tt.want) { 451 t.Errorf("queryRangeSync() got = %v, want %v", got, tt.want) 452 } 453 }) 454 } 455 }