From 283c5d1be1d950a2c35970a1833b2db2a48e9731 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 11:23:55 -0700 Subject: [PATCH 1/8] consul: Replace single tag with list of tags --- consul/structs/structs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 638749cfc0..5c11801a7f 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -105,7 +105,7 @@ type ServiceNode struct { Address string ServiceID string ServiceName string - ServiceTag string + ServiceTags []string ServicePort int } type ServiceNodes []ServiceNode @@ -114,7 +114,7 @@ type ServiceNodes []ServiceNode type NodeService struct { ID string Service string - Tag string + Tags []string Port int } type NodeServices struct { From 13bff3a09bf9ef88295feecd9ab67188be787a88 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 12:03:02 -0700 Subject: [PATCH 2/8] consul: Change state store to support multiple tags --- consul/state_store.go | 44 +++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/consul/state_store.go b/consul/state_store.go index 4b08c04640..8962b0e0b0 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -145,7 +145,7 @@ func (s *StateStore) initialize() error { }, "service": &MDBIndex{ AllowBlank: true, - Fields: []string{"ServiceName", "ServiceTag"}, + Fields: []string{"ServiceName"}, }, }, Decoder: func(buf []byte) interface{} { @@ -318,7 +318,7 @@ func (s *StateStore) EnsureService(index uint64, node string, ns *structs.NodeSe Node: node, ServiceID: ns.ID, ServiceName: ns.Service, - ServiceTag: ns.Tag, + ServiceTags: ns.Tags, ServicePort: ns.Port, } @@ -382,7 +382,7 @@ func (s *StateStore) parseNodeServices(tables MDBTables, tx *MDBTxn, name string srv := &structs.NodeService{ ID: service.ServiceID, Service: service.ServiceName, - Tag: service.ServiceTag, + Tags: service.ServiceTags, Port: service.ServicePort, } ns.Services[srv.ID] = srv @@ -456,8 +456,6 @@ func (s *StateStore) DeleteNode(index uint64, node string) error { // Services is used to return all the services with a list of associated tags func (s *StateStore) Services() (uint64, map[string][]string) { - // TODO: Optimize to not table scan.. We can do a distinct - // type of query to avoid this services := make(map[string][]string) idx, res, err := s.serviceTable.Get("id") if err != nil { @@ -466,12 +464,17 @@ func (s *StateStore) Services() (uint64, map[string][]string) { } for _, r := range res { srv := r.(*structs.ServiceNode) - - tags := services[srv.ServiceName] - if !strContains(tags, srv.ServiceTag) { - tags = append(tags, srv.ServiceTag) + tags, ok := services[srv.ServiceName] + if !ok { services[srv.ServiceName] = tags } + + for _, tag := range srv.ServiceTags { + if !strContains(tags, tag) { + tags = append(tags, tag) + services[srv.ServiceName] = tags + } + } } return idx, services } @@ -508,10 +511,26 @@ func (s *StateStore) ServiceTagNodes(service, tag string) (uint64, structs.Servi panic(fmt.Errorf("Failed to get last index: %v", err)) } - res, err := s.serviceTable.GetTxn(tx, "service", service, tag) + res, err := s.serviceTable.GetTxn(tx, "service", service) + res = serviceTagFilter(res, tag) return idx, s.parseServiceNodes(tx, s.nodeTable, res, err) } +// serviceTagFilter is used to filter a list of *structs.ServiceNode which do +// not have the specified tag +func serviceTagFilter(l []interface{}, tag string) []interface{} { + n := len(l) + for i := 0; i < n; i++ { + srv := l[i].(*structs.ServiceNode) + if !strContains(srv.ServiceTags, tag) { + l[i], l[n-1] = l[n-1], nil + i-- + n-- + } + } + return l[:n] +} + // parseServiceNodes parses results ServiceNodes and ServiceTagNodes func (s *StateStore) parseServiceNodes(tx *MDBTxn, table *MDBTable, res []interface{}, err error) structs.ServiceNodes { nodes := make(structs.ServiceNodes, len(res)) @@ -668,7 +687,8 @@ func (s *StateStore) CheckServiceTagNodes(service, tag string) (uint64, structs. panic(fmt.Errorf("Failed to get last index: %v", err)) } - res, err := s.serviceTable.GetTxn(tx, "service", service, tag) + res, err := s.serviceTable.GetTxn(tx, "service", service) + res = serviceTagFilter(res, tag) return idx, s.parseCheckServiceNodes(tx, res, err) } @@ -704,7 +724,7 @@ func (s *StateStore) parseCheckServiceNodes(tx *MDBTxn, res []interface{}, err e nodes[i].Service = structs.NodeService{ ID: srv.ServiceID, Service: srv.ServiceName, - Tag: srv.ServiceTag, + Tags: srv.ServiceTags, Port: srv.ServicePort, } nodes[i].Checks = checks From 4a1faf435c1e0795b95a842017afb3101041ddb5 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 12:03:10 -0700 Subject: [PATCH 3/8] consul: Updating all the tests --- consul/catalog_endpoint_test.go | 24 ++++----- consul/fsm_test.go | 16 +++--- consul/health_endpoint_test.go | 8 +-- consul/state_store_test.go | 96 ++++++++++++++++----------------- 4 files changed, 72 insertions(+), 72 deletions(-) diff --git a/consul/catalog_endpoint_test.go b/consul/catalog_endpoint_test.go index 9ebd079d1f..1184d6ddb5 100644 --- a/consul/catalog_endpoint_test.go +++ b/consul/catalog_endpoint_test.go @@ -23,7 +23,7 @@ func TestCatalogRegister(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 8000, }, } @@ -79,7 +79,7 @@ func TestCatalogRegister_ForwardLeader(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 8000, }, } @@ -116,7 +116,7 @@ func TestCatalogRegister_ForwardDC(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 8000, }, } @@ -277,7 +277,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", "primary", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, 5000}) if err := client.Call("Catalog.ListServices", &args, &out); err != nil { t.Fatalf("err: %v", err) @@ -287,7 +287,7 @@ func TestCatalogListServices(t *testing.T) { t.Fatalf("bad: %v", out) } // Consul service should auto-register - if len(out.Services["consul"]) != 1 { + if _, ok := out.Services["consul"]; !ok { t.Fatalf("bad: %v", out) } if len(out.Services["db"]) != 1 { @@ -327,7 +327,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", "primary", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, 5000}) }() // Re-run the query @@ -418,7 +418,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", "primary", 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, 5000}) if err := client.Call("Catalog.ServiceNodes", &args, &out); err != nil { t.Fatalf("err: %v", err) @@ -462,8 +462,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", "primary", 5000}) - s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{"web", "web", "", 80}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, 5000}) + s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, 80}) if err := client.Call("Catalog.NodeServices", &args, &out); err != nil { t.Fatalf("err: %v", err) @@ -476,10 +476,10 @@ func TestCatalogNodeServices(t *testing.T) { t.Fatalf("bad: %v", out) } services := out.NodeServices.Services - if services["db"].Tag != "primary" || services["db"].Port != 5000 { + if !strContains(services["db"].Tags, "primary") || services["db"].Port != 5000 { t.Fatalf("bad: %v", out) } - if services["web"].Tag != "" || services["web"].Port != 80 { + if services["web"].Tags != nil || services["web"].Port != 80 { t.Fatalf("bad: %v", out) } } @@ -498,7 +498,7 @@ func TestCatalogRegister_FailedCase1(t *testing.T) { Address: "127.0.0.2", Service: &structs.NodeService{ Service: "web", - Tag: "", + Tags: nil, Port: 8000, }, } diff --git a/consul/fsm_test.go b/consul/fsm_test.go index 24f7c7cbbf..ac8453bec2 100644 --- a/consul/fsm_test.go +++ b/consul/fsm_test.go @@ -85,7 +85,7 @@ func TestFSM_RegisterNode_Service(t *testing.T) { Service: &structs.NodeService{ ID: "db", Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 8000, }, Check: &structs.HealthCheck{ @@ -138,7 +138,7 @@ func TestFSM_DeregisterService(t *testing.T) { Service: &structs.NodeService{ ID: "db", Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 8000, }, } @@ -248,7 +248,7 @@ func TestFSM_DeregisterNode(t *testing.T) { Service: &structs.NodeService{ ID: "db", Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 8000, }, Check: &structs.HealthCheck{ @@ -311,10 +311,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", "", 80}) - fsm.state.EnsureService(4, "foo", &structs.NodeService{"db", "db", "primary", 5000}) - fsm.state.EnsureService(5, "baz", &structs.NodeService{"web", "web", "", 80}) - fsm.state.EnsureService(6, "baz", &structs.NodeService{"db", "db", "secondary", 5000}) + fsm.state.EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, 80}) + fsm.state.EnsureService(4, "foo", &structs.NodeService{"db", "db", []string{"primary"}, 5000}) + fsm.state.EnsureService(5, "baz", &structs.NodeService{"web", "web", nil, 80}) + fsm.state.EnsureService(6, "baz", &structs.NodeService{"db", "db", []string{"secondary"}, 5000}) fsm.state.EnsureCheck(7, &structs.HealthCheck{ Node: "foo", CheckID: "web", @@ -363,7 +363,7 @@ func TestFSM_SnapshotRestore(t *testing.T) { if len(fooSrv.Services) != 2 { t.Fatalf("Bad: %v", fooSrv) } - if fooSrv.Services["db"].Tag != "primary" { + if !strContains(fooSrv.Services["db"].Tags, "primary") { t.Fatalf("Bad: %v", fooSrv) } if fooSrv.Services["db"].Port != 5000 { diff --git a/consul/health_endpoint_test.go b/consul/health_endpoint_test.go index 000b57ee47..465f358467 100644 --- a/consul/health_endpoint_test.go +++ b/consul/health_endpoint_test.go @@ -160,7 +160,7 @@ func TestHealth_ServiceNodes(t *testing.T) { Service: &structs.NodeService{ ID: "db", Service: "db", - Tag: "master", + Tags: []string{"master"}, }, Check: &structs.HealthCheck{ Name: "db connect", @@ -180,7 +180,7 @@ func TestHealth_ServiceNodes(t *testing.T) { Service: &structs.NodeService{ ID: "db", Service: "db", - Tag: "slave", + Tags: []string{"slave"}, }, Check: &structs.HealthCheck{ Name: "db connect", @@ -213,10 +213,10 @@ func TestHealth_ServiceNodes(t *testing.T) { if nodes[1].Node.Node != "bar" { t.Fatalf("Bad: %v", nodes[1]) } - if nodes[0].Service.Tag != "master" { + if !strContains(nodes[0].Service.Tags, "master") { t.Fatalf("Bad: %v", nodes[0]) } - if nodes[1].Service.Tag != "slave" { + if !strContains(nodes[1].Service.Tags, "slave") { t.Fatalf("Bad: %v", nodes[1]) } if nodes[0].Checks[0].Status != structs.HealthPassing { diff --git a/consul/state_store_test.go b/consul/state_store_test.go index 5e5bf3e8e1..dbdb2bcc23 100644 --- a/consul/state_store_test.go +++ b/consul/state_store_test.go @@ -96,15 +96,15 @@ func TestEnsureService(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(11, "foo", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(11, "foo", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", "", 5001}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, 5001}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "foo", &structs.NodeService{"db", "db", "master", 8000}); err != nil { + if err := store.EnsureService(13, "foo", &structs.NodeService{"db", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v", err) } @@ -117,7 +117,7 @@ func TestEnsureService(t *testing.T) { if !ok { t.Fatalf("missing api: %#v", services) } - if entry.Tag != "" || entry.Port != 5001 { + if entry.Tags != nil || entry.Port != 5001 { t.Fatalf("Bad entry: %#v", entry) } @@ -125,7 +125,7 @@ func TestEnsureService(t *testing.T) { if !ok { t.Fatalf("missing db: %#v", services) } - if entry.Tag != "master" || entry.Port != 8000 { + if !strContains(entry.Tags, "master") || entry.Port != 8000 { t.Fatalf("Bad entry: %#v", entry) } } @@ -141,15 +141,15 @@ func TestEnsureService_DuplicateNode(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(11, "foo", &structs.NodeService{"api1", "api", "", 5000}); err != nil { + if err := store.EnsureService(11, "foo", &structs.NodeService{"api1", "api", nil, 5000}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api2", "api", "", 5001}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api2", "api", nil, 5001}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "foo", &structs.NodeService{"api3", "api", "", 5002}); err != nil { + if err := store.EnsureService(13, "foo", &structs.NodeService{"api3", "api", nil, 5002}); err != nil { t.Fatalf("err: %v", err) } @@ -162,7 +162,7 @@ func TestEnsureService_DuplicateNode(t *testing.T) { if !ok { t.Fatalf("missing api: %#v", services) } - if entry.Tag != "" || entry.Port != 5000 { + if entry.Tags != nil || entry.Port != 5000 { t.Fatalf("Bad entry: %#v", entry) } @@ -170,7 +170,7 @@ func TestEnsureService_DuplicateNode(t *testing.T) { if !ok { t.Fatalf("missing api: %#v", services) } - if entry.Tag != "" || entry.Port != 5001 { + if entry.Tags != nil || entry.Port != 5001 { t.Fatalf("Bad entry: %#v", entry) } @@ -178,7 +178,7 @@ func TestEnsureService_DuplicateNode(t *testing.T) { if !ok { t.Fatalf("missing api: %#v", services) } - if entry.Tag != "" || entry.Port != 5002 { + if entry.Tags != nil || entry.Port != 5002 { t.Fatalf("Bad entry: %#v", entry) } } @@ -194,7 +194,7 @@ func TestDeleteNodeService(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v", err) } @@ -242,11 +242,11 @@ func TestDeleteNodeService_One(t *testing.T) { t.Fatalf("err: %v", err) } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(13, "foo", &structs.NodeService{"api2", "api", "", 5001}); err != nil { + if err := store.EnsureService(13, "foo", &structs.NodeService{"api2", "api", nil, 5001}); err != nil { t.Fatalf("err: %v", err) } @@ -279,7 +279,7 @@ func TestDeleteNode(t *testing.T) { t.Fatalf("err: %v") } - if err := store.EnsureService(21, "foo", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(21, "foo", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v") } @@ -338,15 +338,15 @@ func TestGetServices(t *testing.T) { t.Fatalf("err: %v") } - if err := store.EnsureService(32, "foo", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(32, "foo", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(33, "foo", &structs.NodeService{"db", "db", "master", 8000}); err != nil { + if err := store.EnsureService(33, "foo", &structs.NodeService{"db", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(34, "bar", &structs.NodeService{"db", "db", "slave", 8000}); err != nil { + if err := store.EnsureService(34, "bar", &structs.NodeService{"db", "db", []string{"slave"}, 8000}); err != nil { t.Fatalf("err: %v") } @@ -359,7 +359,7 @@ func TestGetServices(t *testing.T) { if !ok { t.Fatalf("missing api: %#v", services) } - if len(tags) != 1 || tags[0] != "" { + if len(tags) != 0 { t.Fatalf("Bad entry: %#v", tags) } @@ -388,23 +388,23 @@ func TestServiceNodes(t *testing.T) { t.Fatalf("err: %v") } - if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(12, "foo", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(13, "bar", &structs.NodeService{"api", "api", "", 5000}); err != nil { + if err := store.EnsureService(13, "bar", &structs.NodeService{"api", "api", nil, 5000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(14, "foo", &structs.NodeService{"db", "db", "master", 8000}); err != nil { + if err := store.EnsureService(14, "foo", &structs.NodeService{"db", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(15, "bar", &structs.NodeService{"db", "db", "slave", 8000}); err != nil { + if err := store.EnsureService(15, "bar", &structs.NodeService{"db", "db", []string{"slave"}, 8000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(16, "bar", &structs.NodeService{"db2", "db", "slave", 8001}); err != nil { + if err := store.EnsureService(16, "bar", &structs.NodeService{"db2", "db", []string{"slave"}, 8001}); err != nil { t.Fatalf("err: %v") } @@ -424,7 +424,7 @@ func TestServiceNodes(t *testing.T) { if nodes[0].ServiceID != "db" { t.Fatalf("bad: %v", nodes) } - if nodes[0].ServiceTag != "master" { + if !strContains(nodes[0].ServiceTags, "master") { t.Fatalf("bad: %v", nodes) } if nodes[0].ServicePort != 8000 { @@ -440,7 +440,7 @@ func TestServiceNodes(t *testing.T) { if nodes[1].ServiceID != "db" { t.Fatalf("bad: %v", nodes) } - if nodes[1].ServiceTag != "slave" { + if !strContains(nodes[1].ServiceTags, "slave") { t.Fatalf("bad: %v", nodes) } if nodes[1].ServicePort != 8000 { @@ -456,7 +456,7 @@ func TestServiceNodes(t *testing.T) { if nodes[2].ServiceID != "db2" { t.Fatalf("bad: %v", nodes) } - if nodes[2].ServiceTag != "slave" { + if !strContains(nodes[2].ServiceTags, "slave") { t.Fatalf("bad: %v", nodes) } if nodes[2].ServicePort != 8001 { @@ -479,15 +479,15 @@ func TestServiceTagNodes(t *testing.T) { t.Fatalf("err: %v") } - if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", "master", 8000}); err != nil { + if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", "slave", 8001}); err != nil { + if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", []string{"slave"}, 8001}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", "slave", 8000}); err != nil { + if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", []string{"slave"}, 8000}); err != nil { t.Fatalf("err: %v") } @@ -504,7 +504,7 @@ func TestServiceTagNodes(t *testing.T) { if nodes[0].Address != "127.0.0.1" { t.Fatalf("bad: %v", nodes) } - if nodes[0].ServiceTag != "master" { + if !strContains(nodes[0].ServiceTags, "master") { t.Fatalf("bad: %v", nodes) } if nodes[0].ServicePort != 8000 { @@ -527,15 +527,15 @@ func TestStoreSnapshot(t *testing.T) { t.Fatalf("err: %v") } - if err := store.EnsureService(10, "foo", &structs.NodeService{"db", "db", "master", 8000}); err != nil { + if err := store.EnsureService(10, "foo", &structs.NodeService{"db", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(11, "foo", &structs.NodeService{"db2", "db", "slave", 8001}); err != nil { + if err := store.EnsureService(11, "foo", &structs.NodeService{"db2", "db", []string{"slave"}, 8001}); err != nil { t.Fatalf("err: %v") } - if err := store.EnsureService(12, "bar", &structs.NodeService{"db", "db", "slave", 8000}); err != nil { + if err := store.EnsureService(12, "bar", &structs.NodeService{"db", "db", []string{"slave"}, 8000}); err != nil { t.Fatalf("err: %v") } @@ -580,15 +580,15 @@ func TestStoreSnapshot(t *testing.T) { // Ensure we get the service entries services := snap.NodeServices("foo") - if services.Services["db"].Tag != "master" { + if !strContains(services.Services["db"].Tags, "master") { t.Fatalf("bad: %v", services) } - if services.Services["db2"].Tag != "slave" { + if !strContains(services.Services["db2"].Tags, "slave") { t.Fatalf("bad: %v", services) } services = snap.NodeServices("bar") - if services.Services["db"].Tag != "slave" { + if !strContains(services.Services["db"].Tags, "slave") { t.Fatalf("bad: %v", services) } @@ -624,10 +624,10 @@ func TestStoreSnapshot(t *testing.T) { } // Make some changes! - if err := store.EnsureService(14, "foo", &structs.NodeService{"db", "db", "slave", 8000}); err != nil { + if err := store.EnsureService(14, "foo", &structs.NodeService{"db", "db", []string{"slave"}, 8000}); err != nil { t.Fatalf("err: %v", err) } - if err := store.EnsureService(15, "bar", &structs.NodeService{"db", "db", "master", 8000}); err != nil { + if err := store.EnsureService(15, "bar", &structs.NodeService{"db", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v", err) } if err := store.EnsureNode(16, structs.Node{"baz", "127.0.0.3"}); err != nil { @@ -656,15 +656,15 @@ func TestStoreSnapshot(t *testing.T) { // Ensure old service entries services = snap.NodeServices("foo") - if services.Services["db"].Tag != "master" { + if !strContains(services.Services["db"].Tags, "master") { t.Fatalf("bad: %v", services) } - if services.Services["db2"].Tag != "slave" { + if !strContains(services.Services["db2"].Tags, "slave") { t.Fatalf("bad: %v", services) } services = snap.NodeServices("bar") - if services.Services["db"].Tag != "slave" { + if !strContains(services.Services["db"].Tags, "slave") { t.Fatalf("bad: %v", services) } @@ -709,7 +709,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", "master", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } check := &structs.HealthCheck{ @@ -791,7 +791,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", "master", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } check := &structs.HealthCheck{ @@ -841,7 +841,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", "master", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } check := &structs.HealthCheck{ @@ -922,7 +922,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", "master", 8000}); err != nil { + if err := store.EnsureService(2, "foo", &structs.NodeService{"db1", "db", []string{"master"}, 8000}); err != nil { t.Fatalf("err: %v") } check := &structs.HealthCheck{ @@ -964,7 +964,7 @@ func TestSS_Register_Deregister_Query(t *testing.T) { srv := &structs.NodeService{ "statsite-box-stats", "statsite-box-stats", - "", + nil, 0} if err := store.EnsureService(2, "foo", srv); err != nil { t.Fatalf("err: %v") @@ -973,7 +973,7 @@ func TestSS_Register_Deregister_Query(t *testing.T) { srv = &structs.NodeService{ "statsite-share-stats", "statsite-share-stats", - "", + nil, 0} if err := store.EnsureService(3, "foo", srv); err != nil { t.Fatalf("err: %v") From 7f399a0eccb3a7ac54ec2cbf6e616616eb823dc4 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 12:07:17 -0700 Subject: [PATCH 4/8] consul: Adding test for multiple tags with lookup --- consul/state_store_test.go | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/consul/state_store_test.go b/consul/state_store_test.go index dbdb2bcc23..dd46adf6ae 100644 --- a/consul/state_store_test.go +++ b/consul/state_store_test.go @@ -512,6 +512,82 @@ func TestServiceTagNodes(t *testing.T) { } } +func TestServiceTagNodes_MultipleTags(t *testing.T) { + store, err := testStateStore() + if err != nil { + t.Fatalf("err: %v", err) + } + defer store.Close() + + if err := store.EnsureNode(15, structs.Node{"foo", "127.0.0.1"}); err != nil { + t.Fatalf("err: %v") + } + + if err := store.EnsureNode(16, structs.Node{"bar", "127.0.0.2"}); err != nil { + t.Fatalf("err: %v") + } + + if err := store.EnsureService(17, "foo", &structs.NodeService{"db", "db", []string{"master", "v2"}, 8000}); err != nil { + t.Fatalf("err: %v") + } + + if err := store.EnsureService(18, "foo", &structs.NodeService{"db2", "db", []string{"slave", "v2", "dev"}, 8001}); err != nil { + t.Fatalf("err: %v") + } + + if err := store.EnsureService(19, "bar", &structs.NodeService{"db", "db", []string{"slave", "v2"}, 8000}); err != nil { + t.Fatalf("err: %v") + } + + idx, nodes := store.ServiceTagNodes("db", "master") + if idx != 19 { + t.Fatalf("bad: %v", idx) + } + if len(nodes) != 1 { + t.Fatalf("bad: %v", nodes) + } + if nodes[0].Node != "foo" { + t.Fatalf("bad: %v", nodes) + } + if nodes[0].Address != "127.0.0.1" { + t.Fatalf("bad: %v", nodes) + } + if !strContains(nodes[0].ServiceTags, "master") { + t.Fatalf("bad: %v", nodes) + } + if nodes[0].ServicePort != 8000 { + t.Fatalf("bad: %v", nodes) + } + + idx, nodes = store.ServiceTagNodes("db", "v2") + if idx != 19 { + t.Fatalf("bad: %v", idx) + } + if len(nodes) != 3 { + t.Fatalf("bad: %v", nodes) + } + + idx, nodes = store.ServiceTagNodes("db", "dev") + if idx != 19 { + t.Fatalf("bad: %v", idx) + } + if len(nodes) != 1 { + t.Fatalf("bad: %v", nodes) + } + if nodes[0].Node != "foo" { + t.Fatalf("bad: %v", nodes) + } + if nodes[0].Address != "127.0.0.1" { + t.Fatalf("bad: %v", nodes) + } + if !strContains(nodes[0].ServiceTags, "dev") { + t.Fatalf("bad: %v", nodes) + } + if nodes[0].ServicePort != 8001 { + t.Fatalf("bad: %v", nodes) + } +} + func TestStoreSnapshot(t *testing.T) { store, err := testStateStore() if err != nil { From 8db2e3bd5844cd7dcc7b0c80a3eb1168f6e78575 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 12:12:23 -0700 Subject: [PATCH 5/8] agent: Support multiple tags per service registration --- command/agent/agent_endpoint_test.go | 4 ++-- command/agent/agent_test.go | 2 +- command/agent/catalog_endpoint_test.go | 4 ++-- command/agent/config_test.go | 4 ++-- command/agent/dns_test.go | 18 +++++++++--------- command/agent/local_test.go | 8 ++++---- command/agent/structs.go | 4 ++-- command/agent/util.go | 10 ++++++++++ 8 files changed, 32 insertions(+), 22 deletions(-) diff --git a/command/agent/agent_endpoint_test.go b/command/agent/agent_endpoint_test.go index 14a07ea2cb..088e42ee5a 100644 --- a/command/agent/agent_endpoint_test.go +++ b/command/agent/agent_endpoint_test.go @@ -19,7 +19,7 @@ func TestHTTPAgentServices(t *testing.T) { srv1 := &structs.NodeService{ ID: "mysql", Service: "mysql", - Tag: "master", + Tags: []string{"master"}, Port: 5000, } srv.agent.state.AddService(srv1) @@ -394,7 +394,7 @@ func TestHTTPAgentRegisterService(t *testing.T) { } args := &ServiceDefinition{ Name: "test", - Tag: "master", + Tags: []string{"master"}, Port: 8000, Check: CheckType{ TTL: 15 * time.Second, diff --git a/command/agent/agent_test.go b/command/agent/agent_test.go index 8dde02690b..7d59d420e2 100644 --- a/command/agent/agent_test.go +++ b/command/agent/agent_test.go @@ -108,7 +108,7 @@ func TestAgent_AddService(t *testing.T) { srv := &structs.NodeService{ ID: "redis", Service: "redis", - Tag: "foo", + Tags: []string{"foo"}, Port: 8000, } chk := &CheckType{TTL: time.Minute} diff --git a/command/agent/catalog_endpoint_test.go b/command/agent/catalog_endpoint_test.go index 03928aaf93..573ddf8863 100644 --- a/command/agent/catalog_endpoint_test.go +++ b/command/agent/catalog_endpoint_test.go @@ -249,7 +249,7 @@ func TestCatalogServiceNodes(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "api", - Tag: "a", + Tags: []string{"a"}, }, } var out struct{} @@ -293,7 +293,7 @@ func TestCatalogNodeServices(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "api", - Tag: "a", + Tags: []string{"a"}, }, } var out struct{} diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 878e61a0e7..d6d3f6eaa2 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -197,7 +197,7 @@ func TestDecodeConfig(t *testing.T) { func TestDecodeConfig_Service(t *testing.T) { // Basics - input := `{"service": {"id": "red1", "name": "redis", "tag": "master", "port":8000, "check": {"script": "/bin/check_redis", "interval": "10s", "ttl": "15s" }}}` + input := `{"service": {"id": "red1", "name": "redis", "tags": ["master"], "port":8000, "check": {"script": "/bin/check_redis", "interval": "10s", "ttl": "15s" }}}` config, err := DecodeConfig(bytes.NewReader([]byte(input))) if err != nil { t.Fatalf("err: %s", err) @@ -216,7 +216,7 @@ func TestDecodeConfig_Service(t *testing.T) { t.Fatalf("bad: %v", serv) } - if serv.Tag != "master" { + if !strContains(serv.Tags, "master") { t.Fatalf("bad: %v", serv) } diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index 68592923d1..e065ea4a46 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -219,7 +219,7 @@ func TestDNS_ServiceLookup(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 12345, }, } @@ -279,7 +279,7 @@ func TestDNS_ServiceLookup_Dedup(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 12345, }, } @@ -295,7 +295,7 @@ func TestDNS_ServiceLookup_Dedup(t *testing.T) { Service: &structs.NodeService{ ID: "db2", Service: "db", - Tag: "slave", + Tags: []string{"slave"}, Port: 12345, }, } @@ -310,7 +310,7 @@ func TestDNS_ServiceLookup_Dedup(t *testing.T) { Service: &structs.NodeService{ ID: "db3", Service: "db", - Tag: "slave", + Tags: []string{"slave"}, Port: 12346, }, } @@ -355,7 +355,7 @@ func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 12345, }, } @@ -371,7 +371,7 @@ func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) { Service: &structs.NodeService{ ID: "db2", Service: "db", - Tag: "slave", + Tags: []string{"slave"}, Port: 12345, }, } @@ -386,7 +386,7 @@ func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) { Service: &structs.NodeService{ ID: "db3", Service: "db", - Tag: "slave", + Tags: []string{"slave"}, Port: 12346, }, } @@ -482,7 +482,7 @@ func TestDNS_ServiceLookup_FilterCritical(t *testing.T) { Address: "127.0.0.1", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 12345, }, Check: &structs.HealthCheck{ @@ -502,7 +502,7 @@ func TestDNS_ServiceLookup_FilterCritical(t *testing.T) { Address: "127.0.0.2", Service: &structs.NodeService{ Service: "db", - Tag: "master", + Tags: []string{"master"}, Port: 12345, }, Check: &structs.HealthCheck{ diff --git a/command/agent/local_test.go b/command/agent/local_test.go index be6bdbbebb..a84c1a2929 100644 --- a/command/agent/local_test.go +++ b/command/agent/local_test.go @@ -29,7 +29,7 @@ func TestAgentAntiEntropy_Services(t *testing.T) { srv1 := &structs.NodeService{ ID: "mysql", Service: "mysql", - Tag: "master", + Tags: []string{"master"}, Port: 5000, } agent.state.AddService(srv1) @@ -42,7 +42,7 @@ func TestAgentAntiEntropy_Services(t *testing.T) { srv2 := &structs.NodeService{ ID: "redis", Service: "redis", - Tag: "", + Tags: nil, Port: 8000, } agent.state.AddService(srv2) @@ -59,7 +59,7 @@ func TestAgentAntiEntropy_Services(t *testing.T) { srv3 := &structs.NodeService{ ID: "web", Service: "web", - Tag: "", + Tags: nil, Port: 80, } agent.state.AddService(srv3) @@ -68,7 +68,7 @@ func TestAgentAntiEntropy_Services(t *testing.T) { srv4 := &structs.NodeService{ ID: "lb", Service: "lb", - Tag: "", + Tags: nil, Port: 443, } args.Service = srv4 diff --git a/command/agent/structs.go b/command/agent/structs.go index 876ce4f8d8..2370c3936a 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -8,7 +8,7 @@ import ( type ServiceDefinition struct { ID string Name string - Tag string + Tags []string Port int Check CheckType } @@ -17,7 +17,7 @@ func (s *ServiceDefinition) NodeService() *structs.NodeService { ns := &structs.NodeService{ ID: s.ID, Service: s.Name, - Tag: s.Tag, + Tags: s.Tags, Port: s.Port, } if ns.ID == "" && ns.Service != "" { diff --git a/command/agent/util.go b/command/agent/util.go index f6ce7747e9..8f6103a804 100644 --- a/command/agent/util.go +++ b/command/agent/util.go @@ -29,3 +29,13 @@ func aeScale(interval time.Duration, n int) time.Duration { func randomStagger(intv time.Duration) time.Duration { return time.Duration(uint64(rand.Int63()) % uint64(intv)) } + +// strContains checks if a list contains a string +func strContains(l []string, s string) bool { + for _, v := range l { + if v == s { + return true + } + } + return false +} From 6e26f463a534ea9300fd8e9bccd9e01d6566b7ca Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 14:18:57 -0700 Subject: [PATCH 6/8] website: Updating the tags documentation --- website/source/docs/agent/http.html.markdown | 22 +++++++++---------- .../source/docs/agent/services.html.markdown | 8 +++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/website/source/docs/agent/http.html.markdown b/website/source/docs/agent/http.html.markdown index b938e7ec94..6150a5946b 100644 --- a/website/source/docs/agent/http.html.markdown +++ b/website/source/docs/agent/http.html.markdown @@ -169,7 +169,7 @@ This endpoint is hit with a GET and returns a JSON body like this: "redis":{ "ID":"redis", "Service":"redis", - "Tag":"", + "Tags":[], "Port":8000 } } @@ -301,7 +301,7 @@ body must look like: { "ID": "redis1", "Name": "redis", - "Tag": "master", + "Tags": ["master", "v1"], "Port": 8000, "Check": { "Script": "/usr/local/bin/check_redis.py", @@ -312,7 +312,7 @@ body must look like: The `Name` field is mandatory, If an `ID` is not provided, it is set to `Name`. You cannot have duplicate `ID` entries per agent, so it may be necessary to provide an ID. -`Tag`, `Port` and `Check` are optional. If `Check` is provided, only one of `Script` and `Interval` +`Tags`, `Port` and `Check` are optional. If `Check` is provided, only one of `Script` and `Interval` or `TTL` should be provided. There is more information about checks [here](/docs/agent/checks.html). The created check will be named "service:\". @@ -361,7 +361,7 @@ body must look like: "Service": { "ID": "redis1", "Service": "redis", - "Tag": "master", + "Tags": ["master", "v1"], "Port": 8000, }, "Check": { @@ -381,7 +381,7 @@ the node with the catalog. If the `Service` key is provided, then the service will also be registered. If `ID` is not provided, it will be defaulted to `Service`. It is mandated that the -ID be node-unique. Both `Tag` and `Port` can be omitted. +ID be node-unique. Both `Tags` and `Port` can be omitted. If the `Check` key is provided, then a health check will also be registered. It is important to remember that this register API is very low level. This manipulates @@ -480,8 +480,8 @@ however the dc can be provided using the "?dc=" query parameter. It returns a JSON body like this: { - "consul":[""], - "redis":[""], + "consul":[], + "redis":[], "postgresql":["master","slave"] } @@ -508,7 +508,7 @@ It returns a JSON body like this: "Address":"10.1.10.12", "ServiceID":"redis", "ServiceName":"redis", - "ServiceTag":"", + "ServiceTags":[], "ServicePort":8000 } ] @@ -533,13 +533,13 @@ It returns a JSON body like this: "consul":{ "ID":"consul", "Service":"consul", - "Tag":"", + "Tags":[], "Port":8300 }, "redis":{ "ID":"redis", "Service":"redis", - "Tag":"", + "Tags":["v1"], "Port":8000 } } @@ -655,7 +655,7 @@ It returns a JSON body like this: "Service":{ "ID":"redis", "Service":"redis", - "Tag":"", + "Tags":[], "Port":8000 }, "Checks":[ diff --git a/website/source/docs/agent/services.html.markdown b/website/source/docs/agent/services.html.markdown index 19d8cbed09..22f958588c 100644 --- a/website/source/docs/agent/services.html.markdown +++ b/website/source/docs/agent/services.html.markdown @@ -20,7 +20,7 @@ A service definition that is a script looks like: { "service": { "name": "redis", - "tag": "master", + "tags": ["master"], "port": 8000, "check": { "script": "/usr/local/bin/check_redis.py", @@ -30,12 +30,12 @@ A service definition that is a script looks like: } A service definition must include a `name`, and may optionally provide -an `id`, `tag`, `port`, and `check`. The `id` is set to the `name` if not +an `id`, `tags`, `port`, and `check`. The `id` is set to the `name` if not provided. It is required that all services have a unique ID, so if names might conflict, then unique ID's should be provided. -The `tag` is an opaque value to Consul, but can be used to distinguish -between "master" or "slave" nodes, or any other service level labels. +The `tags` is a list of opaque value to Consul, but can be used to distinguish +between "master" or "slave" nodes, different versions, or any other service level labels. The `port` can be used as well to make a service oriented architecture simpler to configure. This way the address and port of a service can be discovered. From 30307f877b6816214f355531b6f57976370d190b Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 14:22:43 -0700 Subject: [PATCH 7/8] agent: Add backwards compatibility hack for old 'tag' definitions --- command/agent/config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/command/agent/config.go b/command/agent/config.go index ad8a8b8754..a2e83740b9 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -186,6 +186,14 @@ func DecodeServiceDefinition(raw interface{}) (*ServiceDefinition, error) { if !ok { goto AFTER_FIX } + + // If no 'tags', handle the deprecated 'tag' value. + if _, ok := rawMap["tags"]; !ok { + if tag, ok := rawMap["tag"]; ok { + rawMap["tags"] = []interface{}{tag} + } + } + sub, ok = rawMap["check"] if !ok { goto AFTER_FIX From 7c31a313db4baeb65eca62f9a7a4e138ea944caf Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Thu, 3 Apr 2014 14:40:05 -0700 Subject: [PATCH 8/8] website: Correct some empty lists to null --- website/source/docs/agent/http.html.markdown | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/source/docs/agent/http.html.markdown b/website/source/docs/agent/http.html.markdown index 6150a5946b..e9e95a00cb 100644 --- a/website/source/docs/agent/http.html.markdown +++ b/website/source/docs/agent/http.html.markdown @@ -169,7 +169,7 @@ This endpoint is hit with a GET and returns a JSON body like this: "redis":{ "ID":"redis", "Service":"redis", - "Tags":[], + "Tags":null, "Port":8000 } } @@ -508,7 +508,7 @@ It returns a JSON body like this: "Address":"10.1.10.12", "ServiceID":"redis", "ServiceName":"redis", - "ServiceTags":[], + "ServiceTags":null, "ServicePort":8000 } ] @@ -533,7 +533,7 @@ It returns a JSON body like this: "consul":{ "ID":"consul", "Service":"consul", - "Tags":[], + "Tags":null, "Port":8300 }, "redis":{ @@ -655,7 +655,7 @@ It returns a JSON body like this: "Service":{ "ID":"redis", "Service":"redis", - "Tags":[], + "Tags":null, "Port":8000 }, "Checks":[