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  }