diff --git a/command/agent/dns.go b/command/agent/dns.go index 8575723325..1b270bae7d 100644 --- a/command/agent/dns.go +++ b/command/agent/dns.go @@ -409,17 +409,17 @@ RPC: } // Add the node record - records := d.formatNodeRecord(&out.NodeServices.Node, req.Question[0].Name, - qType, d.config.NodeTTL) + records := d.formatNodeRecord(&out.NodeServices.Node, out.NodeServices.Node.Address, + req.Question[0].Name, qType, d.config.NodeTTL) if records != nil { resp.Answer = append(resp.Answer, records...) } } // formatNodeRecord takes a Node and returns an A, AAAA, or CNAME record -func (d *DNSServer) formatNodeRecord(node *structs.Node, qName string, qType uint16, ttl time.Duration) (records []dns.RR) { +func (d *DNSServer) formatNodeRecord(node *structs.Node, addr, qName string, qType uint16, ttl time.Duration) (records []dns.RR) { // Parse the IP - ip := net.ParseIP(node.Address) + ip := net.ParseIP(addr) var ipv4 net.IP if ip != nil { ipv4 = ip.To4() @@ -457,7 +457,7 @@ func (d *DNSServer) formatNodeRecord(node *structs.Node, qName string, qType uin Class: dns.ClassINET, Ttl: uint32(ttl / time.Second), }, - Target: dns.Fqdn(node.Address), + Target: dns.Fqdn(addr), } records = append(records, cnRec) @@ -584,13 +584,17 @@ func (d *DNSServer) serviceNodeRecords(nodes structs.CheckServiceNodes, req, res // Avoid duplicate entries, possible if a node has // the same service on multiple ports, etc. addr := node.Node.Address + if node.Service.Address != "" { + addr = node.Service.Address + } + if _, ok := handled[addr]; ok { continue } handled[addr] = struct{}{} // Add the node record - records := d.formatNodeRecord(&node.Node, qName, qType, ttl) + records := d.formatNodeRecord(&node.Node, addr, qName, qType, ttl) if records != nil { resp.Answer = append(resp.Answer, records...) } @@ -603,7 +607,7 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes for _, node := range nodes { // Avoid duplicate entries, possible if a node has // the same service the same port, etc. - tuple := fmt.Sprintf("%s:%d", node.Node.Node, node.Service.Port) + tuple := fmt.Sprintf("%s:%s:%d", node.Node.Node, node.Service.Address, node.Service.Port) if _, ok := handled[tuple]; ok { continue } @@ -624,8 +628,14 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes } resp.Answer = append(resp.Answer, srvRec) + // Determine advertised address + addr := node.Node.Address + if node.Service.Address != "" { + addr = node.Service.Address + } + // Add the extra record - records := d.formatNodeRecord(&node.Node, srvRec.Target, dns.TypeANY, ttl) + records := d.formatNodeRecord(&node.Node, addr, srvRec.Target, dns.TypeANY, ttl) if records != nil { resp.Extra = append(resp.Extra, records...) } diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index 4c713bb4c2..dbd7fad745 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -430,6 +430,7 @@ func TestDNS_ReverseLookup_IPV6(t *testing.T) { t.Fatalf("Bad: %#v", ptrRec) } } + func TestDNS_ServiceLookup(t *testing.T) { dir, srv := makeDNSServer(t) defer os.RemoveAll(dir) @@ -497,6 +498,74 @@ func TestDNS_ServiceLookup(t *testing.T) { } } +func TestDNS_ServiceLookup_ServiceAddress(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Address: "127.0.0.2", + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeSRV) + + c := new(dns.Client) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.2" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } +} + func TestDNS_CaseInsensitiveServiceLookup(t *testing.T) { dir, srv := makeDNSServer(t) defer os.RemoveAll(dir) diff --git a/command/agent/local_test.go b/command/agent/local_test.go index 7595b8fc12..b47adb9448 100644 --- a/command/agent/local_test.go +++ b/command/agent/local_test.go @@ -77,6 +77,24 @@ func TestAgentAntiEntropy_Services(t *testing.T) { t.Fatalf("err: %v", err) } + // Exists both, different address (update) + srv5 := &structs.NodeService{ + ID: "api", + Service: "api", + Tags: []string{}, + Address: "127.0.0.10", + Port: 8000, + } + agent.state.AddService(srv5) + + srv5_mod := new(structs.NodeService) + *srv5_mod = *srv5 + srv5_mod.Address = "127.0.0.1" + args.Service = srv5_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) @@ -91,8 +109,8 @@ func TestAgentAntiEntropy_Services(t *testing.T) { t.Fatalf("err: %v", err) } - // We should have 4 services (consul included) - if len(services.NodeServices.Services) != 4 { + // We should have 5 services (consul included) + if len(services.NodeServices.Services) != 5 { t.Fatalf("bad: %v", services.NodeServices.Services) } @@ -111,6 +129,10 @@ func TestAgentAntiEntropy_Services(t *testing.T) { if !reflect.DeepEqual(serv, srv3) { t.Fatalf("bad: %v %v", serv, srv3) } + case "api": + if !reflect.DeepEqual(serv, srv5) { + t.Fatalf("bad: %v %v", serv, srv5) + } case "consul": // ignore default: @@ -119,10 +141,10 @@ func TestAgentAntiEntropy_Services(t *testing.T) { } // Check the local state - if len(agent.state.services) != 4 { + if len(agent.state.services) != 5 { t.Fatalf("bad: %v", agent.state.services) } - if len(agent.state.serviceStatus) != 4 { + if len(agent.state.serviceStatus) != 5 { t.Fatalf("bad: %v", agent.state.serviceStatus) } for name, status := range agent.state.serviceStatus { diff --git a/command/agent/structs.go b/command/agent/structs.go index e202823710..86629e543f 100644 --- a/command/agent/structs.go +++ b/command/agent/structs.go @@ -6,11 +6,12 @@ import ( // ServiceDefinition is used to JSON decode the Service definitions type ServiceDefinition struct { - ID string - Name string - Tags []string - Port int - Check CheckType + ID string + Name string + Tags []string + Address string + Port int + Check CheckType } func (s *ServiceDefinition) NodeService() *structs.NodeService { @@ -18,6 +19,7 @@ func (s *ServiceDefinition) NodeService() *structs.NodeService { ID: s.ID, Service: s.Name, Tags: s.Tags, + Address: s.Address, Port: s.Port, } if ns.ID == "" && ns.Service != "" { diff --git a/consul/catalog_endpoint_test.go b/consul/catalog_endpoint_test.go index e2674f12b9..b84c9438ac 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"}, 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) 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"}, 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) }() // 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"}, 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) // 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"}, 5000}) + s1.fsm.State().EnsureService(2, "foo", &structs.NodeService{"db", "db", []string{"primary"}, "127.0.0.1", 5000}) 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"}, 5000}) - s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, 80}) + 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}) 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 3a069ff462..280f388d18 100644 --- a/consul/fsm_test.go +++ b/consul/fsm_test.go @@ -337,10 +337,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, 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.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.EnsureCheck(7, &structs.HealthCheck{ Node: "foo", CheckID: "web", diff --git a/consul/state_store.go b/consul/state_store.go index f4124eee21..9cf88b1646 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -587,11 +587,12 @@ func (s *StateStore) ensureServiceTxn(index uint64, node string, ns *structs.Nod // Create the entry entry := structs.ServiceNode{ - Node: node, - ServiceID: ns.ID, - ServiceName: ns.Service, - ServiceTags: ns.Tags, - ServicePort: ns.Port, + Node: node, + ServiceID: ns.ID, + ServiceName: ns.Service, + ServiceTags: ns.Tags, + ServiceAddress: ns.Address, + ServicePort: ns.Port, } // Ensure the service entry is set @@ -655,6 +656,7 @@ func (s *StateStore) parseNodeServices(tables MDBTables, tx *MDBTxn, name string ID: service.ServiceID, Service: service.ServiceName, Tags: service.ServiceTags, + Address: service.ServiceAddress, Port: service.ServicePort, } ns.Services[srv.ID] = srv @@ -1039,6 +1041,7 @@ func (s *StateStore) parseCheckServiceNodes(tx *MDBTxn, res []interface{}, err e ID: srv.ServiceID, Service: srv.ServiceName, Tags: srv.ServiceTags, + Address: srv.ServiceAddress, Port: srv.ServicePort, } nodes[i].Checks = checks @@ -1113,6 +1116,7 @@ func (s *StateStore) parseNodeInfo(tx *MDBTxn, res []interface{}, err error) str ID: service.ServiceID, Service: service.ServiceName, Tags: service.ServiceTags, + Address: service.ServiceAddress, Port: service.ServicePort, } info.Services = append(info.Services, srv) diff --git a/consul/state_store_test.go b/consul/state_store_test.go index 463733467d..b456c19e97 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}, Check: &structs.HealthCheck{ Node: "foo", CheckID: "api", @@ -149,15 +149,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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -194,15 +194,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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -247,7 +247,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}); err != nil { t.Fatalf("err: %v", err) } @@ -295,11 +295,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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -332,7 +332,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}); err != nil { t.Fatalf("err: %v", err) } @@ -391,15 +391,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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -441,23 +441,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}); 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}); 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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -532,15 +532,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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -580,15 +580,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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -656,15 +656,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}); 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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -849,10 +849,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}); 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}); err != nil { t.Fatalf("err: %v", err) } if err := store.EnsureNode(25, structs.Node{"baz", "127.0.0.3"}); err != nil { @@ -979,7 +979,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}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1075,7 +1075,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}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1125,7 +1125,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}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1206,7 +1206,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}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1249,6 +1249,7 @@ func TestSS_Register_Deregister_Query(t *testing.T) { "statsite-box-stats", "statsite-box-stats", nil, + "", 0} if err := store.EnsureService(2, "foo", srv); err != nil { t.Fatalf("err: %v", err) @@ -1258,6 +1259,7 @@ func TestSS_Register_Deregister_Query(t *testing.T) { "statsite-share-stats", "statsite-share-stats", nil, + "", 0} if err := store.EnsureService(3, "foo", srv); err != nil { t.Fatalf("err: %v", err) @@ -1286,7 +1288,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}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ @@ -1345,13 +1347,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}); 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}); err != nil { t.Fatalf("err: %v", err) } @@ -2459,7 +2461,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}); err != nil { t.Fatalf("err: %v", err) } check := &structs.HealthCheck{ diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 4b9263c54b..ffa44cc50d 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -217,12 +217,13 @@ type Services map[string][]string // ServiceNode represents a node that is part of a service type ServiceNode struct { - Node string - Address string - ServiceID string - ServiceName string - ServiceTags []string - ServicePort int + Node string + Address string + ServiceID string + ServiceName string + ServiceTags []string + ServiceAddress string + ServicePort int } type ServiceNodes []ServiceNode @@ -231,6 +232,7 @@ type NodeService struct { ID string Service string Tags []string + Address string Port int } type NodeServices struct { diff --git a/consul/structs/structs_test.go b/consul/structs/structs_test.go index cb7808731a..4a2215cf8b 100644 --- a/consul/structs/structs_test.go +++ b/consul/structs/structs_test.go @@ -12,6 +12,7 @@ func TestEncodeDecode(t *testing.T) { Address: "baz", Service: &NodeService{ Service: "test", + Address: "127.0.0.2", }, } buf, err := Encode(RegisterRequestType, arg) diff --git a/website/source/docs/agent/http.html.markdown b/website/source/docs/agent/http.html.markdown index 008a17b8f0..e82abc6527 100644 --- a/website/source/docs/agent/http.html.markdown +++ b/website/source/docs/agent/http.html.markdown @@ -283,6 +283,7 @@ This endpoint is hit with a GET and returns a JSON body like this: "ID": "redis", "Service": "redis", "Tags": null, + "Address": "", "Port": 8000 } } @@ -502,6 +503,7 @@ body must look like: "master", "v1" ], + "Address": "127.0.0.1", "Port": 8000, "Check": { "Script": "/usr/local/bin/check_redis.py", @@ -513,8 +515,9 @@ 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. -`Tags`, `Port` and `Check` are optional. If `Check` is provided, only one of `Script` and `Interval` +`Tags`, `Address`, `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 `Address` will default to that of the agent if not provided. The created check will be named "service:\". @@ -568,6 +571,7 @@ body must look like: "master", "v1" ], + "Address": "127.0.0.1", "Port": 8000 }, "Check": { @@ -588,7 +592,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 `Tags` and `Port` can be omitted. +ID be node-unique. The `Tags`, `Address` and `Port` fields 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 @@ -732,6 +736,7 @@ It returns a JSON body like this: "ServiceID": "redis", "ServiceName": "redis", "ServiceTags": null, + "ServiceAddress": "", "ServicePort": 8000 } ] diff --git a/website/source/docs/agent/services.html.markdown b/website/source/docs/agent/services.html.markdown index ad66da571a..2a4bce7a5f 100644 --- a/website/source/docs/agent/services.html.markdown +++ b/website/source/docs/agent/services.html.markdown @@ -24,6 +24,7 @@ A service definition that is a script looks like: "service": { "name": "redis", "tags": ["master"], + "address": "127.0.0.1", "port": 8000, "check": { "script": "/usr/local/bin/check_redis.py", @@ -34,12 +35,14 @@ A service definition that is a script looks like: ``` A service definition must include a `name`, and may optionally provide -an `id`, `tags`, `port`, and `check`. The `id` is set to the `name` if not +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 ID's should be provided. 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 `address` can be used to specify a service specific IP address. By default, +the IP address of the agent is used, and this does not need to be provided. 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. @@ -76,6 +79,7 @@ Multiple services definitions can be provided at once using the `services` "tags": [ "master" ], + "address": "127.0.0.1", "port": 6000, "check": { "script": "/bin/check_redis -p 6000", @@ -90,6 +94,7 @@ Multiple services definitions can be provided at once using the `services` "delayed", "slave" ], + "address": "127.0.0.1", "port": 7000, "check": { "script": "/bin/check_redis -p 7000",