diff --git a/client/fingerprint/consul.go b/client/fingerprint/consul.go index 3b2fe4054..183f39a79 100644 --- a/client/fingerprint/consul.go +++ b/client/fingerprint/consul.go @@ -3,10 +3,12 @@ package fingerprint import ( "fmt" "strconv" + "strings" "time" consul "github.com/hashicorp/consul/api" log "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-version" ) const ( @@ -16,78 +18,120 @@ const ( // ConsulFingerprint is used to fingerprint for Consul type ConsulFingerprint struct { - logger log.Logger - client *consul.Client - lastState string + logger log.Logger + client *consul.Client + lastState string + extractors map[string]consulExtractor } +// consulInfo aliases the type returned from the Consul agent self endpoint. +type consulInfo = map[string]map[string]interface{} + +// consulExtractor is used to parse out one attribute from consulInfo. Returns +// the value of the attribute, and whether the attribute exists. +type consulExtractor func(consulInfo) (string, bool) + // NewConsulFingerprint is used to create a Consul fingerprint func NewConsulFingerprint(logger log.Logger) Fingerprint { - return &ConsulFingerprint{logger: logger.Named("consul"), lastState: consulUnavailable} + return &ConsulFingerprint{ + logger: logger.Named("consul"), + lastState: consulUnavailable, + } } func (f *ConsulFingerprint) Fingerprint(req *FingerprintRequest, resp *FingerprintResponse) error { - // Only create the client once to avoid creating too many connections to - // Consul. + + // establish consul client if necessary + if err := f.initialize(req); err != nil { + return err + } + + // query consul for agent self api + info := f.query(resp) + if len(info) == 0 { + // unable to reach consul, nothing to do this time + return nil + } + + // apply the extractor for each attribute + for attr, extractor := range f.extractors { + if s, ok := extractor(info); !ok { + f.logger.Warn("unable to fingerprint consul", "attribute", attr) + } else { + resp.AddAttribute(attr, s) + } + } + + // create link for consul + f.link(resp) + + // indicate Consul is now available + if f.lastState == consulUnavailable { + f.logger.Info("consul agent is available") + } + + f.lastState = consulAvailable + resp.Detected = true + return nil +} + +func (f *ConsulFingerprint) Periodic() (bool, time.Duration) { + return true, 15 * time.Second +} + +// clearConsulAttributes removes consul attributes and links from the passed Node. +func (f *ConsulFingerprint) clearConsulAttributes(r *FingerprintResponse) { + for attr := range f.extractors { + r.RemoveAttribute(attr) + } + r.RemoveLink("consul") +} + +func (f *ConsulFingerprint) initialize(req *FingerprintRequest) error { + // Only create the Consul client once to avoid creating many connections if f.client == nil { consulConfig, err := req.Config.ConsulConfig.ApiConfig() if err != nil { - return fmt.Errorf("Failed to initialize the Consul client config: %v", err) + return fmt.Errorf("failed to initialize Consul client config: %v", err) } f.client, err = consul.NewClient(consulConfig) if err != nil { - return fmt.Errorf("Failed to initialize consul client: %s", err) + return fmt.Errorf("failed to initialize Consul client: %s", err) + } + + f.extractors = map[string]consulExtractor{ + "consul.server": f.server, + "consul.version": f.version, + "consul.sku": f.sku, + "consul.revision": f.revision, + "unique.consul.name": f.name, + "consul.datacenter": f.dc, + "consul.segment": f.segment, } } + return nil +} + +func (f *ConsulFingerprint) query(resp *FingerprintResponse) consulInfo { // We'll try to detect consul by making a query to to the agent's self API. // If we can't hit this URL consul is probably not running on this machine. info, err := f.client.Agent().Self() if err != nil { f.clearConsulAttributes(resp) - // Print a message indicating that the Consul Agent is not available - // anymore + // indicate consul no longer available if f.lastState == consulAvailable { f.logger.Info("consul agent is unavailable") } f.lastState = consulUnavailable return nil } + return info +} - if s, ok := info["Config"]["Server"].(bool); ok { - resp.AddAttribute("consul.server", strconv.FormatBool(s)) - } else { - f.logger.Warn("unable to fingerprint consul.server") - } - if v, ok := info["Config"]["Version"].(string); ok { - resp.AddAttribute("consul.version", v) - } else { - f.logger.Warn("unable to fingerprint consul.version") - } - if r, ok := info["Config"]["Revision"].(string); ok { - resp.AddAttribute("consul.revision", r) - } else { - f.logger.Warn("unable to fingerprint consul.revision") - } - if n, ok := info["Config"]["NodeName"].(string); ok { - resp.AddAttribute("unique.consul.name", n) - } else { - f.logger.Warn("unable to fingerprint unique.consul.name") - } - if d, ok := info["Config"]["Datacenter"].(string); ok { - resp.AddAttribute("consul.datacenter", d) - } else { - f.logger.Warn("unable to fingerprint consul.datacenter") - } - if g, ok := info["Member"]["Tags"].(map[string]interface{}); ok { - if s, ok := g["segment"].(string); ok { - resp.AddAttribute("consul.segment", s) - } - } else { - f.logger.Warn("unable to fingerprint consul.segment") - } +func (f *ConsulFingerprint) link(resp *FingerprintResponse) { if dc, ok := resp.Attributes["consul.datacenter"]; ok { if name, ok2 := resp.Attributes["unique.consul.name"]; ok2 { resp.AddLink("consul", fmt.Sprintf("%s.%s", dc, name)) @@ -95,29 +139,54 @@ func (f *ConsulFingerprint) Fingerprint(req *FingerprintRequest, resp *Fingerpri } else { f.logger.Warn("malformed Consul response prevented linking") } +} - // If the Consul Agent was previously unavailable print a message to - // indicate the Agent is available now - if f.lastState == consulUnavailable { - f.logger.Info("consul agent is available") +func (f *ConsulFingerprint) server(info consulInfo) (string, bool) { + s, ok := info["Config"]["Server"].(bool) + return strconv.FormatBool(s), ok +} + +func (f *ConsulFingerprint) version(info consulInfo) (string, bool) { + v, ok := info["Config"]["Version"].(string) + return v, ok +} + +func (f *ConsulFingerprint) sku(info consulInfo) (string, bool) { + v, ok := info["Config"]["Version"].(string) + if !ok { + return "", ok } - f.lastState = consulAvailable - resp.Detected = true - return nil + + ver, vErr := version.NewVersion(v) + if vErr != nil { + return "", false + } + if strings.Contains(ver.Metadata(), "ent") { + return "ent", true + } + return "oss", true } -// clearConsulAttributes removes consul attributes and links from the passed -// Node. -func (f *ConsulFingerprint) clearConsulAttributes(r *FingerprintResponse) { - r.RemoveAttribute("consul.server") - r.RemoveAttribute("consul.version") - r.RemoveAttribute("consul.revision") - r.RemoveAttribute("unique.consul.name") - r.RemoveAttribute("consul.datacenter") - r.RemoveAttribute("consul.segment") - r.RemoveLink("consul") +func (f *ConsulFingerprint) revision(info consulInfo) (string, bool) { + r, ok := info["Config"]["Revision"].(string) + return r, ok } -func (f *ConsulFingerprint) Periodic() (bool, time.Duration) { - return true, 15 * time.Second +func (f *ConsulFingerprint) name(info consulInfo) (string, bool) { + n, ok := info["Config"]["NodeName"].(string) + return n, ok +} + +func (f *ConsulFingerprint) dc(info consulInfo) (string, bool) { + d, ok := info["Config"]["Datacenter"].(string) + return d, ok +} + +func (f *ConsulFingerprint) segment(info consulInfo) (string, bool) { + tags, tagsOK := info["Member"]["Tags"].(map[string]interface{}) + if !tagsOK { + return "", false + } + s, ok := tags["segment"].(string) + return s, ok } diff --git a/client/fingerprint/consul_test.go b/client/fingerprint/consul_test.go index 4f0287fbf..5e3c40930 100644 --- a/client/fingerprint/consul_test.go +++ b/client/fingerprint/consul_test.go @@ -1,7 +1,8 @@ package fingerprint import ( - "fmt" + "io" + "io/ioutil" "net/http" "net/http/httptest" "strings" @@ -10,207 +11,327 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/helper/testlog" "github.com/hashicorp/nomad/nomad/structs" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -func TestConsulFingerprint(t *testing.T) { - fp := NewConsulFingerprint(testlog.HCLogger(t)) - node := &structs.Node{ - Attributes: make(map[string]string), - } +// fakeConsul creates an HTTP server mimicking Consul /v1/agent/self endpoint on +// the first request, and alternates between success and failure responses on +// subsequent requests +func fakeConsul(payload string) (*httptest.Server, *config.Config) { + working := true ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - fmt.Fprintln(w, mockConsulResponse) - })) - defer ts.Close() - - conf := config.DefaultConfig() - conf.ConsulConfig.Addr = strings.TrimPrefix(ts.URL, "http://") - - request := &FingerprintRequest{Config: conf, Node: node} - var response FingerprintResponse - err := fp.Fingerprint(request, &response) - if err != nil { - t.Fatalf("Failed to fingerprint: %s", err) - } - - if !response.Detected { - t.Fatalf("expected response to be applicable") - } - - assertNodeAttributeContains(t, response.Attributes, "consul.server") - assertNodeAttributeContains(t, response.Attributes, "consul.version") - assertNodeAttributeContains(t, response.Attributes, "consul.revision") - assertNodeAttributeContains(t, response.Attributes, "unique.consul.name") - assertNodeAttributeContains(t, response.Attributes, "consul.datacenter") - assertNodeAttributeContains(t, response.Attributes, "consul.segment") - - if _, ok := response.Links["consul"]; !ok { - t.Errorf("Expected a link to consul, none found") - } -} - -// Taken from tryconsul using consul release 0.5.2 -const mockConsulResponse = ` -{ - "Config": { - "Bootstrap": false, - "BootstrapExpect": 3, - "Server": true, - "Datacenter": "vagrant", - "DataDir": "/var/lib/consul", - "DNSRecursor": "", - "DNSRecursors": [], - "DNSConfig": { - "NodeTTL": 0, - "ServiceTTL": null, - "AllowStale": false, - "EnableTruncate": false, - "MaxStale": 5000000000, - "OnlyPassing": false - }, - "Domain": "consul.", - "LogLevel": "INFO", - "NodeName": "consul2", - "ClientAddr": "0.0.0.0", - "BindAddr": "0.0.0.0", - "AdvertiseAddr": "172.16.59.133", - "AdvertiseAddrWan": "172.16.59.133", - "Ports": { - "DNS": 8600, - "HTTP": 8500, - "HTTPS": -1, - "RPC": 8400, - "SerfLan": 8301, - "SerfWan": 8302, - "Server": 8300 - }, - "Addresses": { - "DNS": "", - "HTTP": "", - "HTTPS": "", - "RPC": "" - }, - "LeaveOnTerm": false, - "SkipLeaveOnInt": false, - "StatsiteAddr": "", - "StatsitePrefix": "consul", - "StatsdAddr": "", - "Protocol": 2, - "EnableDebug": false, - "VerifyIncoming": false, - "VerifyOutgoing": false, - "VerifyServerHostname": false, - "CAFile": "", - "CertFile": "", - "KeyFile": "", - "ServerName": "", - "StartJoin": [], - "StartJoinWan": [], - "RetryJoin": [], - "RetryMaxAttempts": 0, - "RetryIntervalRaw": "", - "RetryJoinWan": [], - "RetryMaxAttemptsWan": 0, - "RetryIntervalWanRaw": "", - "UiDir": "/opt/consul-ui", - "PidFile": "", - "EnableSyslog": true, - "SyslogFacility": "LOCAL0", - "RejoinAfterLeave": false, - "CheckUpdateInterval": 300000000000, - "ACLDatacenter": "", - "ACLTTL": 30000000000, - "ACLTTLRaw": "", - "ACLDefaultPolicy": "allow", - "ACLDownPolicy": "extend-cache", - "Watches": null, - "DisableRemoteExec": false, - "DisableUpdateCheck": false, - "DisableAnonymousSignature": false, - "HTTPAPIResponseHeaders": null, - "AtlasInfrastructure": "", - "AtlasJoin": false, - "Revision": "9a9cc9341bb487651a0399e3fc5e1e8a42e62dd9+CHANGES", - "Version": "0.5.2", - "VersionPrerelease": "", - "UnixSockets": { - "Usr": "", - "Grp": "", - "Perms": "" - }, - "SessionTTLMin": 0, - "SessionTTLMinRaw": "" - }, - "Member": { - "Name": "consul2", - "Addr": "172.16.59.133", - "Port": 8301, - "Tags": { - "build": "0.5.2:9a9cc934", - "dc": "vagrant", - "expect": "3", - "port": "8300", - "role": "consul", - "segment": "mysegment", - "vsn": "2" - }, - "Status": 1, - "ProtocolMin": 1, - "ProtocolMax": 2, - "ProtocolCur": 2, - "DelegateMin": 2, - "DelegateMax": 4, - "DelegateCur": 4 - } -} -` - -// TestConsulFingerprint_UnexpectedResponse asserts that the Consul -// fingerprinter does not panic when it encounters an unexpected response. -// See https://github.com/hashicorp/nomad/issues/3326 -func TestConsulFingerprint_UnexpectedResponse(t *testing.T) { - assert := assert.New(t) - fp := NewConsulFingerprint(testlog.HCLogger(t)) - node := &structs.Node{ - Attributes: make(map[string]string), - } - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - fmt.Fprintln(w, "{}") - })) - defer ts.Close() - - conf := config.DefaultConfig() - conf.ConsulConfig.Addr = strings.TrimPrefix(ts.URL, "http://") - - request := &FingerprintRequest{Config: conf, Node: node} - var response FingerprintResponse - err := fp.Fingerprint(request, &response) - assert.Nil(err) - - if !response.Detected { - t.Fatalf("expected response to be applicable") - } - - attrs := []string{ - "consul.server", - "consul.version", - "consul.revision", - "unique.consul.name", - "consul.datacenter", - "consul.segment", - } - - for _, attr := range attrs { - if v, ok := response.Attributes[attr]; ok { - t.Errorf("unexpected node attribute %q with vlaue %q", attr, v) + if working { + _, _ = io.WriteString(w, payload) + working = false + } else { + w.WriteHeader(http.StatusInternalServerError) + working = true } - } + })) - if v, ok := response.Links["consul"]; ok { - t.Errorf("Unexpected link to consul: %v", v) - } + cfg := config.DefaultConfig() + cfg.ConsulConfig.Addr = strings.TrimPrefix(ts.URL, `http://`) + return ts, cfg +} + +func fakeConsulPayload(t *testing.T, filename string) string { + b, err := ioutil.ReadFile(filename) + require.NoError(t, err) + return string(b) +} + +func newConsulFingerPrint(t *testing.T) *ConsulFingerprint { + return NewConsulFingerprint(testlog.HCLogger(t)).(*ConsulFingerprint) +} + +func TestConsulFingerprint_server(t *testing.T) { + t.Parallel() + + fp := newConsulFingerPrint(t) + + t.Run("is server", func(t *testing.T) { + s, ok := fp.server(consulInfo{ + "Config": {"Server": true}, + }) + require.True(t, ok) + require.Equal(t, "true", s) + }) + + t.Run("is not server", func(t *testing.T) { + s, ok := fp.server(consulInfo{ + "Config": {"Server": false}, + }) + require.True(t, ok) + require.Equal(t, "false", s) + }) + + t.Run("missing", func(t *testing.T) { + _, ok := fp.server(consulInfo{ + "Config": {}, + }) + require.False(t, ok) + }) + + t.Run("malformed", func(t *testing.T) { + _, ok := fp.server(consulInfo{ + "Config": {"Server": 9000}, + }) + require.False(t, ok) + }) +} + +func TestConsulFingerprint_version(t *testing.T) { + t.Parallel() + + fp := newConsulFingerPrint(t) + + t.Run("oss", func(t *testing.T) { + v, ok := fp.version(consulInfo{ + "Config": {"Version": "v1.9.5"}, + }) + require.True(t, ok) + require.Equal(t, "v1.9.5", v) + }) + + t.Run("ent", func(t *testing.T) { + v, ok := fp.version(consulInfo{ + "Config": {"Version": "v1.9.5+ent"}, + }) + require.True(t, ok) + require.Equal(t, "v1.9.5+ent", v) + }) + + t.Run("missing", func(t *testing.T) { + _, ok := fp.version(consulInfo{ + "Config": {}, + }) + require.False(t, ok) + }) + + t.Run("malformed", func(t *testing.T) { + _, ok := fp.version(consulInfo{ + "Config": {"Version": 9000}, + }) + require.False(t, ok) + }) +} + +func TestConsulFingerprint_sku(t *testing.T) { + t.Parallel() + + fp := newConsulFingerPrint(t) + + t.Run("oss", func(t *testing.T) { + s, ok := fp.sku(consulInfo{ + "Config": {"Version": "v1.9.5"}, + }) + require.True(t, ok) + require.Equal(t, "oss", s) + }) + + t.Run("oss dev", func(t *testing.T) { + s, ok := fp.sku(consulInfo{ + "Config": {"Version": "v1.9.5-dev"}, + }) + require.True(t, ok) + require.Equal(t, "oss", s) + }) + + t.Run("ent", func(t *testing.T) { + s, ok := fp.sku(consulInfo{ + "Config": {"Version": "v1.9.5+ent"}, + }) + require.True(t, ok) + require.Equal(t, "ent", s) + }) + + t.Run("ent dev", func(t *testing.T) { + s, ok := fp.sku(consulInfo{ + "Config": {"Version": "v1.9.5+ent-dev"}, + }) + require.True(t, ok) + require.Equal(t, "ent", s) + }) + + t.Run("missing", func(t *testing.T) { + _, ok := fp.sku(consulInfo{ + "Config": {}, + }) + require.False(t, ok) + }) + + t.Run("malformed", func(t *testing.T) { + _, ok := fp.sku(consulInfo{ + "Config": {"Version": "***"}, + }) + require.False(t, ok) + }) +} + +func TestConsulFingerprint_revision(t *testing.T) { + t.Parallel() + + fp := newConsulFingerPrint(t) + + t.Run("ok", func(t *testing.T) { + r, ok := fp.revision(consulInfo{ + "Config": {"Revision": "3c1c22679"}, + }) + require.True(t, ok) + require.Equal(t, "3c1c22679", r) + }) + + t.Run("malformed", func(t *testing.T) { + _, ok := fp.revision(consulInfo{ + "Config": {"Revision": 9000}, + }) + require.False(t, ok) + }) + + t.Run("missing", func(t *testing.T) { + _, ok := fp.revision(consulInfo{ + "Config": {}, + }) + require.False(t, ok) + }) +} + +func TestConsulFingerprint_dc(t *testing.T) { + t.Parallel() + + fp := newConsulFingerPrint(t) + + t.Run("ok", func(t *testing.T) { + dc, ok := fp.dc(consulInfo{ + "Config": {"Datacenter": "dc1"}, + }) + require.True(t, ok) + require.Equal(t, "dc1", dc) + }) + + t.Run("malformed", func(t *testing.T) { + _, ok := fp.dc(consulInfo{ + "Config": {"Datacenter": 9000}, + }) + require.False(t, ok) + }) + + t.Run("missing", func(t *testing.T) { + _, ok := fp.dc(consulInfo{ + "Config": {}, + }) + require.False(t, ok) + }) +} + +func TestConsulFingerprint_segment(t *testing.T) { + t.Parallel() + + fp := newConsulFingerPrint(t) + + t.Run("ok", func(t *testing.T) { + s, ok := fp.segment(consulInfo{ + "Member": {"Tags": map[string]interface{}{"segment": "seg1"}}, + }) + require.True(t, ok) + require.Equal(t, "seg1", s) + }) + + t.Run("segment missing", func(t *testing.T) { + _, ok := fp.segment(consulInfo{ + "Member": {"Tags": map[string]interface{}{}}, + }) + require.False(t, ok) + }) + + t.Run("tags missing", func(t *testing.T) { + _, ok := fp.segment(consulInfo{ + "Member": {}, + }) + require.False(t, ok) + }) + + t.Run("malformed", func(t *testing.T) { + _, ok := fp.segment(consulInfo{ + "Member": {"Tags": map[string]interface{}{"segment": 9000}}, + }) + require.False(t, ok) + }) +} + +func TestConsulFingerprint_Fingerprint(t *testing.T) { + cf := newConsulFingerPrint(t) + + ts, cfg := fakeConsul(fakeConsulPayload(t, "test_fixtures/consul/agent_self.json")) + defer ts.Close() + + node := &structs.Node{Attributes: make(map[string]string)} + + // consul not available before first run + require.Equal(t, consulUnavailable, cf.lastState) + + // execute first query with good response + var resp FingerprintResponse + err := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "consul.datacenter": "dc1", + "consul.revision": "3c1c22679", + "consul.segment": "seg1", + "consul.server": "true", + "consul.sku": "oss", + "consul.version": "1.9.5", + "unique.consul.name": "HAL9000", + }, resp.Attributes) + require.True(t, resp.Detected) + + // consul now available + require.Equal(t, consulAvailable, cf.lastState) + + var resp2 FingerprintResponse + + // pretend attributes set for failing request + node.Attributes["consul.datacenter"] = "foo" + node.Attributes["consul.revision"] = "foo" + node.Attributes["consul.segment"] = "foo" + node.Attributes["consul.server"] = "foo" + node.Attributes["consul.sku"] = "foo" + node.Attributes["consul.version"] = "foo" + node.Attributes["unique.consul.name"] = "foo" + + // execute second query with error + err2 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp2) + require.NoError(t, err2) // does not return error + require.Equal(t, map[string]string{ // attributes set empty + "consul.datacenter": "", + "consul.revision": "", + "consul.segment": "", + "consul.server": "", + "consul.sku": "", + "consul.version": "", + "unique.consul.name": "", + }, resp2.Attributes) + require.True(t, resp.Detected) // never downgrade + + // consul no longer available + require.Equal(t, consulUnavailable, cf.lastState) + + // execute third query no error + var resp3 FingerprintResponse + err3 := cf.Fingerprint(&FingerprintRequest{Config: cfg, Node: node}, &resp3) + require.NoError(t, err3) + require.Equal(t, map[string]string{ + "consul.datacenter": "dc1", + "consul.revision": "3c1c22679", + "consul.segment": "seg1", + "consul.server": "true", + "consul.sku": "oss", + "consul.version": "1.9.5", + "unique.consul.name": "HAL9000", + }, resp3.Attributes) + + // consul now available again + require.Equal(t, consulAvailable, cf.lastState) + require.True(t, resp.Detected) } diff --git a/client/fingerprint/test_fixtures/consul/agent_self.json b/client/fingerprint/test_fixtures/consul/agent_self.json new file mode 100644 index 000000000..e1698a12b --- /dev/null +++ b/client/fingerprint/test_fixtures/consul/agent_self.json @@ -0,0 +1,478 @@ +{ + "Config": { + "Datacenter": "dc1", + "NodeName": "HAL9000", + "NodeID": "fa512dd3-4e92-6fb5-6446-bd7ed012ebe0", + "Revision": "3c1c22679", + "Server": true, + "Version": "1.9.5" + }, + "DebugConfig": { + "ACLDatacenter": "dc1", + "ACLDefaultPolicy": "allow", + "ACLDisabledTTL": "2m0s", + "ACLDownPolicy": "extend-cache", + "ACLEnableKeyListPolicy": false, + "ACLMasterToken": "hidden", + "ACLPolicyTTL": "30s", + "ACLRoleTTL": "0s", + "ACLTokenReplication": false, + "ACLTokenTTL": "30s", + "ACLTokens": { + "ACLAgentMasterToken": "hidden", + "ACLAgentToken": "hidden", + "ACLDefaultToken": "hidden", + "ACLReplicationToken": "hidden", + "DataDir": "", + "EnablePersistence": false, + "EnterpriseConfig": {} + }, + "ACLsEnabled": false, + "AEInterval": "1m0s", + "AdvertiseAddrLAN": "127.0.0.1", + "AdvertiseAddrWAN": "127.0.0.1", + "AdvertiseReconnectTimeout": "0s", + "AllowWriteHTTPFrom": [], + "AutoConfig": { + "Authorizer": { + "AllowReuse": false, + "AuthMethod": { + "ACLAuthMethodEnterpriseFields": {}, + "Config": { + "BoundAudiences": null, + "BoundIssuer": "", + "ClaimMappings": null, + "ClockSkewLeeway": 0, + "ExpirationLeeway": 0, + "JWKSCACert": "", + "JWKSURL": "", + "JWTSupportedAlgs": null, + "JWTValidationPubKeys": null, + "ListClaimMappings": null, + "NotBeforeLeeway": 0, + "OIDCDiscoveryCACert": "", + "OIDCDiscoveryURL": "" + }, + "Description": "", + "DisplayName": "", + "EnterpriseMeta": {}, + "MaxTokenTTL": "0s", + "Name": "Auto Config Authorizer", + "RaftIndex": { + "CreateIndex": 0, + "ModifyIndex": 0 + }, + "TokenLocality": "", + "Type": "jwt" + }, + "ClaimAssertions": [], + "Enabled": false + }, + "DNSSANs": [], + "Enabled": false, + "IPSANs": [], + "IntroToken": "hidden", + "IntroTokenFile": "", + "ServerAddresses": [] + }, + "AutoEncryptAllowTLS": false, + "AutoEncryptDNSSAN": [], + "AutoEncryptIPSAN": [], + "AutoEncryptTLS": false, + "AutopilotCleanupDeadServers": true, + "AutopilotDisableUpgradeMigration": false, + "AutopilotLastContactThreshold": "200ms", + "AutopilotMaxTrailingLogs": 250, + "AutopilotMinQuorum": 0, + "AutopilotRedundancyZoneTag": "", + "AutopilotServerStabilizationTime": "10s", + "AutopilotUpgradeVersionTag": "", + "BindAddr": "127.0.0.1", + "Bootstrap": false, + "BootstrapExpect": 0, + "CAFile": "", + "CAPath": "", + "Cache": { + "EntryFetchMaxBurst": 2, + "EntryFetchRate": 1.7976931348623157e+308, + "Logger": null + }, + "CertFile": "", + "CheckDeregisterIntervalMin": "1m0s", + "CheckOutputMaxSize": 4096, + "CheckReapInterval": "30s", + "CheckUpdateInterval": "5m0s", + "Checks": [], + "ClientAddrs": [ + "127.0.0.1" + ], + "ConfigEntryBootstrap": [], + "ConnectCAConfig": {}, + "ConnectCAProvider": "", + "ConnectEnabled": true, + "ConnectMeshGatewayWANFederationEnabled": false, + "ConnectSidecarMaxPort": 21255, + "ConnectSidecarMinPort": 21000, + "ConnectTestCALeafRootChangeSpread": "0s", + "ConsulCoordinateUpdateBatchSize": 128, + "ConsulCoordinateUpdateMaxBatches": 5, + "ConsulCoordinateUpdatePeriod": "100ms", + "ConsulRaftElectionTimeout": "52ms", + "ConsulRaftHeartbeatTimeout": "35ms", + "ConsulRaftLeaderLeaseTimeout": "20ms", + "ConsulServerHealthInterval": "10ms", + "DNSARecordLimit": 0, + "DNSAddrs": [ + "tcp://127.0.0.1:8600", + "udp://127.0.0.1:8600" + ], + "DNSAllowStale": true, + "DNSAltDomain": "", + "DNSCacheMaxAge": "0s", + "DNSDisableCompression": false, + "DNSDomain": "consul.", + "DNSEnableTruncate": false, + "DNSMaxStale": "87600h0m0s", + "DNSNodeMetaTXT": true, + "DNSNodeTTL": "0s", + "DNSOnlyPassing": false, + "DNSPort": 8600, + "DNSRecursorTimeout": "2s", + "DNSRecursors": [], + "DNSSOA": { + "Expire": 86400, + "Minttl": 0, + "Refresh": 3600, + "Retry": 600 + }, + "DNSServiceTTL": {}, + "DNSUDPAnswerLimit": 3, + "DNSUseCache": false, + "DataDir": "", + "Datacenter": "dc1", + "DefaultQueryTime": "5m0s", + "DevMode": true, + "DisableAnonymousSignature": true, + "DisableCoordinates": false, + "DisableHTTPUnprintableCharFilter": false, + "DisableHostNodeID": true, + "DisableKeyringFile": true, + "DisableRemoteExec": true, + "DisableUpdateCheck": false, + "DiscardCheckOutput": false, + "DiscoveryMaxStale": "0s", + "EnableAgentTLSForChecks": false, + "EnableCentralServiceConfig": true, + "EnableDebug": true, + "EnableLocalScriptChecks": false, + "EnableRemoteScriptChecks": false, + "EncryptKey": "hidden", + "EncryptVerifyIncoming": true, + "EncryptVerifyOutgoing": true, + "EnterpriseRuntimeConfig": {}, + "ExposeMaxPort": 21755, + "ExposeMinPort": 21500, + "GRPCAddrs": [ + "tcp://127.0.0.1:8502" + ], + "GRPCPort": 8502, + "GossipLANGossipInterval": "100ms", + "GossipLANGossipNodes": 3, + "GossipLANProbeInterval": "100ms", + "GossipLANProbeTimeout": "100ms", + "GossipLANRetransmitMult": 4, + "GossipLANSuspicionMult": 3, + "GossipWANGossipInterval": "100ms", + "GossipWANGossipNodes": 3, + "GossipWANProbeInterval": "100ms", + "GossipWANProbeTimeout": "100ms", + "GossipWANRetransmitMult": 4, + "GossipWANSuspicionMult": 3, + "HTTPAddrs": [ + "tcp://127.0.0.1:8500" + ], + "HTTPBlockEndpoints": [], + "HTTPMaxConnsPerClient": 200, + "HTTPMaxHeaderBytes": 0, + "HTTPPort": 8500, + "HTTPResponseHeaders": {}, + "HTTPSAddrs": [], + "HTTPSHandshakeTimeout": "5s", + "HTTPSPort": -1, + "HTTPUseCache": true, + "KVMaxValueSize": 524288, + "KeyFile": "hidden", + "LeaveDrainTime": "5s", + "LeaveOnTerm": false, + "Logging": { + "EnableSyslog": false, + "LogFilePath": "", + "LogJSON": false, + "LogLevel": "DEBUG", + "LogRotateBytes": 0, + "LogRotateDuration": "0s", + "LogRotateMaxFiles": 0, + "Name": "", + "SyslogFacility": "LOCAL0" + }, + "MaxQueryTime": "10m0s", + "NodeID": "fa512dd3-4e92-6fb5-6446-bd7ed012ebe0", + "NodeMeta": {}, + "NodeName": "x52", + "PidFile": "", + "PrimaryDatacenter": "dc1", + "PrimaryGateways": [], + "PrimaryGatewaysInterval": "30s", + "RPCAdvertiseAddr": "tcp://127.0.0.1:8300", + "RPCBindAddr": "tcp://127.0.0.1:8300", + "RPCConfig": { + "EnableStreaming": false + }, + "RPCHandshakeTimeout": "5s", + "RPCHoldTimeout": "7s", + "RPCMaxBurst": 1000, + "RPCMaxConnsPerClient": 100, + "RPCProtocol": 2, + "RPCRateLimit": -1, + "RaftProtocol": 3, + "RaftSnapshotInterval": "0s", + "RaftSnapshotThreshold": 0, + "RaftTrailingLogs": 0, + "ReadReplica": false, + "ReconnectTimeoutLAN": "0s", + "ReconnectTimeoutWAN": "0s", + "RejoinAfterLeave": false, + "RetryJoinIntervalLAN": "30s", + "RetryJoinIntervalWAN": "30s", + "RetryJoinLAN": [], + "RetryJoinMaxAttemptsLAN": 0, + "RetryJoinMaxAttemptsWAN": 0, + "RetryJoinWAN": [], + "Revision": "", + "SegmentLimit": 64, + "SegmentName": "", + "SegmentNameLimit": 64, + "Segments": [], + "SerfAdvertiseAddrLAN": "tcp://127.0.0.1:8301", + "SerfAdvertiseAddrWAN": "tcp://127.0.0.1:8302", + "SerfAllowedCIDRsLAN": [], + "SerfAllowedCIDRsWAN": [], + "SerfBindAddrLAN": "tcp://127.0.0.1:8301", + "SerfBindAddrWAN": "tcp://127.0.0.1:8302", + "SerfPortLAN": 8301, + "SerfPortWAN": 8302, + "ServerMode": true, + "ServerName": "", + "ServerPort": 8300, + "Services": [], + "SessionTTLMin": "0s", + "SkipLeaveOnInt": true, + "StartJoinAddrsLAN": [], + "StartJoinAddrsWAN": [], + "SyncCoordinateIntervalMin": "15s", + "SyncCoordinateRateTarget": 64, + "TLSCipherSuites": [], + "TLSMinVersion": "tls12", + "TLSPreferServerCipherSuites": false, + "TaggedAddresses": { + "lan": "127.0.0.1", + "lan_ipv4": "127.0.0.1", + "wan": "127.0.0.1", + "wan_ipv4": "127.0.0.1" + }, + "Telemetry": { + "AllowedPrefixes": [], + "BlockedPrefixes": [], + "CirconusAPIApp": "", + "CirconusAPIToken": "hidden", + "CirconusAPIURL": "", + "CirconusBrokerID": "", + "CirconusBrokerSelectTag": "", + "CirconusCheckDisplayName": "", + "CirconusCheckForceMetricActivation": "", + "CirconusCheckID": "", + "CirconusCheckInstanceID": "", + "CirconusCheckSearchTag": "", + "CirconusCheckTags": "", + "CirconusSubmissionInterval": "", + "CirconusSubmissionURL": "", + "Disable": false, + "DisableCompatOneNine": false, + "DisableHostname": false, + "DogstatsdAddr": "", + "DogstatsdTags": [], + "FilterDefault": true, + "MetricsPrefix": "consul", + "PrometheusOpts": { + "CounterDefinitions": [], + "Expiration": "0s", + "GaugeDefinitions": [], + "Registerer": null, + "SummaryDefinitions": [] + }, + "StatsdAddr": "", + "StatsiteAddr": "" + }, + "TranslateWANAddrs": false, + "TxnMaxReqLen": 524288, + "UIConfig": { + "ContentPath": "/ui/", + "DashboardURLTemplates": {}, + "Dir": "", + "Enabled": true, + "MetricsProvider": "", + "MetricsProviderFiles": [], + "MetricsProviderOptionsJSON": "", + "MetricsProxy": { + "AddHeaders": [], + "BaseURL": "", + "PathAllowlist": [] + } + }, + "UnixSocketGroup": "", + "UnixSocketMode": "", + "UnixSocketUser": "", + "UseStreamingBackend": false, + "VerifyIncoming": false, + "VerifyIncomingHTTPS": false, + "VerifyIncomingRPC": false, + "VerifyOutgoing": false, + "VerifyServerHostname": false, + "Version": "1.9.5", + "VersionPrerelease": "", + "Watches": [] + }, + "Coord": { + "Vec": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "Error": 1.5, + "Adjustment": 0, + "Height": 0.00001 + }, + "Member": { + "Name": "x52", + "Addr": "127.0.0.1", + "Port": 8301, + "Tags": { + "acls": "0", + "build": "1.9.5:", + "dc": "dc1", + "ft_fs": "1", + "ft_si": "1", + "id": "fa512dd3-4e92-6fb5-6446-bd7ed012ebe0", + "port": "8300", + "raft_vsn": "3", + "role": "consul", + "segment": "seg1", + "vsn": "2", + "vsn_max": "3", + "vsn_min": "2", + "wan_join_port": "8302" + }, + "Status": 1, + "ProtocolMin": 1, + "ProtocolMax": 5, + "ProtocolCur": 2, + "DelegateMin": 2, + "DelegateMax": 5, + "DelegateCur": 4 + }, + "Stats": { + "agent": { + "check_monitors": "0", + "check_ttls": "0", + "checks": "0", + "services": "0" + }, + "build": { + "prerelease": "", + "revision": "", + "version": "1.9.5" + }, + "consul": { + "acl": "disabled", + "bootstrap": "false", + "known_datacenters": "1", + "leader": "true", + "leader_addr": "127.0.0.1:8300", + "server": "true" + }, + "raft": { + "applied_index": "13", + "commit_index": "13", + "fsm_pending": "0", + "last_contact": "0", + "last_log_index": "13", + "last_log_term": "2", + "last_snapshot_index": "0", + "last_snapshot_term": "0", + "latest_configuration": "[{Suffrage:Voter ID:fa512dd3-4e92-6fb5-6446-bd7ed012ebe0 Address:127.0.0.1:8300}]", + "latest_configuration_index": "0", + "num_peers": "0", + "protocol_version": "3", + "protocol_version_max": "3", + "protocol_version_min": "0", + "snapshot_version_max": "1", + "snapshot_version_min": "0", + "state": "Leader", + "term": "2" + }, + "runtime": { + "arch": "amd64", + "cpu_count": "24", + "goroutines": "85", + "max_procs": "24", + "os": "linux", + "version": "go1.16.4" + }, + "serf_lan": { + "coordinate_resets": "0", + "encrypted": "false", + "event_queue": "1", + "event_time": "2", + "failed": "0", + "health_score": "0", + "intent_queue": "0", + "left": "0", + "member_time": "1", + "members": "1", + "query_queue": "0", + "query_time": "1" + }, + "serf_wan": { + "coordinate_resets": "0", + "encrypted": "false", + "event_queue": "0", + "event_time": "1", + "failed": "0", + "health_score": "0", + "intent_queue": "0", + "left": "0", + "member_time": "1", + "members": "1", + "query_queue": "0", + "query_time": "1" + } + }, + "Meta": { + "consul-network-segment": "" + }, + "xDS": { + "SupportedProxies": { + "envoy": [ + "1.16.2", + "1.15.3", + "1.14.6", + "1.13.7" + ] + } + } +}