From 81b43135f9655bf118b949c467440a2b96309a71 Mon Sep 17 00:00:00 2001 From: James Phillips Date: Fri, 13 Nov 2015 03:39:07 -0800 Subject: [PATCH] Adds unit tests for prepared queries and DNS, using existing tests for equivalence. --- command/agent/dns.go | 26 +- command/agent/dns_test.go | 2082 +++++++++++++++--------- consul/prepared_query_endpoint_test.go | 2 +- 3 files changed, 1350 insertions(+), 760 deletions(-) diff --git a/command/agent/dns.go b/command/agent/dns.go index 4f7ef33f2d..a5816b3d65 100644 --- a/command/agent/dns.go +++ b/command/agent/dns.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/hashicorp/consul/consul" "github.com/hashicorp/consul/consul/structs" "github.com/miekg/dns" ) @@ -555,17 +556,25 @@ func (d *DNSServer) preparedQueryLookup(network, datacenter, query string, req, }, } - // If the network is not TCP then we just get enough responses to - // tell that things got truncated. This saves bandwidth since we - // will trim the list anyway. - if network != "tcp" { - args.Limit = maxServiceResponses + 1 - } + // TODO (slackpad) - What's a safe limit we can set here? It seems like + // with dup filtering done at this level we need to get everything to + // match the previous behavior. We can optimize by pushing more filtering + // into the query execution, but for now I think we need to get the full + // response. endpoint := d.agent.getEndpoint(preparedQueryEndpoint) var out structs.PreparedQueryExecuteResponse RPC: if err := d.agent.RPC(endpoint+".Execute", &args, &out); err != nil { + // If they give a bogus query name, treat that as a name error, + // not a full on server error. We have to use a string compare + // here since the RPC layer loses the type information. + if err.Error() == consul.ErrQueryNotFound.Error() { + d.addSOA(d.domain, resp) + resp.SetRcode(req, dns.RcodeNameError) + return + } + d.logger.Printf("[ERR] dns: rpc error: %v", err) resp.SetRcode(req, dns.RcodeServerFailure) return @@ -578,6 +587,11 @@ RPC: goto RPC } + // TODO (slackpad) Do we want to apply the DNS server's per-service TTL + // configs if the query's TTL is not set? That seems like it adds a lot + // of complexity (we'd have to plumb the service name back with the query + // results to do this), but is it what people would expect? + // Determine the TTL. The parse should never fail since we vet it when // the query is created, but we check anyway. var ttl time.Duration diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index f71703ebc1..41a2356a85 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -506,86 +506,118 @@ func TestDNS_ServiceLookup(t *testing.T) { 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"}, - Port: 12345, - }, + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, 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) + 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.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) + // Lookup a non-existing service/query, we should receive an SOA. + questions = []string{ + "nodb.service.consul.", + "nope.query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - 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]) - } + 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) + } - 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.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v", in) + } - // lookup a non-existing service, we should receive a SOA - m = new(dns.Msg) - m.SetQuestion("nodb.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.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } - - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } } } @@ -596,64 +628,90 @@ func TestDNS_ServiceLookup_ServiceAddress(t *testing.T) { 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, - }, + // Register a node with a service. + { + 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) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, 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) - } + 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) - } + 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]) - } + 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]) + 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]) + } } } @@ -664,35 +722,69 @@ func TestDNS_CaseInsensitiveServiceLookup(t *testing.T) { 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"}, - Port: 12345, - }, + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "Db", + Tags: []string{"Master"}, + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query, as well as a name. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "somequery", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - m := new(dns.Msg) - m.SetQuestion("mASTER.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) + // Try some variations to make sure case doesn't matter. + questions := []string{ + "master.db.service.consul.", + "mASTER.dB.service.consul.", + "MASTER.dB.service.consul.", + "db.service.consul.", + "DB.service.consul.", + "Db.service.consul.", + "somequery.query.consul.", + "SomeQuery.query.consul.", + "SOMEQUERY.query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - if len(in.Answer) != 1 { - t.Fatalf("empty lookup: %#v", in) + 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("empty lookup: %#v", in) + } } } @@ -757,62 +849,52 @@ func TestDNS_ServiceLookup_TagPeriod(t *testing.T) { } } -func TestDNS_ServiceLookup_Dedup(t *testing.T) { +func TestDNS_ServiceLookup_PreparedQueryNamePeriod(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"}, - Port: 12345, - }, + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + // Register a prepared query with a period in the name. + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "some.query.we.like", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db2", - Service: "db", - Tags: []string{"slave"}, - Port: 12345, - }, - } - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db3", - Service: "db", - Tags: []string{"slave"}, - Port: 12346, - }, - } - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + var id string + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) + m.SetQuestion("some.query.we.like.query.consul.", dns.TypeSRV) c := new(dns.Client) addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) @@ -825,108 +907,17 @@ func TestDNS_ServiceLookup_Dedup(t *testing.T) { t.Fatalf("Bad: %#v", in) } - aRec, ok := in.Answer[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } -} - -func TestDNS_ServiceLookup_Dedup_SRV(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"}, - Port: 12345, - }, - } - - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db2", - Service: "db", - Tags: []string{"slave"}, - Port: 12345, - }, - } - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db3", - Service: "db", - Tags: []string{"slave"}, - Port: 12346, - }, - } - 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) != 2 { - 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 && srvRec.Port != 12346 { + if srvRec.Port != 12345 { t.Fatalf("Bad: %#v", srvRec) } if srvRec.Target != "foo.node.dc1.consul." { t.Fatalf("Bad: %#v", srvRec) } - srvRec, ok = in.Answer[1].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[1]) - } - if srvRec.Port != 12346 && srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Port == in.Answer[0].(*dns.SRV).Port { - t.Fatalf("should be a different port") - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - aRec, ok := in.Extra[0].(*dns.A) if !ok { t.Fatalf("Bad: %#v", in.Extra[0]) @@ -939,6 +930,242 @@ func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) { } } +func TestDNS_ServiceLookup_Dedup(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register a single node with multiple instances of a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db2", + Service: "db", + Tags: []string{"slave"}, + Port: 12345, + }, + } + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db3", + Service: "db", + Tags: []string{"slave"}, + Port: 12346, + }, + } + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query, make sure only + // one IP is returned. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + + 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) + } + + aRec, ok := in.Answer[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + } +} + +func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register a single node with multiple instances of a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db2", + Service: "db", + Tags: []string{"slave"}, + Port: 12345, + }, + } + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db3", + Service: "db", + Tags: []string{"slave"}, + Port: 12346, + }, + } + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query, make sure only + // one IP is returned and two unique ports are returned. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, 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) != 2 { + 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 && srvRec.Port != 12346 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + + srvRec, ok = in.Answer[1].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[1]) + } + if srvRec.Port != 12346 && srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Port == in.Answer[0].(*dns.SRV).Port { + t.Fatalf("should be a different port") + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + + 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.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + } +} + func TestDNS_Recurse(t *testing.T) { recursor := makeRecursor(t, []dns.RR{dnsA("apple.com", "1.2.3.4")}) defer recursor.Shutdown() @@ -974,127 +1201,153 @@ func TestDNS_ServiceLookup_FilterCritical(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register nodes - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: structs.HealthCritical, - }, + // Register nodes with health checks in various states. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: structs.HealthCritical, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args2 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: structs.HealthCritical, + }, + } + if err := srv.agent.RPC("Catalog.Register", args2, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args3 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthCritical, + }, + } + if err := srv.agent.RPC("Catalog.Register", args3, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args4 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "baz", + Address: "127.0.0.3", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + if err := srv.agent.RPC("Catalog.Register", args4, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args5 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "quux", + Address: "127.0.0.4", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthWarning, + }, + } + if err := srv.agent.RPC("Catalog.Register", args5, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - args2 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: structs.HealthCritical, - }, - } - if err := srv.agent.RPC("Catalog.Register", args2, &out); err != nil { - t.Fatalf("err: %v", err) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - args3 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthCritical, - }, - } - if err := srv.agent.RPC("Catalog.Register", args3, &out); err != nil { - t.Fatalf("err: %v", err) - } + 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) + } - args4 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "baz", - Address: "127.0.0.3", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - } - if err := srv.agent.RPC("Catalog.Register", args4, &out); err != nil { - t.Fatalf("err: %v", err) - } + // Only 4 and 5 are not failing, so we should get 2 answers + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } - args5 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "quux", - Address: "127.0.0.4", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthWarning, - }, - } - if err := srv.agent.RPC("Catalog.Register", args5, &out); err != nil { - t.Fatalf("err: %v", err) - } + ips := make(map[string]bool) + for _, resp := range in.Answer { + aRec := resp.(*dns.A) + ips[aRec.A.String()] = true + } - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) - - 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) - } - - // Only 4 and 5 are not failing, so we should get 2 answers - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) - } - - ips := make(map[string]bool) - for _, resp := range in.Answer { - aRec := resp.(*dns.A) - ips[aRec.A.String()] = true - } - - if !ips["127.0.0.3"] { - t.Fatalf("Bad: %#v should contain 127.0.0.3 (state healthy)", in) - } - if !ips["127.0.0.4"] { - t.Fatalf("Bad: %#v should contain 127.0.0.4 (state warning)", in) + if !ips["127.0.0.3"] { + t.Fatalf("Bad: %#v should contain 127.0.0.3 (state healthy)", in) + } + if !ips["127.0.0.4"] { + t.Fatalf("Bad: %#v should contain 127.0.0.4 (state warning)", in) + } } } @@ -1105,84 +1358,110 @@ func TestDNS_ServiceLookup_OnlyFailing(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register nodes - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: structs.HealthCritical, - }, + // Register nodes with all health checks in a critical state. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: structs.HealthCritical, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args2 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: structs.HealthCritical, + }, + } + if err := srv.agent.RPC("Catalog.Register", args2, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args3 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthCritical, + }, + } + if err := srv.agent.RPC("Catalog.Register", args3, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - args2 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: structs.HealthCritical, - }, - } - if err := srv.agent.RPC("Catalog.Register", args2, &out); err != nil { - t.Fatalf("err: %v", err) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - args3 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthCritical, - }, - } - if err := srv.agent.RPC("Catalog.Register", args3, &out); err != nil { - t.Fatalf("err: %v", err) - } + 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) + } - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) + // All 3 are failing, so we should get 0 answers and an NXDOMAIN response + if len(in.Answer) != 0 { + t.Fatalf("Bad: %#v", in) + } - 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) - } - - // All 3 are failing, so we should get 0 answers and an NXDOMAIN response - if len(in.Answer) != 0 { - t.Fatalf("Bad: %#v", in) - } - - if in.Rcode != dns.RcodeNameError { - t.Fatalf("Bad: %#v", in) + if in.Rcode != dns.RcodeNameError { + t.Fatalf("Bad: %#v", in) + } } } @@ -1195,112 +1474,139 @@ func TestDNS_ServiceLookup_OnlyPassing(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register nodes - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthPassing, - }, + // Register nodes with health checks in various states. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthPassing, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args2 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthWarning, + }, + } + + if err := srv.agent.RPC("Catalog.Register", args2, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args3 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "baz", + Address: "127.0.0.3", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthCritical, + }, + } + + if err := srv.agent.RPC("Catalog.Register", args3, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args4 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "quux", + Address: "127.0.0.4", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: structs.HealthUnknown, + }, + } + + if err := srv.agent.RPC("Catalog.Register", args4, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "db", + OnlyPassing: true, + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - args2 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthWarning, - }, + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - if err := srv.agent.RPC("Catalog.Register", args2, &out); err != nil { - t.Fatalf("err: %v", err) - } + 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) + } - args3 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "baz", - Address: "127.0.0.3", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthCritical, - }, - } + // Only 1 is passing, so we should only get 1 answer + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - if err := srv.agent.RPC("Catalog.Register", args3, &out); err != nil { - t.Fatalf("err: %v", err) - } + resp := in.Answer[0] + aRec := resp.(*dns.A) - args4 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "quux", - Address: "127.0.0.4", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"master"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: structs.HealthUnknown, - }, - } - - if err := srv.agent.RPC("Catalog.Register", args4, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) - - 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) - } - - // Only 1 is passing, so we should only get 1 answer - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - resp := in.Answer[0] - aRec := resp.(*dns.A) - - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } } } @@ -1311,7 +1617,7 @@ func TestDNS_ServiceLookup_Randomize(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register nodes + // Register a large set of nodes. for i := 0; i < 3*maxServiceResponses; i++ { args := &structs.RegisterRequest{ Datacenter: "dc1", @@ -1329,46 +1635,70 @@ func TestDNS_ServiceLookup_Randomize(t *testing.T) { } } - // Ensure the response is randomized each time. - uniques := map[string]struct{}{} - addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) - for i := 0; i < 10; i++ { - m := new(dns.Msg) - m.SetQuestion("web.service.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, addr.String()) - if err != nil { + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "web", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { t.Fatalf("err: %v", err) } - - // Response length should be truncated - // We should get an A record for each response - if len(in.Answer) != maxServiceResponses { - t.Fatalf("Bad: %#v", len(in.Answer)) - } - - // Collect all the names - var names []string - for _, rec := range in.Answer { - switch v := rec.(type) { - case *dns.SRV: - names = append(names, v.Target) - case *dns.A: - names = append(names, v.A.String()) - } - } - nameS := strings.Join(names, "|") - - // Tally the results - uniques[nameS] = struct{}{} } - // Give some wiggle room. Since the responses are randomized and there - // is a finite number of combinations, requiring 0 duplicates every - // test run eventually gives us failures. - if len(uniques) < 2 { - t.Fatalf("unique response ratio too low: %d/10\n%v", len(uniques), uniques) + // Look up the service directly and via prepared query. Ensure the + // response is randomized each time. + questions := []string{ + "web.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + uniques := map[string]struct{}{} + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + for i := 0; i < 10; i++ { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Response length should be truncated and we should get + // an A record for each response. + if len(in.Answer) != maxServiceResponses { + t.Fatalf("Bad: %#v", len(in.Answer)) + } + + // Collect all the names. + var names []string + for _, rec := range in.Answer { + switch v := rec.(type) { + case *dns.SRV: + names = append(names, v.Target) + case *dns.A: + names = append(names, v.A.String()) + } + } + nameS := strings.Join(names, "|") + + // Tally the results. + uniques[nameS] = struct{}{} + } + + // Give some wiggle room. Since the responses are randomized and + // there is a finite number of combinations, requiring 0 + // duplicates every test run eventually gives us failures. + if len(uniques) < 2 { + t.Fatalf("unique response ratio too low: %d/10\n%v", len(uniques), uniques) + } } } @@ -1381,7 +1711,7 @@ func TestDNS_ServiceLookup_Truncate(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register nodes + // Register nodes a large number of nodes. for i := 0; i < 3*maxServiceResponses; i++ { args := &structs.RegisterRequest{ Datacenter: "dc1", @@ -1399,20 +1729,44 @@ func TestDNS_ServiceLookup_Truncate(t *testing.T) { } } - // Ensure the response is randomized each time. - m := new(dns.Msg) - m.SetQuestion("web.service.consul.", dns.TypeANY) - - addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, addr.String()) - if err != nil && err != dns.ErrTruncated { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "web", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - // Check for the truncate bit - if !in.Truncated { - t.Fatalf("should have truncate bit") + // Look up the service directly and via prepared query. Ensure the + // response is truncated each time. + questions := []string{ + "web.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + c := new(dns.Client) + in, _, err := c.Exchange(m, addr.String()) + if err != nil && err != dns.ErrTruncated { + t.Fatalf("err: %v", err) + } + + // Check for the truncate bit + if !in.Truncated { + t.Fatalf("should have truncate bit") + } } } @@ -1423,7 +1777,7 @@ func TestDNS_ServiceLookup_MaxResponses(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register nodes + // Register a large number of nodes. for i := 0; i < 6*maxServiceResponses; i++ { nodeAddress := fmt.Sprintf("127.0.0.%d", i+1) if i > 3 { @@ -1445,41 +1799,63 @@ func TestDNS_ServiceLookup_MaxResponses(t *testing.T) { } } - // Ensure the response is randomized each time. - m := new(dns.Msg) - m.SetQuestion("web.service.consul.", dns.TypeANY) - - addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, addr.String()) - if err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "web", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - if len(in.Answer) != 3 { - t.Fatalf("should receive 3 answers for ANY") + // Look up the service directly and via prepared query. + questions := []string{ + "web.service.consul.", + id + ".query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - m.SetQuestion("web.service.consul.", dns.TypeA) - in, _, err = c.Exchange(m, addr.String()) - if err != nil { - t.Fatalf("err: %v", err) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + c := new(dns.Client) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 3 { + t.Fatalf("should receive 3 answers for ANY") + } + + m.SetQuestion(question, dns.TypeA) + in, _, err = c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 3 { + t.Fatalf("should receive 3 answers for A") + } + + m.SetQuestion(question, dns.TypeAAAA) + in, _, err = c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 3 { + t.Fatalf("should receive 3 answers for AAAA") + } } - - if len(in.Answer) != 3 { - t.Fatalf("should receive 3 answers for A") - } - - m.SetQuestion("web.service.consul.", dns.TypeAAAA) - in, _, err = c.Exchange(m, addr.String()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 3 { - t.Fatalf("should receive 3 answers for AAAA") - } - } func TestDNS_ServiceLookup_CNAME(t *testing.T) { @@ -1497,58 +1873,84 @@ func TestDNS_ServiceLookup_CNAME(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "www.google.com", - Service: &structs.NodeService{ - Service: "search", - Port: 80, - }, + // Register a node with a name for an address. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "www.google.com", + Service: &structs.NodeService{ + Service: "search", + Port: 80, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Service: structs.ServiceQuery{ + Service: "search", + }, + }, + } + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - m := new(dns.Msg) - m.SetQuestion("search.service.consul.", dns.TypeANY) + // Look up the service directly and via prepared query. + questions := []string{ + "search.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - 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) - } + 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) + } - // Service CNAME, google CNAME, google A record - if len(in.Answer) != 3 { - t.Fatalf("Bad: %#v", in) - } + // Service CNAME, google CNAME, google A record + if len(in.Answer) != 3 { + t.Fatalf("Bad: %#v", in) + } - // Should have service CNAME - cnRec, ok := in.Answer[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if cnRec.Target != "www.google.com." { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + // Should have service CNAME + cnRec, ok := in.Answer[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if cnRec.Target != "www.google.com." { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - // Should have google CNAME - cnRec, ok = in.Answer[1].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[1]) - } - if cnRec.Target != "google.com." { - t.Fatalf("Bad: %#v", in.Answer[1]) - } + // Should have google CNAME + cnRec, ok = in.Answer[1].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[1]) + } + if cnRec.Target != "google.com." { + t.Fatalf("Bad: %#v", in.Answer[1]) + } - // Check we recursively resolve - if _, ok := in.Answer[2].(*dns.A); !ok { - t.Fatalf("Bad: %#v", in.Answer[2]) + // Check we recursively resolve + if _, ok := in.Answer[2].(*dns.A); !ok { + t.Fatalf("Bad: %#v", in.Answer[2]) + } } } @@ -1781,6 +2183,129 @@ func TestDNS_ServiceLookup_TTL(t *testing.T) { } } +func TestDNS_PreparedQuery_TTL(t *testing.T) { + dir, srv := makeDNSServer(t) + defer os.RemoveAll(dir) + defer srv.agent.Shutdown() + + testutil.WaitForLeader(t, srv.agent.RPC, "dc1") + + // Register a node and a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"master"}, + Port: 12345, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register prepared queries with and without a TTL set. + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "ttl", + Service: structs.ServiceQuery{ + Service: "db", + }, + DNS: structs.QueryDNSOptions{ + TTL: "10s", + }, + }, + } + + var id string + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "nottl", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Make sure the TTL is set when requested. + m := new(dns.Msg) + m.SetQuestion("ttl.query.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.Hdr.Ttl != 10 { + 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.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + + // And the TTL should default to 0 otherwise. + m = new(dns.Msg) + m.SetQuestion("nottl.query.consul.", dns.TypeSRV) + 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.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.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } +} + func TestDNS_ServiceLookup_SRV_RFC(t *testing.T) { dir, srv := makeDNSServer(t) defer os.RemoveAll(dir) @@ -2008,86 +2533,137 @@ func TestDNS_NonExistingLookupEmptyAorAAAA(t *testing.T) { testutil.WaitForLeader(t, srv.agent.RPC, "dc1") - // register v6 only service - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foov6", - Address: "fe80::1", - Service: &structs.NodeService{ - Service: "webv6", - Port: 8000, - }, + // Register a v6-only service and a v4-only service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foov6", + Address: "fe80::1", + Service: &structs.NodeService{ + Service: "webv6", + Port: 8000, + }, + } + + var out struct{} + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foov4", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "webv4", + Port: 8000, + }, + } + + if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } - var out struct{} - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Register equivalent prepared queries. + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "webv4", + Service: structs.ServiceQuery{ + Service: "webv4", + }, + }, + } + + var id string + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "webv6", + Service: structs.ServiceQuery{ + Service: "webv6", + }, + }, + } + + if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } } - // register v4 only service - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foov4", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "webv4", - Port: 8000, - }, + // Check for ipv6 records on ipv4-only service directly and via the + // prepared query. + questions := []string{ + "webv4.service.consul.", + "webv4.query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeAAAA) + + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + c := new(dns.Client) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v", in) + } + + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) + } } - if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) + // Check for ipv4 records on ipv6-only service directly and via the + // prepared query. + questions = []string{ + "webv6.service.consul.", + "webv6.query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - // check for ipv6 records on ipv4 only service - m := new(dns.Msg) - m.SetQuestion("webv4.service.consul.", dns.TypeAAAA) + addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) + c := new(dns.Client) + in, _, err := c.Exchange(m, addr.String()) + if err != nil { + t.Fatalf("err: %v", err) + } - addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, addr.String()) - if err != nil { - t.Fatalf("err: %v", err) + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v", in) + } + + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) + } } - - if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } - - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } - - // check for ipv4 records on ipv6 only service - m.SetQuestion("webv6.service.consul.", dns.TypeA) - - in, _, err = c.Exchange(m, addr.String()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } - - soaRec, ok = in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } - } diff --git a/consul/prepared_query_endpoint_test.go b/consul/prepared_query_endpoint_test.go index b526ac8705..dfc2a5193a 100644 --- a/consul/prepared_query_endpoint_test.go +++ b/consul/prepared_query_endpoint_test.go @@ -1077,7 +1077,7 @@ func TestPreparedQuery_Execute(t *testing.T) { var reply structs.PreparedQueryExecuteResponse err := msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply) - if err == nil || !strings.Contains(err.Error(), ErrQueryNotFound.Error()) { + if err == nil || err.Error() != ErrQueryNotFound.Error() { t.Fatalf("bad: %v", err) }