github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/fingerprint/consul_test.go (about)

     1  package fingerprint
     2  
     3  import (
     4  	"io"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/nomad/ci"
    12  	"github.com/hashicorp/nomad/client/config"
    13  	agentconsul "github.com/hashicorp/nomad/command/agent/consul"
    14  	"github.com/hashicorp/nomad/helper/testlog"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  // fakeConsul creates an HTTP server mimicking Consul /v1/agent/self endpoint on
    20  // the first request, and alternates between success and failure responses on
    21  // subsequent requests
    22  func fakeConsul(payload string) (*httptest.Server, *config.Config) {
    23  	working := true
    24  
    25  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    26  		if working {
    27  			_, _ = io.WriteString(w, payload)
    28  			working = false
    29  		} else {
    30  			w.WriteHeader(http.StatusInternalServerError)
    31  			working = true
    32  		}
    33  	}))
    34  
    35  	cfg := config.DefaultConfig()
    36  	cfg.ConsulConfig.Addr = strings.TrimPrefix(ts.URL, `http://`)
    37  	return ts, cfg
    38  }
    39  
    40  func fakeConsulPayload(t *testing.T, filename string) string {
    41  	b, err := ioutil.ReadFile(filename)
    42  	require.NoError(t, err)
    43  	return string(b)
    44  }
    45  
    46  func newConsulFingerPrint(t *testing.T) *ConsulFingerprint {
    47  	return NewConsulFingerprint(testlog.HCLogger(t)).(*ConsulFingerprint)
    48  }
    49  
    50  func TestConsulFingerprint_server(t *testing.T) {
    51  	ci.Parallel(t)
    52  
    53  	fp := newConsulFingerPrint(t)
    54  
    55  	t.Run("is server", func(t *testing.T) {
    56  		s, ok := fp.server(agentconsul.Self{
    57  			"Config": {"Server": true},
    58  		})
    59  		require.True(t, ok)
    60  		require.Equal(t, "true", s)
    61  	})
    62  
    63  	t.Run("is not server", func(t *testing.T) {
    64  		s, ok := fp.server(agentconsul.Self{
    65  			"Config": {"Server": false},
    66  		})
    67  		require.True(t, ok)
    68  		require.Equal(t, "false", s)
    69  	})
    70  
    71  	t.Run("missing", func(t *testing.T) {
    72  		_, ok := fp.server(agentconsul.Self{
    73  			"Config": {},
    74  		})
    75  		require.False(t, ok)
    76  	})
    77  
    78  	t.Run("malformed", func(t *testing.T) {
    79  		_, ok := fp.server(agentconsul.Self{
    80  			"Config": {"Server": 9000},
    81  		})
    82  		require.False(t, ok)
    83  	})
    84  }
    85  
    86  func TestConsulFingerprint_version(t *testing.T) {
    87  	ci.Parallel(t)
    88  
    89  	fp := newConsulFingerPrint(t)
    90  
    91  	t.Run("oss", func(t *testing.T) {
    92  		v, ok := fp.version(agentconsul.Self{
    93  			"Config": {"Version": "v1.9.5"},
    94  		})
    95  		require.True(t, ok)
    96  		require.Equal(t, "v1.9.5", v)
    97  	})
    98  
    99  	t.Run("ent", func(t *testing.T) {
   100  		v, ok := fp.version(agentconsul.Self{
   101  			"Config": {"Version": "v1.9.5+ent"},
   102  		})
   103  		require.True(t, ok)
   104  		require.Equal(t, "v1.9.5+ent", v)
   105  	})
   106  
   107  	t.Run("missing", func(t *testing.T) {
   108  		_, ok := fp.version(agentconsul.Self{
   109  			"Config": {},
   110  		})
   111  		require.False(t, ok)
   112  	})
   113  
   114  	t.Run("malformed", func(t *testing.T) {
   115  		_, ok := fp.version(agentconsul.Self{
   116  			"Config": {"Version": 9000},
   117  		})
   118  		require.False(t, ok)
   119  	})
   120  }
   121  
   122  func TestConsulFingerprint_sku(t *testing.T) {
   123  	ci.Parallel(t)
   124  
   125  	fp := newConsulFingerPrint(t)
   126  
   127  	t.Run("oss", func(t *testing.T) {
   128  		s, ok := fp.sku(agentconsul.Self{
   129  			"Config": {"Version": "v1.9.5"},
   130  		})
   131  		require.True(t, ok)
   132  		require.Equal(t, "oss", s)
   133  	})
   134  
   135  	t.Run("oss dev", func(t *testing.T) {
   136  		s, ok := fp.sku(agentconsul.Self{
   137  			"Config": {"Version": "v1.9.5-dev"},
   138  		})
   139  		require.True(t, ok)
   140  		require.Equal(t, "oss", s)
   141  	})
   142  
   143  	t.Run("ent", func(t *testing.T) {
   144  		s, ok := fp.sku(agentconsul.Self{
   145  			"Config": {"Version": "v1.9.5+ent"},
   146  		})
   147  		require.True(t, ok)
   148  		require.Equal(t, "ent", s)
   149  	})
   150  
   151  	t.Run("ent dev", func(t *testing.T) {
   152  		s, ok := fp.sku(agentconsul.Self{
   153  			"Config": {"Version": "v1.9.5+ent-dev"},
   154  		})
   155  		require.True(t, ok)
   156  		require.Equal(t, "ent", s)
   157  	})
   158  
   159  	t.Run("missing", func(t *testing.T) {
   160  		_, ok := fp.sku(agentconsul.Self{
   161  			"Config": {},
   162  		})
   163  		require.False(t, ok)
   164  	})
   165  
   166  	t.Run("malformed", func(t *testing.T) {
   167  		_, ok := fp.sku(agentconsul.Self{
   168  			"Config": {"Version": "***"},
   169  		})
   170  		require.False(t, ok)
   171  	})
   172  }
   173  
   174  func TestConsulFingerprint_revision(t *testing.T) {
   175  	ci.Parallel(t)
   176  
   177  	fp := newConsulFingerPrint(t)
   178  
   179  	t.Run("ok", func(t *testing.T) {
   180  		r, ok := fp.revision(agentconsul.Self{
   181  			"Config": {"Revision": "3c1c22679"},
   182  		})
   183  		require.True(t, ok)
   184  		require.Equal(t, "3c1c22679", r)
   185  	})
   186  
   187  	t.Run("malformed", func(t *testing.T) {
   188  		_, ok := fp.revision(agentconsul.Self{
   189  			"Config": {"Revision": 9000},
   190  		})
   191  		require.False(t, ok)
   192  	})
   193  
   194  	t.Run("missing", func(t *testing.T) {
   195  		_, ok := fp.revision(agentconsul.Self{
   196  			"Config": {},
   197  		})
   198  		require.False(t, ok)
   199  	})
   200  }
   201  
   202  func TestConsulFingerprint_dc(t *testing.T) {
   203  	ci.Parallel(t)
   204  
   205  	fp := newConsulFingerPrint(t)
   206  
   207  	t.Run("ok", func(t *testing.T) {
   208  		dc, ok := fp.dc(agentconsul.Self{
   209  			"Config": {"Datacenter": "dc1"},
   210  		})
   211  		require.True(t, ok)
   212  		require.Equal(t, "dc1", dc)
   213  	})
   214  
   215  	t.Run("malformed", func(t *testing.T) {
   216  		_, ok := fp.dc(agentconsul.Self{
   217  			"Config": {"Datacenter": 9000},
   218  		})
   219  		require.False(t, ok)
   220  	})
   221  
   222  	t.Run("missing", func(t *testing.T) {
   223  		_, ok := fp.dc(agentconsul.Self{
   224  			"Config": {},
   225  		})
   226  		require.False(t, ok)
   227  	})
   228  }
   229  
   230  func TestConsulFingerprint_segment(t *testing.T) {
   231  	ci.Parallel(t)
   232  
   233  	fp := newConsulFingerPrint(t)
   234  
   235  	t.Run("ok", func(t *testing.T) {
   236  		s, ok := fp.segment(agentconsul.Self{
   237  			"Member": {"Tags": map[string]interface{}{"segment": "seg1"}},
   238  		})
   239  		require.True(t, ok)
   240  		require.Equal(t, "seg1", s)
   241  	})
   242  
   243  	t.Run("segment missing", func(t *testing.T) {
   244  		_, ok := fp.segment(agentconsul.Self{
   245  			"Member": {"Tags": map[string]interface{}{}},
   246  		})
   247  		require.False(t, ok)
   248  	})
   249  
   250  	t.Run("tags missing", func(t *testing.T) {
   251  		_, ok := fp.segment(agentconsul.Self{
   252  			"Member": {},
   253  		})
   254  		require.False(t, ok)
   255  	})
   256  
   257  	t.Run("malformed", func(t *testing.T) {
   258  		_, ok := fp.segment(agentconsul.Self{
   259  			"Member": {"Tags": map[string]interface{}{"segment": 9000}},
   260  		})
   261  		require.False(t, ok)
   262  	})
   263  }
   264  
   265  func TestConsulFingerprint_connect(t *testing.T) {
   266  	ci.Parallel(t)
   267  
   268  	fp := newConsulFingerPrint(t)
   269  
   270  	t.Run("connect enabled", func(t *testing.T) {
   271  		s, ok := fp.connect(agentconsul.Self{
   272  			"DebugConfig": {"ConnectEnabled": true},
   273  		})
   274  		require.True(t, ok)
   275  		require.Equal(t, "true", s)
   276  	})
   277  
   278  	t.Run("connect not enabled", func(t *testing.T) {
   279  		s, ok := fp.connect(agentconsul.Self{
   280  			"DebugConfig": {"ConnectEnabled": false},
   281  		})
   282  		require.True(t, ok)
   283  		require.Equal(t, "false", s)
   284  	})
   285  
   286  	t.Run("connect missing", func(t *testing.T) {
   287  		_, ok := fp.connect(agentconsul.Self{
   288  			"DebugConfig": {},
   289  		})
   290  		require.False(t, ok)
   291  	})
   292  }
   293  
   294  func TestConsulFingerprint_grpc(t *testing.T) {
   295  	ci.Parallel(t)
   296  
   297  	fp := newConsulFingerPrint(t)
   298  
   299  	t.Run("grpc set pre-1.14 http", func(t *testing.T) {
   300  		s, ok := fp.grpc("http")(agentconsul.Self{
   301  			"Config":      {"Version": "1.13.3"},
   302  			"DebugConfig": {"GRPCPort": 8502.0}, // JSON numbers are floats
   303  		})
   304  		require.True(t, ok)
   305  		require.Equal(t, "8502", s)
   306  	})
   307  
   308  	t.Run("grpc disabled pre-1.14 http", func(t *testing.T) {
   309  		s, ok := fp.grpc("http")(agentconsul.Self{
   310  			"Config":      {"Version": "1.13.3"},
   311  			"DebugConfig": {"GRPCPort": -1.0}, // JSON numbers are floats
   312  		})
   313  		require.True(t, ok)
   314  		require.Equal(t, "-1", s)
   315  	})
   316  
   317  	t.Run("grpc set pre-1.14 https", func(t *testing.T) {
   318  		s, ok := fp.grpc("https")(agentconsul.Self{
   319  			"Config":      {"Version": "1.13.3"},
   320  			"DebugConfig": {"GRPCPort": 8502.0}, // JSON numbers are floats
   321  		})
   322  		require.True(t, ok)
   323  		require.Equal(t, "8502", s)
   324  	})
   325  
   326  	t.Run("grpc disabled pre-1.14 https", func(t *testing.T) {
   327  		s, ok := fp.grpc("https")(agentconsul.Self{
   328  			"Config":      {"Version": "1.13.3"},
   329  			"DebugConfig": {"GRPCPort": -1.0}, // JSON numbers are floats
   330  		})
   331  		require.True(t, ok)
   332  		require.Equal(t, "-1", s)
   333  	})
   334  
   335  	t.Run("grpc set post-1.14 http", func(t *testing.T) {
   336  		s, ok := fp.grpc("http")(agentconsul.Self{
   337  			"Config":      {"Version": "1.14.0"},
   338  			"DebugConfig": {"GRPCPort": 8502.0}, // JSON numbers are floats
   339  		})
   340  		require.True(t, ok)
   341  		require.Equal(t, "8502", s)
   342  	})
   343  
   344  	t.Run("grpc disabled post-1.14 http", func(t *testing.T) {
   345  		s, ok := fp.grpc("http")(agentconsul.Self{
   346  			"Config":      {"Version": "1.14.0"},
   347  			"DebugConfig": {"GRPCPort": -1.0}, // JSON numbers are floats
   348  		})
   349  		require.True(t, ok)
   350  		require.Equal(t, "-1", s)
   351  	})
   352  
   353  	t.Run("grpc disabled post-1.14 https", func(t *testing.T) {
   354  		s, ok := fp.grpc("https")(agentconsul.Self{
   355  			"Config":      {"Version": "1.14.0"},
   356  			"DebugConfig": {"GRPCTLSPort": -1.0}, // JSON numbers are floats
   357  		})
   358  		require.True(t, ok)
   359  		require.Equal(t, "-1", s)
   360  	})
   361  
   362  	t.Run("grpc set post-1.14 https", func(t *testing.T) {
   363  		s, ok := fp.grpc("https")(agentconsul.Self{
   364  			"Config":      {"Version": "1.14.0"},
   365  			"DebugConfig": {"GRPCTLSPort": 8503.0}, // JSON numbers are floats
   366  		})
   367  		require.True(t, ok)
   368  		require.Equal(t, "8503", s)
   369  	})
   370  
   371  	t.Run("grpc missing http", func(t *testing.T) {
   372  		_, ok := fp.grpc("http")(agentconsul.Self{
   373  			"DebugConfig": {},
   374  		})
   375  		require.False(t, ok)
   376  	})
   377  
   378  	t.Run("grpc missing https", func(t *testing.T) {
   379  		_, ok := fp.grpc("https")(agentconsul.Self{
   380  			"DebugConfig": {},
   381  		})
   382  		require.False(t, ok)
   383  	})
   384  }
   385  
   386  func TestConsulFingerprint_namespaces(t *testing.T) {
   387  	ci.Parallel(t)
   388  
   389  	fp := newConsulFingerPrint(t)
   390  
   391  	t.Run("supports namespaces", func(t *testing.T) {
   392  		value, ok := fp.namespaces(agentconsul.Self{
   393  			"Stats": {"license": map[string]interface{}{"features": "Automated Backups, Automated Upgrades, Enhanced Read Scalability, Network Segments, Redundancy Zone, Advanced Network Federation, Namespaces, SSO, Audit Logging"}},
   394  		})
   395  		require.True(t, ok)
   396  		require.Equal(t, "true", value)
   397  	})
   398  
   399  	t.Run("no namespaces", func(t *testing.T) {
   400  		value, ok := fp.namespaces(agentconsul.Self{
   401  			"Stats": {"license": map[string]interface{}{"features": "Automated Backups, Automated Upgrades, Enhanced Read Scalability, Network Segments, Redundancy Zone, Advanced Network Federation, SSO, Audit Logging"}},
   402  		})
   403  		require.True(t, ok)
   404  		require.Equal(t, "false", value)
   405  
   406  	})
   407  
   408  	t.Run("stats missing", func(t *testing.T) {
   409  		value, ok := fp.namespaces(agentconsul.Self{})
   410  		require.True(t, ok)
   411  		require.Equal(t, "false", value)
   412  	})
   413  
   414  	t.Run("license missing", func(t *testing.T) {
   415  		value, ok := fp.namespaces(agentconsul.Self{"Stats": {}})
   416  		require.True(t, ok)
   417  		require.Equal(t, "false", value)
   418  	})
   419  
   420  	t.Run("features missing", func(t *testing.T) {
   421  		value, ok := fp.namespaces(agentconsul.Self{"Stats": {"license": map[string]interface{}{}}})
   422  		require.True(t, ok)
   423  		require.Equal(t, "false", value)
   424  	})
   425  }
   426  
   427  func TestConsulFingerprint_Fingerprint_oss(t *testing.T) {
   428  	ci.Parallel(t)
   429  
   430  	cf := newConsulFingerPrint(t)
   431  
   432  	ts, cfg := fakeConsul(fakeConsulPayload(t, "test_fixtures/consul/agent_self_oss.json"))
   433  	defer ts.Close()
   434  
   435  	node := &structs.Node{Attributes: make(map[string]string)}
   436  
   437  	// consul not available before first run
   438  	require.Equal(t, consulUnavailable, cf.lastState)
   439  
   440  	// execute first query with good response
   441  	var resp FingerprintResponse
   442  	err := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp)
   443  	require.NoError(t, err)
   444  	require.Equal(t, map[string]string{
   445  		"consul.datacenter":    "dc1",
   446  		"consul.revision":      "3c1c22679",
   447  		"consul.segment":       "seg1",
   448  		"consul.server":        "true",
   449  		"consul.sku":           "oss",
   450  		"consul.version":       "1.9.5",
   451  		"consul.connect":       "true",
   452  		"consul.grpc":          "8502",
   453  		"consul.ft.namespaces": "false",
   454  		"unique.consul.name":   "HAL9000",
   455  	}, resp.Attributes)
   456  	require.True(t, resp.Detected)
   457  
   458  	// consul now available
   459  	require.Equal(t, consulAvailable, cf.lastState)
   460  
   461  	var resp2 FingerprintResponse
   462  
   463  	// pretend attributes set for failing request
   464  	node.Attributes["consul.datacenter"] = "foo"
   465  	node.Attributes["consul.revision"] = "foo"
   466  	node.Attributes["consul.segment"] = "foo"
   467  	node.Attributes["consul.server"] = "foo"
   468  	node.Attributes["consul.sku"] = "foo"
   469  	node.Attributes["consul.version"] = "foo"
   470  	node.Attributes["consul.connect"] = "foo"
   471  	node.Attributes["connect.grpc"] = "foo"
   472  	node.Attributes["unique.consul.name"] = "foo"
   473  
   474  	// execute second query with error
   475  	err2 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp2)
   476  	require.NoError(t, err2)         // does not return error
   477  	require.Nil(t, resp2.Attributes) // attributes unset so they don't change
   478  	require.True(t, resp.Detected)   // never downgrade
   479  
   480  	// consul no longer available
   481  	require.Equal(t, consulUnavailable, cf.lastState)
   482  
   483  	// execute third query no error
   484  	var resp3 FingerprintResponse
   485  	err3 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp3)
   486  	require.NoError(t, err3)
   487  	require.Equal(t, map[string]string{
   488  		"consul.datacenter":    "dc1",
   489  		"consul.revision":      "3c1c22679",
   490  		"consul.segment":       "seg1",
   491  		"consul.server":        "true",
   492  		"consul.sku":           "oss",
   493  		"consul.version":       "1.9.5",
   494  		"consul.connect":       "true",
   495  		"consul.grpc":          "8502",
   496  		"consul.ft.namespaces": "false",
   497  		"unique.consul.name":   "HAL9000",
   498  	}, resp3.Attributes)
   499  
   500  	// consul now available again
   501  	require.Equal(t, consulAvailable, cf.lastState)
   502  	require.True(t, resp.Detected)
   503  }
   504  
   505  func TestConsulFingerprint_Fingerprint_ent(t *testing.T) {
   506  	ci.Parallel(t)
   507  
   508  	cf := newConsulFingerPrint(t)
   509  
   510  	ts, cfg := fakeConsul(fakeConsulPayload(t, "test_fixtures/consul/agent_self_ent.json"))
   511  	defer ts.Close()
   512  
   513  	node := &structs.Node{Attributes: make(map[string]string)}
   514  
   515  	// consul not available before first run
   516  	require.Equal(t, consulUnavailable, cf.lastState)
   517  
   518  	// execute first query with good response
   519  	var resp FingerprintResponse
   520  	err := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp)
   521  	require.NoError(t, err)
   522  	require.Equal(t, map[string]string{
   523  		"consul.datacenter":    "dc1",
   524  		"consul.revision":      "22ce6c6ad",
   525  		"consul.segment":       "seg1",
   526  		"consul.server":        "true",
   527  		"consul.sku":           "ent",
   528  		"consul.version":       "1.9.5+ent",
   529  		"consul.ft.namespaces": "true",
   530  		"consul.connect":       "true",
   531  		"consul.grpc":          "8502",
   532  		"unique.consul.name":   "HAL9000",
   533  	}, resp.Attributes)
   534  	require.True(t, resp.Detected)
   535  
   536  	// consul now available
   537  	require.Equal(t, consulAvailable, cf.lastState)
   538  
   539  	var resp2 FingerprintResponse
   540  
   541  	// pretend attributes set for failing request
   542  	node.Attributes["consul.datacenter"] = "foo"
   543  	node.Attributes["consul.revision"] = "foo"
   544  	node.Attributes["consul.segment"] = "foo"
   545  	node.Attributes["consul.server"] = "foo"
   546  	node.Attributes["consul.sku"] = "foo"
   547  	node.Attributes["consul.version"] = "foo"
   548  	node.Attributes["consul.ft.namespaces"] = "foo"
   549  	node.Attributes["consul.connect"] = "foo"
   550  	node.Attributes["connect.grpc"] = "foo"
   551  	node.Attributes["unique.consul.name"] = "foo"
   552  
   553  	// execute second query with error
   554  	err2 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp2)
   555  	require.NoError(t, err2)         // does not return error
   556  	require.Nil(t, resp2.Attributes) // attributes unset so they don't change
   557  	require.True(t, resp.Detected)   // never downgrade
   558  
   559  	// consul no longer available
   560  	require.Equal(t, consulUnavailable, cf.lastState)
   561  
   562  	// execute third query no error
   563  	var resp3 FingerprintResponse
   564  	err3 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp3)
   565  	require.NoError(t, err3)
   566  	require.Equal(t, map[string]string{
   567  		"consul.datacenter":    "dc1",
   568  		"consul.revision":      "22ce6c6ad",
   569  		"consul.segment":       "seg1",
   570  		"consul.server":        "true",
   571  		"consul.sku":           "ent",
   572  		"consul.version":       "1.9.5+ent",
   573  		"consul.ft.namespaces": "true",
   574  		"consul.connect":       "true",
   575  		"consul.grpc":          "8502",
   576  		"unique.consul.name":   "HAL9000",
   577  	}, resp3.Attributes)
   578  
   579  	// consul now available again
   580  	require.Equal(t, consulAvailable, cf.lastState)
   581  	require.True(t, resp.Detected)
   582  }