Merge branch 'MerlinDMC-feature/configurable_service_address'

This commit is contained in:
Armon Dadgar 2015-01-08 12:08:41 -08:00
commit 3b8043814f
12 changed files with 201 additions and 79 deletions

View File

@ -409,17 +409,17 @@ RPC:
} }
// Add the node record // Add the node record
records := d.formatNodeRecord(&out.NodeServices.Node, req.Question[0].Name, records := d.formatNodeRecord(&out.NodeServices.Node, out.NodeServices.Node.Address,
qType, d.config.NodeTTL) req.Question[0].Name, qType, d.config.NodeTTL)
if records != nil { if records != nil {
resp.Answer = append(resp.Answer, records...) resp.Answer = append(resp.Answer, records...)
} }
} }
// formatNodeRecord takes a Node and returns an A, AAAA, or CNAME record // 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 // Parse the IP
ip := net.ParseIP(node.Address) ip := net.ParseIP(addr)
var ipv4 net.IP var ipv4 net.IP
if ip != nil { if ip != nil {
ipv4 = ip.To4() ipv4 = ip.To4()
@ -457,7 +457,7 @@ func (d *DNSServer) formatNodeRecord(node *structs.Node, qName string, qType uin
Class: dns.ClassINET, Class: dns.ClassINET,
Ttl: uint32(ttl / time.Second), Ttl: uint32(ttl / time.Second),
}, },
Target: dns.Fqdn(node.Address), Target: dns.Fqdn(addr),
} }
records = append(records, cnRec) 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 // Avoid duplicate entries, possible if a node has
// the same service on multiple ports, etc. // the same service on multiple ports, etc.
addr := node.Node.Address addr := node.Node.Address
if node.Service.Address != "" {
addr = node.Service.Address
}
if _, ok := handled[addr]; ok { if _, ok := handled[addr]; ok {
continue continue
} }
handled[addr] = struct{}{} handled[addr] = struct{}{}
// Add the node record // Add the node record
records := d.formatNodeRecord(&node.Node, qName, qType, ttl) records := d.formatNodeRecord(&node.Node, addr, qName, qType, ttl)
if records != nil { if records != nil {
resp.Answer = append(resp.Answer, records...) resp.Answer = append(resp.Answer, records...)
} }
@ -603,7 +607,7 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes
for _, node := range nodes { for _, node := range nodes {
// Avoid duplicate entries, possible if a node has // Avoid duplicate entries, possible if a node has
// the same service the same port, etc. // 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 { if _, ok := handled[tuple]; ok {
continue continue
} }
@ -624,8 +628,14 @@ func (d *DNSServer) serviceSRVRecords(dc string, nodes structs.CheckServiceNodes
} }
resp.Answer = append(resp.Answer, srvRec) 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 // 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 { if records != nil {
resp.Extra = append(resp.Extra, records...) resp.Extra = append(resp.Extra, records...)
} }

View File

