From e5798c74d2d473ff8965556ab74c612619de3dc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Thu, 17 Jul 2014 08:37:25 +0200 Subject: [PATCH 01/12] Add helper for lowercase list of strings --- consul/util.go | 9 +++++++++ consul/util_test.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/consul/util.go b/consul/util.go index 00815ea10c..96ee5c3276 100644 --- a/consul/util.go +++ b/consul/util.go @@ -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 { diff --git a/consul/util_test.go b/consul/util_test.go index 107146b521..91b7fd2f53 100644 --- a/consul/util_test.go +++ b/consul/util_test.go @@ -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") From ee4de117417e54bc83e1857f6f991cbe21efe7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Thu, 17 Jul 2014 08:38:01 +0200 Subject: [PATCH 02/12] Add case-insensitive flag to `MDBIndex` --- consul/mdb_table.go | 13 +++++++------ consul/state_store.go | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/consul/mdb_table.go b/consul/mdb_table.go index c4c84b0dc9..53c85d7cf3 100644 --- a/consul/mdb_table.go +++ b/consul/mdb_table.go @@ -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 diff --git a/consul/state_store.go b/consul/state_store.go index a2f139af1c..39d778850c 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -179,6 +179,7 @@ func (s *StateStore) initialize() error { "id": &MDBIndex{ Unique: true, Fields: []string{"Node"}, + CaseInsensitive: true, }, }, Decoder: func(buf []byte) interface{} { From e0ba9a48a7dc13602f05a8be6e9c42ed6238ba49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Thu, 17 Jul 2014 08:38:24 +0200 Subject: [PATCH 03/12] Test DNS case-insensitivity --- command/agent/dns_test.go | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index cf1ae791f7..7d664d74a0 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -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) From e4de2210784a5ba5952507a2d3e10b3fc52ba671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 10:28:54 +0200 Subject: [PATCH 04/12] Always lowercase incoming DNS query --- command/agent/dns.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/agent/dns.go b/command/agent/dns.go index 522376e150..b83243720a 100644 --- a/command/agent/dns.go +++ b/command/agent/dns.go @@ -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 From 0d71b97f8ef1ed1aecf547a9550cf9c69db26f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 10:29:28 +0200 Subject: [PATCH 05/12] Remove DC case-insensitive check from node test --- command/agent/dns_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/agent/dns_test.go b/command/agent/dns_test.go index 7d664d74a0..7d42266143 100644 --- a/command/agent/dns_test.go +++ b/command/agent/dns_test.go @@ -156,7 +156,7 @@ func TestDNS_CaseInsensitiveNodeLookup(t *testing.T) { } m := new(dns.Msg) - m.SetQuestion("fOO.node.DC1.consul.", dns.TypeANY) + m.SetQuestion("fOO.node.dc1.consul.", dns.TypeANY) c := new(dns.Client) addr, _ := srv.agent.config.ClientListener(srv.agent.config.Ports.DNS) From 9359f899f5eea7029f9e9003202e4d1d7d1b6810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 10:30:12 +0200 Subject: [PATCH 06/12] Lowercase index key and lookup value if flag is set --- consul/mdb_table.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/consul/mdb_table.go b/consul/mdb_table.go index 53c85d7cf3..592bce849a 100644 --- a/consul/mdb_table.go +++ b/consul/mdb_table.go @@ -427,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 @@ -614,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...) From 9ad8b9ff19b5f85fe92f611059171b888c116b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 10:33:27 +0200 Subject: [PATCH 07/12] Make service index case-insensitive --- consul/state_store.go | 1 + 1 file changed, 1 insertion(+) diff --git a/consul/state_store.go b/consul/state_store.go index 39d778850c..ed823eb2e1 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -201,6 +201,7 @@ func (s *StateStore) initialize() error { "service": &MDBIndex{ AllowBlank: true, Fields: []string{"ServiceName"}, + CaseInsensitive: true, }, }, Decoder: func(buf []byte) interface{} { From 37426f7410fabc476449126460231f3e91ecdf7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 10:33:47 +0200 Subject: [PATCH 08/12] Make service tag filter case-insensitive --- consul/state_store.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/consul/state_store.go b/consul/state_store.go index ed823eb2e1..043a871032 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -642,7 +642,8 @@ 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) { + srv.ServiceTags = ToLowerList(srv.ServiceTags) + if !strContains(srv.ServiceTags, strings.ToLower(tag)) { l[i], l[n-1] = l[n-1], nil i-- n-- From 57d62eb492e0fbb3bc84c1803ea8d98239655e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 10:34:03 +0200 Subject: [PATCH 09/12] Change order of fixtures --- consul/catalog_endpoint_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/consul/catalog_endpoint_test.go b/consul/catalog_endpoint_test.go index f9721ca098..06066b7dbe 100644 --- a/consul/catalog_endpoint_test.go +++ b/consul/catalog_endpoint_test.go @@ -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) } } From 945e19e139c72230d51e571b978b3ac9200bf193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 15:11:45 +0200 Subject: [PATCH 10/12] Don't override `ServiceTags` --- consul/state_store.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/consul/state_store.go b/consul/state_store.go index 043a871032..12e1c6c453 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -642,8 +642,7 @@ func serviceTagFilter(l []interface{}, tag string) []interface{} { n := len(l) for i := 0; i < n; i++ { srv := l[i].(*structs.ServiceNode) - srv.ServiceTags = ToLowerList(srv.ServiceTags) - if !strContains(srv.ServiceTags, strings.ToLower(tag)) { + if !strContains(ToLowerList(srv.ServiceTags), strings.ToLower(tag)) { l[i], l[n-1] = l[n-1], nil i-- n-- From dd9c59b6c44dc3baeecff20e771206704e7ba618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 23:39:13 +0200 Subject: [PATCH 11/12] Short doc note about DNS case-insensitivity --- website/source/docs/agent/dns.html.markdown | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/source/docs/agent/dns.html.markdown b/website/source/docs/agent/dns.html.markdown index 46303a60d6..938c989a6b 100644 --- a/website/source/docs/agent/dns.html.markdown +++ b/website/source/docs/agent/dns.html.markdown @@ -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 From 674152a3da9aedf45b448e2c680ec285e81a0516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?William=20Tisa=CC=88ter?= Date: Wed, 23 Jul 2014 23:45:03 +0200 Subject: [PATCH 12/12] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2954ce23b..97f87722ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.3.2 (Unreleased) + +IMPROVEMENTS: + + * DNS case-insensitivity [GH-189] + ## 0.3.1 (July 21, 2014) FEATURES: