Merge pull request #224 from hashicorp/f-case-insensitivity

DNS case-insensitivity
This commit is contained in:
William Tisäter 2014-07-23 23:53:11 +02:00
commit da0458e405
9 changed files with 120 additions and 12 deletions

View File

@ -1,3 +1,9 @@
## 0.3.2 (Unreleased)
IMPROVEMENTS:
* DNS case-insensitivity [GH-189]
## 0.3.1 (July 21, 2014)
FEATURES:

View File

@ -248,7 +248,7 @@ func (d *DNSServer) dispatch(network string, req, resp *dns.Msg) {
datacenter := d.agent.config.Datacenter
// Get the QName without the domain suffix
qName := dns.Fqdn(req.Question[0].Name)
qName := strings.ToLower(dns.Fqdn(req.Question[0].Name))
qName = strings.TrimSuffix(qName, d.domain)
// Split into the label parts

View File

@ -136,6 +136,40 @@ func TestDNS_NodeLookup(t *testing.T) {
}
}
func TestDNS_CaseInsensitiveNodeLookup(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",
}
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("fOO.node.dc1.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)
}
if len(in.Answer) != 1 {
t.Fatalf("empty lookup: %#v", in)
}
}
func TestDNS_NodeLookup_PeriodName(t *testing.T) {
dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir)
@ -336,6 +370,45 @@ func TestDNS_ServiceLookup(t *testing.T) {
}
}
func TestDNS_CaseInsensitiveServiceLookup(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)
}
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)
}
if len(in.Answer) != 1 {
t.Fatalf("empty lookup: %#v", in)
}
}
func TestDNS_ServiceLookup_TagPeriod(t *testing.T) {
dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir)

View File

@ -220,13 +220,13 @@ func TestCatalogListNodes(t *testing.T) {
})
// Server node is auto added from Serf
if out.Nodes[0].Node != s1.config.NodeName {
if out.Nodes[1].Node != s1.config.NodeName {
t.Fatalf("bad: %v", out)
}
if out.Nodes[1].Node != "foo" {
if out.Nodes[0].Node != "foo" {
t.Fatalf("bad: %v", out)
}
if out.Nodes[1].Address != "127.0.0.1" {
if out.Nodes[0].Address != "127.0.0.1" {
t.Fatalf("bad: %v", out)
}
}

View File

@ -45,12 +45,13 @@ type MDBTables []*MDBTable
// An Index is named, and uses a series of column values to
// map to the row-id containing the table
type MDBIndex struct {
AllowBlank bool // Can fields be blank
Unique bool // Controls if values are unique
Fields []string // Fields are used to build the index
IdxFunc IndexFunc // Can be used to provide custom indexing
Virtual bool // Virtual index does not exist, but can be used for queries
RealIndex string // Virtual indexes use a RealIndex for iteration
AllowBlank bool // Can fields be blank
Unique bool // Controls if values are unique
Fields []string // Fields are used to build the index
IdxFunc IndexFunc // Can be used to provide custom indexing
Virtual bool // Virtual index does not exist, but can be used for queries
RealIndex string // Virtual indexes use a RealIndex for iteration
CaseInsensitive bool // Controls if values are case-insensitive
table *MDBTable
name string
@ -426,6 +427,10 @@ func (t *MDBTable) getIndex(index string, parts []string) (*MDBIndex, []byte, er
return nil, nil, tooManyFields
}
if idx.CaseInsensitive {
parts = ToLowerList(parts)
}
// Construct the key
key := idx.keyFromParts(parts...)
return idx, key, nil
@ -613,6 +618,9 @@ func (i *MDBIndex) keyFromObject(obj interface{}) ([]byte, error) {
if !i.AllowBlank && val == "" {
return nil, fmt.Errorf("Field '%s' must be set: %#v", field, obj)
}
if i.CaseInsensitive {
val = strings.ToLower(val)
}
parts = append(parts, val)
}
key := i.keyFromParts(parts...)

View File

@ -179,6 +179,7 @@ func (s *StateStore) initialize() error {
"id": &MDBIndex{
Unique: true,
Fields: []string{"Node"},
CaseInsensitive: true,
},
},
Decoder: func(buf []byte) interface{} {
@ -200,6 +201,7 @@ func (s *StateStore) initialize() error {
"service": &MDBIndex{
AllowBlank: true,
Fields: []string{"ServiceName"},
CaseInsensitive: true,
},
},
Decoder: func(buf []byte) interface{} {
@ -640,7 +642,7 @@ func serviceTagFilter(l []interface{}, tag string) []interface{} {
n := len(l)
for i := 0; i < n; i++ {
srv := l[i].(*structs.ServiceNode)
if !strContains(srv.ServiceTags, tag) {
if !strContains(ToLowerList(srv.ServiceTags), strings.ToLower(tag)) {
l[i], l[n-1] = l[n-1], nil
i--
n--

View File

@ -9,6 +9,7 @@ import (
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/hashicorp/serf/serf"
)
@ -68,6 +69,14 @@ func strContains(l []string, s string) bool {
return false
}
func ToLowerList(l []string) []string {
var out []string
for _, value := range l {
out = append(out, strings.ToLower(value))
}
return out
}
// ensurePath is used to make sure a path exists
func ensurePath(path string, dir bool) error {
if !dir {

View File

@ -18,6 +18,15 @@ func TestStrContains(t *testing.T) {
}
}
func TestToLowerList(t *testing.T) {
l := []string{"ABC", "Abc", "abc"}
for _, value := range ToLowerList(l) {
if value != "abc" {
t.Fatalf("failed lowercasing")
}
}
}
func TestIsPrivateIP(t *testing.T) {
if !isPrivateIP("192.168.1.1") {
t.Fatalf("bad")

View File

@ -20,7 +20,8 @@ with no failing health checks. It's that simple!
There are a number of [configuration options](/docs/agent/options.html) that
are important for the DNS interface. They are `client_addr`, `ports.dns`, `recursor`,
`domain`, and `dns_config`. By default Consul will listen on 127.0.0.1:8600 for DNS queries
in the "consul." domain, without support for DNS recursion.
in the "consul." domain, without support for DNS recursion. All queries are case-insensitive, a
name lookup for `PostgreSQL.node.dc1.consul` will find all nodes named `postgresql`, no matter of case.
There are a few ways to use the DNS interface. One option is to use a custom
DNS resolver library and point it at Consul. Another option is to set Consul