diff --git a/command/agent/local.go b/command/agent/local.go index dbe38e79d..4e6a944a8 100644 --- a/command/agent/local.go +++ b/command/agent/local.go @@ -379,6 +379,9 @@ func (l *localState) setSyncState() error { } // If our definition is different, we need to update it + if existing.EnableTagOverride { + existing.Tags = service.Tags + } equal := reflect.DeepEqual(existing, service) l.serviceStatus[id] = syncStatus{inSync: equal} } diff --git a/command/agent/local_test.go b/command/agent/local_test.go index 997860279..4b7b566e2 100644 --- a/command/agent/local_test.go +++ b/command/agent/local_test.go @@ -169,6 +169,102 @@ func TestAgentAntiEntropy_Services(t *testing.T) { } } +func TestAgentAntiEntropy_EnableTagOverride(t *testing.T) { + conf := nextConfig() + dir, agent := makeAgent(t, conf) + defer os.RemoveAll(dir) + defer agent.Shutdown() + + testutil.WaitForLeader(t, agent.RPC, "dc1") + + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: agent.config.NodeName, + Address: "127.0.0.1", + } + var out struct{} + + // EnableTagOverride = true + srv1 := &structs.NodeService{ + ID: "svc_id1", + Service: "svc1", + Tags: []string{"tag1"}, + Port: 6100, + EnableTagOverride: true, + } + agent.state.AddService(srv1, "") + srv1_mod := new(structs.NodeService) + *srv1_mod = *srv1 + srv1_mod.Port = 7100 + srv1_mod.Tags = []string{"tag1_mod"} + args.Service = srv1_mod + if err := agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + // EnableTagOverride = false + srv2 := &structs.NodeService{ + ID: "svc_id2", + Service: "svc2", + Tags: []string{"tag2"}, + Port: 6200, + EnableTagOverride: false, + } + agent.state.AddService(srv2, "") + srv2_mod := new(structs.NodeService) + *srv2_mod = *srv2 + srv2_mod.Port = 7200 + srv2_mod.Tags = []string{"tag2_mod"} + args.Service = srv2_mod + if err := agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + // Trigger anti-entropy run and wait + agent.StartSync() + time.Sleep(200 * time.Millisecond) + + // Verify that we are in sync + req := structs.NodeSpecificRequest{ + Datacenter: "dc1", + Node: agent.config.NodeName, + } + var services structs.IndexedNodeServices + if err := agent.RPC("Catalog.NodeServices", &req, &services); err != nil { + t.Fatalf("err: %v", err) + } + + // All the services should match + for id, serv := range services.NodeServices.Services { + switch id { + case "svc_id1": + if serv.ID != "svc_id1" || + serv.Service != "svc1" || + serv.Port != 6100 || + !reflect.DeepEqual(serv.Tags, []string{"tag1_mod"}) { + t.Fatalf("bad: %v %v", serv, srv1) + } + case "svc_id2": + if serv.ID != "svc_id2" || + serv.Service != "svc2" || + serv.Port != 6200 || + !reflect.DeepEqual(serv.Tags, []string{"tag2"}) { + t.Fatalf("bad: %v %v", serv, srv2) + } + case "consul": + // ignore + default: + t.Fatalf("unexpected service: %v", id) + } + } + + for name, status := range agent.state.serviceStatus { + if !status.inSync { + t.Fatalf("should be in sync: %v %v", name, status) + } + } +} + func TestAgentAntiEntropy_Services_WithChecks(t *testing.T) { conf := nextConfig() dir, agent := makeAgent(t, conf) diff --git a/command/agent/structs.go b/command/agent/structs.go index 1d0e41e8b..5ebc3b455 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -6,23 +6,25 @@ import ( // ServiceDefinition is used to JSON decode the Service definitions type ServiceDefinition struct { - ID string - Name string - Tags []string - Address string - Port int - Check CheckType - Checks CheckTypes - Token string + ID string + Name string + Tags []string + Address string + Port int + Check CheckType + Checks CheckTypes + Token string + EnableTagOverride bool } func (s *ServiceDefinition) NodeService() *structs.NodeService { ns := &structs.NodeService{ - ID: s.ID, - Service: s.Name, - Tags: s.Tags, - Address: s.Address, - Port: s.Port, + ID: s.ID, + Service: s.Name, + Tags: s.Tags, + Address: s.Address, + Port: s.Port, + EnableTagOverride: s.EnableTagOverride, } if ns.ID == "" && ns.Service != "" { ns.ID = ns.Service diff --git a/command/exec_test.go b/command/exec_test.go index 4377756a0..36c5a1d85 100644 --- a/command/exec_test.go +++ b/command/exec_test.go @@ -27,7 +27,7 @@ func TestExecCommandRun(t *testing.T) { code := c.Run(args) if code != 0 { - t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + t.Fatalf("bad: %d. Error:%#v (std)Output:%#v", code, ui.ErrorWriter.String(), ui.OutputWriter.String()) } if !strings.Contains(ui.OutputWriter.String(), "load") { diff --git a/consul/catalog_endpoint_test.go b/consul/catalog_endpoint_test.go index 4b627abc8..7f927a4e9 100644 --- a/consul/catalog_endpoint_test.go +++ b/consul/catalog_endpoint_test.go @@ -490,7 +490,7 @@ func TestCatalogListServices(t *testing.T) { // Just add a node s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) - s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false}) if err := client.Call("Catalog.ListServices", &args, &out); err != nil { t.Fatalf("err: %v", err) @@ -544,7 +544,7 @@ func TestCatalogListServices_Blocking(t *testing.T) { go func() { time.Sleep(100 * time.Millisecond) s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) - s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false}) }() // Re-run the query @@ -625,7 +625,7 @@ func TestCatalogListServices_Stale(t *testing.T) { // Inject a fake service s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) - s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false}) // Run the query, do not wait for leader! if err := client.Call("Catalog.ListServices", &args, &out); err != nil { @@ -666,7 +666,7 @@ func TestCatalogListServiceNodes(t *testing.T) { // Just add a node s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) - s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false}) if err := client.Call("Catalog.ServiceNodes", &args, &out); err != nil { t.Fatalf("err: %v", err) @@ -709,8 +709,8 @@ func TestCatalogNodeServices(t *testing.T) { // Just add a node s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) - s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) - s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, "127.0.0.1", 80}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false}) + s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, "127.0.0.1", 80, false}) if err := client.Call("Catalog.NodeServices", &args, &out); err != nil { t.Fatalf("err: %v", err) diff --git a/consul/fsm_test.go b/consul/fsm_test.go index e08de2369..28594de41 100644 --- a/consul/fsm_test.go +++ b/consul/fsm_test.go @@ -343,10 +343,10 @@ func TestFSM_SnapshotRestore(t *testing.T) { // Add some state fsm.state.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) fsm.state.EnsureNode(2, structs.Node{"baz", "127.0.0.2"}) - fsm.state.EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, "127.0.0.1", 80}) - fsm.state.EnsureService(4, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) - fsm.state.EnsureService(5, "baz", &structs.NodeService{"web", "web", nil, "127.0.0.2", 80}) - fsm.state.EnsureService(6, "baz", &structs.NodeService{"db", "db", []string{"secondary"}, "127.0.0.2", 5000}) + fsm.state.EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, "127.0.0.1", 80, false}) + fsm.state.EnsureService(4, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000, false}) + fsm.state.EnsureService(5, "baz", &structs.NodeService{"web", "web", nil, "127.0.0.2", 80, false}) + fsm.state.EnsureService(6, "baz", &structs.NodeService{"db", "db", []string{"secondary"}, "127.0.0.2", 5000, false}) fsm.state.EnsureCheck(7, &structs.HealthCheck{ Node: "foo", CheckID: "web", diff --git a/consul/state_store_test.go b/consul/state_store_test.go index 97592c11d..13a0044ba 100644 --- a/consul/state_store_test.go +++ b/consul/state_store_test.go @@ -24,7 +24,7 @@ func TestEnsureRegistration(t *testing.T) { reg := &structs.RegisterRequest{ Node: "foo", Address: "127.0.0.1", - Service: &structs.NodeService{"api", "api", nil, "", 5000}, + Service: &structs.NodeService{"api", "api", nil, "", 5000, false}, Check: &structs.HealthCheck{ Node: "foo", CheckID: "api", @@ -189,15 +189,15 @@ func TestEnsureService(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(11, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(11, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5001}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5001, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(13, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -234,15 +234,15 @@ func TestEnsureService_DuplicateNode(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(11, "foo", &structs.NodeService{"api1", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(11, "foo", &structs.NodeService{"api1", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api2", "api", nil, "", 5001}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api2", "api", nil, "", 5001, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "foo", &structs.NodeService{"api3", "api", nil, "", 5002}); err != nil { + if err := store.EnsureService(13, "foo", &structs.NodeService{"api3", "api", nil, "", 5002, false}); err != nil { t.Fatalf("err: %v", err) } @@ -287,7 +287,7 @@ func TestDeleteNodeService(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -335,11 +335,11 @@ func TestDeleteNodeService_One(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "foo", &structs.NodeService{"api2", "api", nil, "", 5001}); err != nil { + if err := store.EnsureService(13, "foo", &structs.NodeService{"api2", "api", nil, "", 5001, false}); err != nil { t.Fatalf("err: %v", err) } @@ -372,7 +372,7 @@ func TestDeleteNode(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(21, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(21, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -431,15 +431,15 @@ func TestGetServices(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(32, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(32, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(33, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(33, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(34, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000}); err != nil { + if err := store.EnsureService(34, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -481,23 +481,23 @@ func TestServiceNodes(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "bar", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(13, "bar", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(14, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(14, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(15, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000}); err != nil { + if err := store.EnsureService(15, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(16, "bar", &structs.NodeService{"db2", "db", []string{"slave"}, "", 8001}); err != nil { + if err := store.EnsureService(16, "bar", &structs.NodeService{"db2", "db", []string{"slave"}, "", 8001, false}); err != nil { t.Fatalf("err: %v", err) } @@ -572,15 +572,15 @@ func TestServiceTagNodes(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", []string{"slave"}, "", 8001}); err != nil { + if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", []string{"slave"}, "", 8001, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000}); err != nil { + if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -620,15 +620,15 @@ func TestServiceTagNodes_MultipleTags(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", []string{"master", "v2"}, "", 8000}); err != nil { + if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", []string{"master", "v2"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", []string{"slave", "v2", "dev"}, "", 8001}); err != nil { + if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", []string{"slave", "v2", "dev"}, "", 8001, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", []string{"slave", "v2"}, "", 8000}); err != nil { + if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", []string{"slave", "v2"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -696,15 +696,15 @@ func TestStoreSnapshot(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(10, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(10, "foo", &structs.NodeService{"db", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(11, "foo", &structs.NodeService{"db2", "db", []string{"slave"}, "", 8001}); err != nil { + if err := store.EnsureService(11, "foo", &structs.NodeService{"db2", "db", []string{"slave"}, "", 8001, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000}); err != nil { + if err := store.EnsureService(12, "bar", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -889,10 +889,10 @@ func TestStoreSnapshot(t *testing.T) { } // Make some changes! - if err := store.EnsureService(23, "foo", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000}); err != nil { + if err := store.EnsureService(23, "foo", &structs.NodeService{"db", "db", []string{"slave"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(24, "bar", &structs.NodeService{"db", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(24, "bar", &structs.NodeService{"db", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } if err := store.EnsureNode(25, structs.Node{"baz", "127.0.0.3"}); err != nil { @@ -1019,7 +1019,7 @@ func TestEnsureCheck(t *testing.T) { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1115,7 +1115,7 @@ func TestDeleteNodeCheck(t *testing.T) { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1165,7 +1165,7 @@ func TestCheckServiceNodes(t *testing.T) { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1246,7 +1246,7 @@ func BenchmarkCheckServiceNodes(t *testing.B) { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1290,7 +1290,8 @@ func TestSS_Register_Deregister_Query(t *testing.T) { "statsite-box-stats", nil, "", - 0} + 0, + false} if err := store.EnsureService(2, "foo", srv); err != nil { t.Fatalf("err: %v", err) } @@ -1300,7 +1301,8 @@ func TestSS_Register_Deregister_Query(t *testing.T) { "statsite-share-stats", nil, "", - 0} + 0, + false} if err := store.EnsureService(3, "foo", srv); err != nil { t.Fatalf("err: %v", err) } @@ -1328,7 +1330,7 @@ func TestNodeInfo(t *testing.T) { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1387,13 +1389,13 @@ func TestNodeDump(t *testing.T) { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } if err := store.EnsureNode(3, structs.Node{"baz", "127.0.0.2"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(4, "baz", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000}); err != nil { + if err := store.EnsureService(4, "baz", &structs.NodeService{"db1", "db", []string{"master"}, "", 8000, false}); err != nil { t.Fatalf("err: %v", err) } @@ -2577,7 +2579,7 @@ func TestSessionInvalidate_DeleteNodeService(t *testing.T) { if err := store.EnsureNode(11, structs.Node{"foo", "127.0.0.1"}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, "", 5000, false}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 60d980028..3f2faf5d5 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -245,11 +245,12 @@ type ServiceNodes []ServiceNode // NodeService is a service provided by a node type NodeService struct { - ID string - Service string - Tags []string - Address string - Port int + ID string + Service string + Tags []string + Address string + Port int + EnableTagOverride bool } type NodeServices struct { Node Node diff --git a/website/source/docs/agent/services.html.markdown b/website/source/docs/agent/services.html.markdown index 1b589b31b..1575c7d29 100644 --- a/website/source/docs/agent/services.html.markdown +++ b/website/source/docs/agent/services.html.markdown @@ -26,6 +26,7 @@ A service definition that is a script looks like: "tags": ["master"], "address": "127.0.0.1", "port": 8000, + "enableTagOverride": false, "checks": [ { "script": "/usr/local/bin/check_redis.py", @@ -37,9 +38,9 @@ A service definition that is a script looks like: ``` A service definition must include a `name` and may optionally provide -an `id`, `tags`, `address`, `port`, and `check`. The `id` is set to the `name` if not -provided. It is required that all services have a unique ID per node, so if names -might conflict then unique IDs should be provided. +an `id`, `tags`, `address`, `port`, `check`, and `enableTagOverride`. The `id` is +set to the `name` if not provided. It is required that all services have a unique +ID per node, so if names might conflict then unique IDs should be provided. The `tags` property is a list of values that are opaque to Consul but can be used to distinguish between "master" or "slave" nodes, different versions, or any other service @@ -73,6 +74,25 @@ from `1`. Note: there is more information about [checks here](/docs/agent/checks.html). +The `enableTagOverride` can optionally be specified to disable the anti-entropy +feature for this service. If enableTagOverride is set to TRUE then external +agents can update this service in the [catalog](/docs/agent/http/catalog.html) and modify the tags. Subsequent +local sync operations by this agent will ignore the updated tags. For instance: If an external agent +modified both the tags and the port for this service and `enableTagOverride` +was set to TRUE then after the next sync cycle the service's port would revert +to the original value but the tags would maintain the updated value. As a +counter example: If an external agent modified both the tags and port for this +service and `enableTagOverride` was set to FALSE then after the next sync +cycle the service's port AND the tags would revert to the original value and +all modifications would be lost. It's important to note that this applies only +to the locally registered service. If you have multiple nodes all registering +the same service their `enableTagOverride` configuration and all other service +configuration items are independant of one another. Updating the tags for +the service registered on one node is independant of the same service (by name) +registered on another node. If `enableTagOverride` is not specified the default +value is false. See [anti-entropy syncs](/docs/internals/anti-entropy.html) +for more info. + To configure a service, either provide it as a `-config-file` option to the agent or place it inside the `-config-dir` of the agent. The file must end in the ".json" extension to be loaded by Consul. Check definitions can diff --git a/website/source/docs/internals/anti-entropy.html.markdown b/website/source/docs/internals/anti-entropy.html.markdown index 8010a33aa..b30a92462 100644 --- a/website/source/docs/internals/anti-entropy.html.markdown +++ b/website/source/docs/internals/anti-entropy.html.markdown @@ -135,3 +135,17 @@ fashion. If an error is encountered during an anti-entropy run, the error is logged and the agent continues to run. The anti-entropy mechanism is run periodically to automatically recover from these types of transient failures. + +### EnableTagOverride + +Synchronization of service registration can be partially modified to allow +external agents to change the tags for a service. This can be useful in +situations where an external monitoring service needs to be the source of +truth for tag information. For instance: Redis DB and its monitoring service +Redis Sentinel have this kind of relationship. Redis instances are responsible +for much of their configuration, but Sentinels determine whether the Redis +instance is a master or a slave. Using the Consul service configuration item +[EnableTagOverride](/docs/agent/services.html) you can instruct the Consul +agent on which the Redis DB is running to NOT update the tags during anti-entropy +synchronization. For more information see [Services](/docs/agent/services.html) +page.