@ -430,6 +430,7 @@ func TestDNS_ReverseLookup_IPV6(t *testing.T) {
t.Fatalf("Bad: %#v", ptrRec) t.Fatalf("Bad: %#v", ptrRec)
} }
} }
func TestDNS_ServiceLookup(t *testing.T) { func TestDNS_ServiceLookup(t *testing.T) {
dir, srv := makeDNSServer(t) dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir) 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) { func TestDNS_CaseInsensitiveServiceLookup(t *testing.T) {
dir, srv := makeDNSServer(t) dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir) defer os.RemoveAll(dir)

View File

@ -77,6 +77,24 @@ func TestAgentAntiEntropy_Services(t *testing.T) {
t.Fatalf("err: %v", err) 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 // Trigger anti-entropy run and wait
agent.StartSync() agent.StartSync()
time.Sleep(200 * time.Millisecond) time.Sleep(200 * time.Millisecond)
@ -91,8 +109,8 @@ func TestAgentAntiEntropy_Services(t *testing.T) {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
// We should have 4 services (consul included) // We should have 5 services (consul included)
if len(services.NodeServices.Services) != 4 { if len(services.NodeServices.Services) != 5 {
t.Fatalf("bad: %v", services.NodeServices.Services) t.Fatalf("bad: %v", services.NodeServices.Services)
} }
@ -111,6 +129,10 @@ func TestAgentAntiEntropy_Services(t *testing.T) {
if !reflect.DeepEqual(serv, srv3) { if !reflect.DeepEqual(serv, srv3) {
t.Fatalf("bad: %v %v", 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": case "consul":
// ignore // ignore
default: default:
@ -119,10 +141,10 @@ func TestAgentAntiEntropy_Services(t *testing.T) {
} }
// Check the local state // Check the local state
if len(agent.state.services) != 4 { if len(agent.state.services) != 5 {
t.Fatalf("bad: %v", agent.state.services) 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) t.Fatalf("bad: %v", agent.state.serviceStatus)
} }
for name, status := range agent.state.serviceStatus { for name, status := range agent.state.serviceStatus {

View File

@ -6,11 +6,12 @@ import (
// ServiceDefinition is used to JSON decode the Service definitions // ServiceDefinition is used to JSON decode the Service definitions
type ServiceDefinition struct { type ServiceDefinition struct {
ID string ID string
Name string Name string
Tags []string Tags []string
Port int Address string
Check CheckType Port int
Check CheckType
} }
func (s *ServiceDefinition) NodeService() *structs.NodeService { func (s *ServiceDefinition) NodeService() *structs.NodeService {
@ -18,6 +19,7 @@ func (s *ServiceDefinition) NodeService() *structs.NodeService {
ID: s.ID, ID: s.ID,
Service: s.Name, Service: s.Name,
Tags: s.Tags, Tags: s.Tags,
Address: s.Address,
Port: s.Port, Port: s.Port,
} }
if ns.ID == "" && ns.Service != "" { if ns.ID == "" && ns.Service != "" {

View File

@ -490,7 +490,7 @@ func TestCatalogListServices(t *testing.T) {
// Just add a node // Just add a node
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) 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 { if err := client.Call("Catalog.ListServices", &args, &out); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@ -544,7 +544,7 @@ func TestCatalogListServices_Blocking(t *testing.T) {
go func() { go func() {
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) 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 // Re-run the query
@ -625,7 +625,7 @@ func TestCatalogListServices_Stale(t *testing.T) {
// Inject a fake service // Inject a fake service
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) 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! // Run the query, do not wait for leader!
if err := client.Call("Catalog.ListServices", &args, &out); err != nil { if err := client.Call("Catalog.ListServices", &args, &out); err != nil {
@ -666,7 +666,7 @@ func TestCatalogListServiceNodes(t *testing.T) {
// Just add a node // Just add a node
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) 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 { if err := client.Call("Catalog.ServiceNodes", &args, &out); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@ -709,8 +709,8 @@ func TestCatalogNodeServices(t *testing.T) {
// Just add a node // Just add a node
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) 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})
s1.fsm.State().EnsureService(3, "foo", &structs.NodeService{"web", "web", nil, 80}) 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 { if err := client.Call("Catalog.NodeServices", &args, &out); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)

View File

@ -337,10 +337,10 @@ func TestFSM_SnapshotRestore(t *testing.T) {
// Add some state // Add some state
fsm.state.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}) fsm.state.EnsureNode(1, structs.Node{"foo", "127.0.0.1"})
fsm.state.EnsureNode(2, structs.Node{"baz", "127.0.0.2"}) 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(3, "foo", &structs.NodeService{"web", "web", nil, "127.0.0.1", 80})
fsm.state.EnsureService(4, "foo", &structs.NodeService{"db", "db", []string{"primary"}, 5000}) 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, 80}) 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"}, 5000}) fsm.state.EnsureService(6, "baz", &structs.NodeService{"db", "db", []string{"secondary"}, "127.0.0.2", 5000})
fsm.state.EnsureCheck(7, &structs.HealthCheck{ fsm.state.EnsureCheck(7, &structs.HealthCheck{
Node: "foo", Node: "foo",
CheckID: "web", CheckID: "web",

View File

@ -587,11 +587,12 @@ func (s *StateStore) ensureServiceTxn(index uint64, node string, ns *structs.Nod
// Create the entry // Create the entry
entry := structs.ServiceNode{ entry := structs.ServiceNode{
Node: node, Node: node,
ServiceID: ns.ID, ServiceID: ns.ID,
ServiceName: ns.Service, ServiceName: ns.Service,
ServiceTags: ns.Tags, ServiceTags: ns.Tags,
ServicePort: ns.Port, ServiceAddress: ns.Address,
ServicePort: ns.Port,
} }
// Ensure the service entry is set // Ensure the service entry is set
@ -655,6 +656,7 @@ func (s *StateStore) parseNodeServices(tables MDBTables, tx *MDBTxn, name string
ID: service.ServiceID, ID: service.ServiceID,
Service: service.ServiceName, Service: service.ServiceName,
Tags: service.ServiceTags, Tags: service.ServiceTags,
Address: service.ServiceAddress,
Port: service.ServicePort, Port: service.ServicePort,
} }
ns.Services[srv.ID] = srv ns.Services[srv.ID] = srv
@ -1039,6 +1041,7 @@ func (s *StateStore) parseCheckServiceNodes(tx *MDBTxn, res []interface{}, err e
ID: srv.ServiceID, ID: srv.ServiceID,
Service: srv.ServiceName, Service: srv.ServiceName,
Tags: srv.ServiceTags, Tags: srv.ServiceTags,
Address: srv.ServiceAddress,
Port: srv.ServicePort, Port: srv.ServicePort,
} }
nodes[i].Checks = checks nodes[i].Checks = checks
@ -1113,6 +1116,7 @@ func (s *StateStore) parseNodeInfo(tx *MDBTxn, res []interface{}, err error) str
ID: service.ServiceID, ID: service.ServiceID,
Service: service.ServiceName, Service: service.ServiceName,
Tags: service.ServiceTags, Tags: service.ServiceTags,
Address: service.ServiceAddress,
Port: service.ServicePort, Port: service.ServicePort,
} }
info.Services = append(info.Services, srv) info.Services = append(info.Services, srv)

View File

@ -24,7 +24,7 @@ func TestEnsureRegistration(t *testing.T) {
reg := &structs.RegisterRequest{ reg := &structs.RegisterRequest{
Node: "foo", Node: "foo",
Address: "127.0.0.1", Address: "127.0.0.1",
Service: &structs.NodeService{"api", "api", nil, 5000}, Service: &structs.NodeService{"api", "api", nil, "", 5000},
Check: &structs.HealthCheck{ Check: &structs.HealthCheck{
Node: "foo", Node: "foo",
CheckID: "api", CheckID: "api",
@ -149,15 +149,15 @@ func TestEnsureService(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -194,15 +194,15 @@ func TestEnsureService_DuplicateNode(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -247,7 +247,7 @@ func TestDeleteNodeService(t *testing.T) {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
@ -295,11 +295,11 @@ func TestDeleteNodeService_One(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) t.Fatalf("err: %v", err)
} }
@ -332,7 +332,7 @@ func TestDeleteNode(t *testing.T) {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
@ -391,15 +391,15 @@ func TestGetServices(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -441,23 +441,23 @@ func TestServiceNodes(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -532,15 +532,15 @@ func TestServiceTagNodes(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -580,15 +580,15 @@ func TestServiceTagNodes_MultipleTags(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -656,15 +656,15 @@ func TestStoreSnapshot(t *testing.T) {
t.Fatalf("err: %v", err) 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) 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) 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) t.Fatalf("err: %v", err)
} }
@ -849,10 +849,10 @@ func TestStoreSnapshot(t *testing.T) {
} }
// Make some changes! // 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) 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) t.Fatalf("err: %v", err)
} }
if err := store.EnsureNode(25, structs.Node{"baz", "127.0.0.3"}); err != nil { 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 { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
check := &structs.HealthCheck{ 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 { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
check := &structs.HealthCheck{ 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 { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
check := &structs.HealthCheck{ 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 { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
check := &structs.HealthCheck{ check := &structs.HealthCheck{
@ -1249,6 +1249,7 @@ func TestSS_Register_Deregister_Query(t *testing.T) {
"statsite-box-stats", "statsite-box-stats",
"statsite-box-stats", "statsite-box-stats",
nil, nil,
"",
0} 0}
if err := store.EnsureService(2, "foo", srv); err != nil { if err := store.EnsureService(2, "foo", srv); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
@ -1258,6 +1259,7 @@ func TestSS_Register_Deregister_Query(t *testing.T) {
"statsite-share-stats", "statsite-share-stats",
"statsite-share-stats", "statsite-share-stats",
nil, nil,
"",
0} 0}
if err := store.EnsureService(3, "foo", srv); err != nil { if err := store.EnsureService(3, "foo", srv); err != nil {
t.Fatalf("err: %v", err) 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 { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
check := &structs.HealthCheck{ 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 { if err := store.EnsureNode(1, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
if err := store.EnsureNode(3, structs.Node{"baz", "127.0.0.2"}); err != nil { if err := store.EnsureNode(3, structs.Node{"baz", "127.0.0.2"}); err != nil {
t.Fatalf("err: %v", err) 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) 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 { if err := store.EnsureNode(11, structs.Node{"foo", "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err) 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) t.Fatalf("err: %v", err)
} }
check := &structs.HealthCheck{ check := &structs.HealthCheck{

View File

@ -217,12 +217,13 @@ type Services map[string][]string
// ServiceNode represents a node that is part of a service // ServiceNode represents a node that is part of a service
type ServiceNode struct { type ServiceNode struct {
Node string Node string
Address string Address string
ServiceID string ServiceID string
ServiceName string ServiceName string
ServiceTags []string ServiceTags []string
ServicePort int ServiceAddress string
ServicePort int
} }
type ServiceNodes []ServiceNode type ServiceNodes []ServiceNode
@ -231,6 +232,7 @@ type NodeService struct {
ID string ID string
Service string Service string
Tags []string Tags []string
Address string
Port int Port int
} }
type NodeServices struct { type NodeServices struct {

View File

@ -12,6 +12,7 @@ func TestEncodeDecode(t *testing.T) {
Address: "baz", Address: "baz",
Service: &NodeService{ Service: &NodeService{
Service: "test", Service: "test",
Address: "127.0.0.2",
}, },
} }
buf, err := Encode(RegisterRequestType, arg) buf, err := Encode(RegisterRequestType, arg)

View File

@ -283,6 +283,7 @@ This endpoint is hit with a GET and returns a JSON body like this:
"ID": "redis", "ID": "redis",
"Service": "redis", "Service": "redis",
"Tags": null, "Tags": null,
"Address": "",
"Port": 8000 "Port": 8000
} }
} }
@ -502,6 +503,7 @@ body must look like:
"master", "master",
"v1" "v1"
], ],
"Address": "127.0.0.1",
"Port": 8000, "Port": 8000,
"Check": { "Check": {
"Script": "/usr/local/bin/check_redis.py", "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`. 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. 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). 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:\<ServiceId\>". The created check will be named "service:\<ServiceId\>".
@ -568,6 +571,7 @@ body must look like:
"master", "master",
"v1" "v1"
], ],
"Address": "127.0.0.1",
"Port": 8000 "Port": 8000
}, },
"Check": { "Check": {
@ -588,7 +592,7 @@ the node with the catalog.
If the `Service` key is provided, then the service will also be registered. If 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` 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 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 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", "ServiceID": "redis",
"ServiceName": "redis", "ServiceName": "redis",
"ServiceTags": null, "ServiceTags": null,
"ServiceAddress": "",
"ServicePort": 8000 "ServicePort": 8000
} }
] ]

View File

@ -24,6 +24,7 @@ A service definition that is a script looks like:
"service": { "service": {
"name": "redis", "name": "redis",
"tags": ["master"], "tags": ["master"],
"address": "127.0.0.1",
"port": 8000, "port": 8000,
"check": { "check": {
"script": "/usr/local/bin/check_redis.py", "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 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 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. 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 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. 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 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 simpler to configure. This way the address and port of a service can
be discovered. be discovered.
@ -76,6 +79,7 @@ Multiple services definitions can be provided at once using the `services`
"tags": [ "tags": [
"master" "master"
], ],
"address": "127.0.0.1",
"port": 6000, "port": 6000,
"check": { "check": {
"script": "/bin/check_redis -p 6000", "script": "/bin/check_redis -p 6000",
@ -90,6 +94,7 @@ Multiple services definitions can be provided at once using the `services`
"delayed", "delayed",
"slave" "slave"
], ],
"address": "127.0.0.1",
"port": 7000, "port": 7000,
"check": { "check": {
"script": "/bin/check_redis -p 7000", "script": "/bin/check_redis -p 7000",