github.com/m-lab/locate@v0.17.6/handler/prometheus_test.go (about)

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/m-lab/go/host"
    14  	"github.com/m-lab/go/testingx"
    15  	"github.com/m-lab/locate/connection/testdata"
    16  	"github.com/m-lab/locate/heartbeat"
    17  	"github.com/m-lab/locate/heartbeat/heartbeattest"
    18  	prom "github.com/prometheus/client_golang/api/prometheus/v1"
    19  	"github.com/prometheus/common/model"
    20  )
    21  
    22  func TestClient_Prometheus(t *testing.T) {
    23  	tests := []struct {
    24  		name    string
    25  		prom    PrometheusClient
    26  		tracker heartbeat.StatusTracker
    27  		want    int
    28  	}{
    29  		{
    30  			name: "success",
    31  			prom: &fakePromClient{
    32  				queryResult: model.Vector{},
    33  			},
    34  			tracker: &heartbeattest.FakeStatusTracker{},
    35  			want:    http.StatusOK,
    36  		},
    37  		{
    38  			name: "e2e error",
    39  			prom: &fakePromClient{
    40  				queryErr:    e2eQuery,
    41  				queryResult: model.Vector{},
    42  			},
    43  			tracker: &heartbeattest.FakeStatusTracker{},
    44  			want:    http.StatusInternalServerError,
    45  		},
    46  		{
    47  			name: "gmx error",
    48  			prom: &fakePromClient{
    49  				queryErr:    gmxQuery,
    50  				queryResult: model.Vector{},
    51  			},
    52  			tracker: &heartbeattest.FakeStatusTracker{},
    53  			want:    http.StatusInternalServerError,
    54  		},
    55  		{
    56  			name: "tracker error",
    57  			prom: &fakePromClient{
    58  				queryResult: model.Vector{},
    59  			},
    60  			tracker: &heartbeattest.FakeStatusTracker{
    61  				Err: errors.New("error"),
    62  			},
    63  			want: http.StatusInternalServerError,
    64  		},
    65  	}
    66  	for _, tt := range tests {
    67  		t.Run(tt.name, func(t *testing.T) {
    68  			locator := heartbeat.NewServerLocator(tt.tracker)
    69  			locator.StopImport()
    70  
    71  			c := &Client{
    72  				LocatorV2:        locator,
    73  				PrometheusClient: tt.prom,
    74  			}
    75  			rw := httptest.NewRecorder()
    76  			req := httptest.NewRequest(http.MethodGet, "/v2/platform/prometheus", nil)
    77  			c.Prometheus(rw, req)
    78  
    79  			if tt.want != rw.Code {
    80  				t.Errorf("Prometheus() expected status code: %d, got: %d", tt.want, rw.Code)
    81  			}
    82  		})
    83  	}
    84  }
    85  
    86  func TestClient_UpdatePrometheusForMachine(t *testing.T) {
    87  	hostname, err := host.Parse(testdata.FakeHostname)
    88  	testingx.Must(t, err, "failed to parse hostname")
    89  
    90  	tests := []struct {
    91  		name     string
    92  		hostname string
    93  		prom     PrometheusClient
    94  		tracker  heartbeat.StatusTracker
    95  		wantErr  bool
    96  	}{
    97  		{
    98  			name:     "success",
    99  			hostname: hostname.StringAll(),
   100  			prom: &fakePromClient{
   101  				queryResult: model.Vector{},
   102  			},
   103  			tracker: &heartbeattest.FakeStatusTracker{},
   104  			wantErr: false,
   105  		},
   106  		{
   107  			name:     "prom-error",
   108  			hostname: hostname.StringAll(),
   109  			prom: &fakePromClient{
   110  				queryErr:    formatQuery(e2eQuery, fmt.Sprintf("machine=%q", hostname.String())),
   111  				queryResult: model.Vector{},
   112  			},
   113  			tracker: &heartbeattest.FakeStatusTracker{},
   114  			wantErr: true,
   115  		},
   116  		{
   117  			name:     "parse-error",
   118  			hostname: "invalid-hostname",
   119  			prom: &fakePromClient{
   120  				queryResult: model.Vector{},
   121  			},
   122  			tracker: &heartbeattest.FakeStatusTracker{},
   123  			wantErr: true,
   124  		},
   125  	}
   126  	for _, tt := range tests {
   127  		t.Run(tt.name, func(t *testing.T) {
   128  			locator := heartbeat.NewServerLocator(tt.tracker)
   129  			locator.StopImport()
   130  
   131  			c := &Client{
   132  				LocatorV2:        locator,
   133  				PrometheusClient: tt.prom,
   134  			}
   135  
   136  			if err := c.UpdatePrometheusForMachine(context.Background(), tt.hostname); (err != nil) != tt.wantErr {
   137  				t.Errorf("Client.UpdatePrometheusForMachine() error = %v, wantErr %v", err, tt.wantErr)
   138  			}
   139  		})
   140  	}
   141  }
   142  
   143  func TestClient_query(t *testing.T) {
   144  	tests := []struct {
   145  		name    string
   146  		prom    PrometheusClient
   147  		query   string
   148  		label   model.LabelName
   149  		f       func(float64) bool
   150  		want    map[string]bool
   151  		wantErr bool
   152  	}{
   153  		{
   154  			name: "query-error",
   155  			prom: &fakePromClient{
   156  				queryErr: "error",
   157  			},
   158  			query:   "error",
   159  			wantErr: true,
   160  		},
   161  		{
   162  			name: "cast-error",
   163  			prom: &fakePromClient{
   164  				queryResult: model.Matrix{},
   165  			},
   166  			query:   "query",
   167  			wantErr: true,
   168  		},
   169  		{
   170  			name: "e2e",
   171  			prom: &fakePromClient{
   172  				queryResult: model.Vector{
   173  					{
   174  						Metric: map[model.LabelName]model.LabelValue{
   175  							e2eLabel: "success",
   176  						},
   177  						Value: 1,
   178  					},
   179  					{
   180  						Metric: map[model.LabelName]model.LabelValue{
   181  							e2eLabel: "failure",
   182  						},
   183  						Value: 0,
   184  					},
   185  				},
   186  			},
   187  			query: e2eQuery,
   188  			label: e2eLabel,
   189  			f:     e2eFunction,
   190  			want: map[string]bool{
   191  				"success": true,
   192  				"failure": false,
   193  			},
   194  			wantErr: false,
   195  		},
   196  		{
   197  			name: "gmx",
   198  			prom: &fakePromClient{
   199  				queryResult: model.Vector{
   200  					{
   201  						Metric: map[model.LabelName]model.LabelValue{
   202  							gmxLabel: "not-gmx",
   203  						},
   204  						Value: 0,
   205  					},
   206  					{
   207  						Metric: map[model.LabelName]model.LabelValue{
   208  							gmxLabel: "gmx",
   209  						},
   210  						Value: 1,
   211  					},
   212  				},
   213  			},
   214  			query: gmxQuery,
   215  			label: gmxLabel,
   216  			f:     gmxFunction,
   217  			want: map[string]bool{
   218  				"not-gmx": true,
   219  				"gmx":     false,
   220  			},
   221  			wantErr: false,
   222  		},
   223  	}
   224  	for _, tt := range tests {
   225  		t.Run(tt.name, func(t *testing.T) {
   226  			c := &Client{
   227  				PrometheusClient: tt.prom,
   228  			}
   229  
   230  			got, err := c.query(context.Background(), tt.query, "", tt.label, tt.f)
   231  			if (err != nil) != tt.wantErr {
   232  				t.Errorf("Client.query() error = %v, wantErr %v", err, tt.wantErr)
   233  				return
   234  			}
   235  
   236  			if !reflect.DeepEqual(got, tt.want) {
   237  				t.Errorf("Client.query() = %v, want %v", got, tt.want)
   238  			}
   239  		})
   240  	}
   241  }
   242  
   243  func Test_formatQuery(t *testing.T) {
   244  	tests := []struct {
   245  		name   string
   246  		query  string
   247  		filter string
   248  		want   string
   249  	}{
   250  		{
   251  			name:   "filter",
   252  			query:  "fake_metric",
   253  			filter: "machine=fake-machine-name",
   254  			want:   "fake_metric{machine=fake-machine-name}",
   255  		},
   256  		{
   257  			name:   "no-filter",
   258  			query:  "fake_metric",
   259  			filter: "",
   260  			want:   "fake_metric",
   261  		},
   262  	}
   263  	for _, tt := range tests {
   264  		t.Run(tt.name, func(t *testing.T) {
   265  			if got := formatQuery(tt.query, tt.filter); got != tt.want {
   266  				t.Errorf("formatQuery() = %v, want %v", got, tt.want)
   267  			}
   268  		})
   269  	}
   270  }
   271  
   272  var errFakeQuery = errors.New("fake query error")
   273  
   274  type fakePromClient struct {
   275  	queryErr    string
   276  	queryResult model.Value
   277  }
   278  
   279  func (p *fakePromClient) Query(ctx context.Context, query string, ts time.Time, opts ...prom.Option) (model.Value, prom.Warnings, error) {
   280  	if query == p.queryErr {
   281  		return nil, prom.Warnings{}, errFakeQuery
   282  	}
   283  
   284  	return p.queryResult, prom.Warnings{}, nil
   285  }