github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/clients/pkg/promtail/discovery/consulagent/consul_test.go (about)

     1  // Copyright 2015 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package consulagent
    15  
    16  import (
    17  	"context"
    18  	"net/http"
    19  	"net/http/httptest"
    20  	"net/url"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/go-kit/log"
    25  	"github.com/prometheus/common/model"
    26  	"github.com/stretchr/testify/require"
    27  	"go.uber.org/goleak"
    28  
    29  	"github.com/prometheus/prometheus/discovery/targetgroup"
    30  )
    31  
    32  //nolint:interfacer // this follows the pattern in prometheus service discovery
    33  func TestMain(m *testing.M) {
    34  	goleak.VerifyTestMain(m)
    35  }
    36  
    37  func TestConfiguredService(t *testing.T) {
    38  	conf := &SDConfig{
    39  		Services: []string{"configuredServiceName"}}
    40  	consulDiscovery, err := NewDiscovery(conf, nil)
    41  
    42  	if err != nil {
    43  		t.Errorf("Unexpected error when initializing discovery %v", err)
    44  	}
    45  	if !consulDiscovery.shouldWatch("configuredServiceName", []string{""}) {
    46  		t.Errorf("Expected service %s to be watched", "configuredServiceName")
    47  	}
    48  	if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) {
    49  		t.Errorf("Expected service %s to not be watched", "nonConfiguredServiceName")
    50  	}
    51  }
    52  
    53  func TestConfiguredServiceWithTag(t *testing.T) {
    54  	conf := &SDConfig{
    55  		Services:    []string{"configuredServiceName"},
    56  		ServiceTags: []string{"http"},
    57  	}
    58  	consulDiscovery, err := NewDiscovery(conf, nil)
    59  
    60  	if err != nil {
    61  		t.Errorf("Unexpected error when initializing discovery %v", err)
    62  	}
    63  	if consulDiscovery.shouldWatch("configuredServiceName", []string{""}) {
    64  		t.Errorf("Expected service %s to not be watched without tag", "configuredServiceName")
    65  	}
    66  	if !consulDiscovery.shouldWatch("configuredServiceName", []string{"http"}) {
    67  		t.Errorf("Expected service %s to be watched with tag %s", "configuredServiceName", "http")
    68  	}
    69  	if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) {
    70  		t.Errorf("Expected service %s to not be watched without tag", "nonConfiguredServiceName")
    71  	}
    72  	if consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{"http"}) {
    73  		t.Errorf("Expected service %s to not be watched with tag %s", "nonConfiguredServiceName", "http")
    74  	}
    75  }
    76  
    77  func TestConfiguredServiceWithTags(t *testing.T) {
    78  	type testcase struct {
    79  		// What we've configured to watch.
    80  		conf *SDConfig
    81  		// The service we're checking if we should watch or not.
    82  		serviceName string
    83  		serviceTags []string
    84  		shouldWatch bool
    85  	}
    86  
    87  	cases := []testcase{
    88  		{
    89  			conf: &SDConfig{
    90  				Services:    []string{"configuredServiceName"},
    91  				ServiceTags: []string{"http", "v1"},
    92  			},
    93  			serviceName: "configuredServiceName",
    94  			serviceTags: []string{""},
    95  			shouldWatch: false,
    96  		},
    97  		{
    98  			conf: &SDConfig{
    99  				Services:    []string{"configuredServiceName"},
   100  				ServiceTags: []string{"http", "v1"},
   101  			},
   102  			serviceName: "configuredServiceName",
   103  			serviceTags: []string{"http", "v1"},
   104  			shouldWatch: true,
   105  		},
   106  		{
   107  			conf: &SDConfig{
   108  				Services:    []string{"configuredServiceName"},
   109  				ServiceTags: []string{"http", "v1"},
   110  			},
   111  			serviceName: "nonConfiguredServiceName",
   112  			serviceTags: []string{""},
   113  			shouldWatch: false,
   114  		},
   115  		{
   116  			conf: &SDConfig{
   117  				Services:    []string{"configuredServiceName"},
   118  				ServiceTags: []string{"http", "v1"},
   119  			},
   120  			serviceName: "nonConfiguredServiceName",
   121  			serviceTags: []string{"http, v1"},
   122  			shouldWatch: false,
   123  		},
   124  		{
   125  			conf: &SDConfig{
   126  				Services:    []string{"configuredServiceName"},
   127  				ServiceTags: []string{"http", "v1"},
   128  			},
   129  			serviceName: "configuredServiceName",
   130  			serviceTags: []string{"http", "v1", "foo"},
   131  			shouldWatch: true,
   132  		},
   133  		{
   134  			conf: &SDConfig{
   135  				Services:    []string{"configuredServiceName"},
   136  				ServiceTags: []string{"http", "v1", "foo"},
   137  			},
   138  			serviceName: "configuredServiceName",
   139  			serviceTags: []string{"http", "v1", "foo"},
   140  			shouldWatch: true,
   141  		},
   142  		{
   143  			conf: &SDConfig{
   144  				Services:    []string{"configuredServiceName"},
   145  				ServiceTags: []string{"http", "v1"},
   146  			},
   147  			serviceName: "configuredServiceName",
   148  			serviceTags: []string{"http", "v1", "v1"},
   149  			shouldWatch: true,
   150  		},
   151  	}
   152  
   153  	for _, tc := range cases {
   154  		consulDiscovery, err := NewDiscovery(tc.conf, nil)
   155  
   156  		if err != nil {
   157  			t.Errorf("Unexpected error when initializing discovery %v", err)
   158  		}
   159  		ret := consulDiscovery.shouldWatch(tc.serviceName, tc.serviceTags)
   160  		if ret != tc.shouldWatch {
   161  			t.Errorf("Expected should watch? %t, got %t. Watched service and tags: %s %+v, input was %s %+v", tc.shouldWatch, ret, tc.conf.Services, tc.conf.ServiceTags, tc.serviceName, tc.serviceTags)
   162  		}
   163  
   164  	}
   165  }
   166  
   167  func TestNonConfiguredService(t *testing.T) {
   168  	conf := &SDConfig{}
   169  	consulDiscovery, err := NewDiscovery(conf, nil)
   170  
   171  	if err != nil {
   172  		t.Errorf("Unexpected error when initializing discovery %v", err)
   173  	}
   174  	if !consulDiscovery.shouldWatch("nonConfiguredServiceName", []string{""}) {
   175  		t.Errorf("Expected service %s to be watched", "nonConfiguredServiceName")
   176  	}
   177  }
   178  
   179  const (
   180  	AgentAnswer = `{
   181    "Config": {
   182      "Datacenter": "test-dc",
   183      "NodeName": "test-node",
   184      "NodeID": "efd2573b-4c48-312b-2097-99bffc4352c4",
   185      "Revision": "a9322b9c7",
   186      "Server": false,
   187      "Version": "1.8.3"
   188    }
   189  }`
   190  	ServiceTestAnswer = `
   191  [{
   192    "AggregatedStatus": "passing",
   193    "Service": {
   194      "ID": "test-id-1234",
   195      "Service": "test",
   196      "Tags": ["tag1"],
   197      "Address": "",
   198      "Meta": {"version":"1.0.0","environment":"staging"},
   199      "Port": 3341,
   200      "Weights": {
   201        "Passing": 1,
   202        "Warning": 1
   203      },
   204      "EnableTagOverride": false,
   205      "ProxyDestination": "",
   206      "Proxy": {},
   207      "Connect": {},
   208      "CreateIndex": 1,
   209      "ModifyIndex": 1
   210    },
   211    "Checks": [{
   212      "Node": "node1",
   213      "CheckID": "serfHealth",
   214      "Name": "Serf Health Status",
   215      "Status": "passing"
   216    }]
   217  }]`
   218  	ServiceOtherAnswer = `
   219  [{
   220    "AggregatedStatus": "passing",
   221    "Service": {
   222      "ID": "other-id-5678",
   223      "Service": "other",
   224      "Tags": ["tag2"],
   225      "Address": "",
   226      "Meta": {"version":"1.0.0","environment":"staging"},
   227      "Port": 0,
   228      "Weights": {
   229        "Passing": 1,
   230        "Warning": 1
   231      },
   232      "EnableTagOverride": false,
   233      "ProxyDestination": "",
   234      "Proxy": {},
   235      "Connect": {},
   236      "CreateIndex": 1,
   237      "ModifyIndex": 1
   238    },
   239    "Checks": [{
   240      "Node": "node1",
   241      "CheckID": "serfHealth",
   242      "Name": "Serf Health Status",
   243      "Status": "passing"
   244    }]
   245  }]`
   246  
   247  	ServicesTestAnswer = `
   248  {
   249    "test-id-1234": {
   250      "ID": "test-id-1234",
   251      "Service": "test",
   252      "Tags": [ "tag1" ],
   253      "Meta": {"version":"1.0.0","environment":"staging"},
   254      "Port": 3341,
   255      "Address": "1.1.1.1",
   256      "TaggedAddresses": {
   257        "lan_ipv4": {
   258          "Address": "1.1.1.1",
   259          "Port": 4646
   260        },
   261        "wan_ipv4": {
   262          "Address": "1.1.1.1",
   263          "Port": 4646
   264        }
   265      },
   266      "Weights": {
   267        "Passing": 1,
   268        "Warning": 1
   269      },
   270      "EnableTagOverride": false
   271    },
   272    "test-id-5678": {
   273      "ID": "test-id-5678",
   274      "Service": "test",
   275      "Tags": [ "tag1" ],
   276      "Meta": {"version":"1.0.0","environment":"staging"},
   277      "Port": 3341,
   278      "Address": "1.1.2.2",
   279      "TaggedAddresses": {
   280        "lan_ipv4": {
   281          "Address": "1.1.2.2",
   282          "Port": 4646
   283        },
   284        "wan_ipv4": {
   285          "Address": "1.1.2.2",
   286          "Port": 4646
   287        }
   288      },
   289      "Weights": {
   290        "Passing": 1,
   291        "Warning": 1
   292      },
   293      "EnableTagOverride": false
   294    },
   295    "other-id-9876": {
   296      "ID": "other-id-9876",
   297      "Service": "other",
   298      "Tags": [ "tag2" ],
   299      "Meta": {"version":"1.0.0","environment":"staging"},
   300      "Port": 0,
   301      "Address": "",
   302      "Weights": {
   303        "Passing": 1,
   304        "Warning": 1
   305      },
   306      "EnableTagOverride": false
   307    }
   308  }`
   309  )
   310  
   311  func newServer(t *testing.T) (*httptest.Server, *SDConfig) {
   312  	// github.com/hashicorp/consul/testutil/ would be nice but it needs a local consul binary.
   313  	stub := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   314  		response := ""
   315  		switch r.URL.String() {
   316  		case "/v1/agent/self":
   317  			response = AgentAnswer
   318  		case "/v1/agent/health/service/name/test?format=json":
   319  			response = ServiceTestAnswer
   320  		case "/v1/agent/health/service/name/other?format=json":
   321  			response = ServiceOtherAnswer
   322  		case "/v1/agent/services":
   323  			response = ServicesTestAnswer
   324  		default:
   325  			t.Errorf("Unhandled consul call: %s", r.URL)
   326  		}
   327  		w.Header().Add("X-Consul-Index", "1")
   328  		_, err := w.Write([]byte(response))
   329  		require.NoError(t, err)
   330  	}))
   331  	stuburl, err := url.Parse(stub.URL)
   332  	require.NoError(t, err)
   333  
   334  	config := &SDConfig{
   335  		Server:          stuburl.Host,
   336  		Token:           "fake-token",
   337  		RefreshInterval: model.Duration(1 * time.Second),
   338  	}
   339  	return stub, config
   340  }
   341  
   342  func newDiscovery(t *testing.T, config *SDConfig) *Discovery {
   343  	logger := log.NewNopLogger()
   344  	d, err := NewDiscovery(config, logger)
   345  	require.NoError(t, err)
   346  	return d
   347  }
   348  
   349  func checkOneTarget(t *testing.T, tg []*targetgroup.Group) {
   350  	require.Equal(t, 1, len(tg))
   351  	target := tg[0]
   352  	require.Equal(t, "test-dc", string(target.Labels["__meta_consulagent_dc"]))
   353  	require.Equal(t, target.Source, string(target.Labels["__meta_consulagent_service"]))
   354  	if target.Source == "test" {
   355  		// test service should have one node.
   356  		require.Greater(t, len(target.Targets), 0, "Test service should have one node")
   357  	}
   358  }
   359  
   360  // Watch all the services in the catalog.
   361  func TestAllServices(t *testing.T) {
   362  	stub, config := newServer(t)
   363  	defer stub.Close()
   364  
   365  	d := newDiscovery(t, config)
   366  
   367  	ctx, cancel := context.WithCancel(context.Background())
   368  	ch := make(chan []*targetgroup.Group)
   369  	go func() {
   370  		d.Run(ctx, ch)
   371  		close(ch)
   372  	}()
   373  	checkOneTarget(t, <-ch)
   374  	checkOneTarget(t, <-ch)
   375  	cancel()
   376  	<-ch
   377  }
   378  
   379  // targetgroup with no targets is emitted if no services were discovered.
   380  func TestNoTargets(t *testing.T) {
   381  	stub, config := newServer(t)
   382  	defer stub.Close()
   383  	config.ServiceTags = []string{"missing"}
   384  
   385  	d := newDiscovery(t, config)
   386  
   387  	ctx, cancel := context.WithCancel(context.Background())
   388  	ch := make(chan []*targetgroup.Group)
   389  	go d.Run(ctx, ch)
   390  
   391  	targets := (<-ch)[0].Targets
   392  	require.Equal(t, 0, len(targets))
   393  	cancel()
   394  }
   395  
   396  // Watch only the test service.
   397  func TestOneService(t *testing.T) {
   398  	stub, config := newServer(t)
   399  	defer stub.Close()
   400  
   401  	config.Services = []string{"test"}
   402  	d := newDiscovery(t, config)
   403  
   404  	ctx, cancel := context.WithCancel(context.Background())
   405  	ch := make(chan []*targetgroup.Group)
   406  	go d.Run(ctx, ch)
   407  	checkOneTarget(t, <-ch)
   408  	cancel()
   409  }
   410  
   411  // Watch the test service with a specific tag and node-meta.
   412  func TestAllOptions(t *testing.T) {
   413  	stub, config := newServer(t)
   414  	defer stub.Close()
   415  
   416  	config.Services = []string{"test"}
   417  	config.NodeMeta = map[string]string{"rack_name": "2304"}
   418  	config.ServiceTags = []string{"tag1"}
   419  	config.AllowStale = true
   420  	config.Token = "fake-token"
   421  
   422  	d := newDiscovery(t, config)
   423  
   424  	ctx, cancel := context.WithCancel(context.Background())
   425  	ch := make(chan []*targetgroup.Group)
   426  	go func() {
   427  		d.Run(ctx, ch)
   428  		close(ch)
   429  	}()
   430  	checkOneTarget(t, <-ch)
   431  	cancel()
   432  	<-ch
   433  }
   434  
   435  func TestGetDatacenterShouldReturnError(t *testing.T) {
   436  	for _, tc := range []struct {
   437  		handler    func(http.ResponseWriter, *http.Request)
   438  		errMessage string
   439  	}{
   440  		{
   441  			// Define a handler that will return status 500.
   442  			handler: func(w http.ResponseWriter, r *http.Request) {
   443  				w.WriteHeader(500)
   444  			},
   445  			errMessage: "Unexpected response code: 500 ()",
   446  		},
   447  		{
   448  			// Define a handler that will return incorrect response.
   449  			handler: func(w http.ResponseWriter, r *http.Request) {
   450  				_, err := w.Write([]byte(`{"Config": {"Not-Datacenter": "test-dc"}}`))
   451  				require.NoError(t, err)
   452  			},
   453  			errMessage: "invalid value '<nil>' for Config.Datacenter",
   454  		},
   455  	} {
   456  		stub := httptest.NewServer(http.HandlerFunc(tc.handler))
   457  		stuburl, err := url.Parse(stub.URL)
   458  		require.NoError(t, err)
   459  
   460  		config := &SDConfig{
   461  			Server:          stuburl.Host,
   462  			Token:           "fake-token",
   463  			RefreshInterval: model.Duration(1 * time.Second),
   464  		}
   465  		defer stub.Close()
   466  		d := newDiscovery(t, config)
   467  
   468  		// Should be empty if not initialized.
   469  		require.Equal(t, "", d.clientDatacenter)
   470  
   471  		err = d.getDatacenter()
   472  
   473  		// An error should be returned.
   474  		require.Equal(t, tc.errMessage, err.Error())
   475  		// Should still be empty.
   476  		require.Equal(t, "", d.clientDatacenter)
   477  	}
   478  }