Skip to content

Commit

Permalink
Merge pull request #3 from phamhongviet/feature/custom-domain-name
Browse files Browse the repository at this point in the history
Feature/custom domain name
  • Loading branch information
phamhongviet committed May 1, 2016
2 parents 3e63f63 + c2435a3 commit 09b1a89
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 13 deletions.
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ $ dig +short oregon.region.redis.service.serf
192.168.10.12
```

Or to query a server with hostname node7.examp.le:
Or to query a pre-configured domain name `us-web.serf` with name `web-.*` and tag `dc=us-.*`:

```
$ dig +short node7.examp.le.name.serf
192.168.7.17
$ dig +short us-web.serf
192.168.6.17
192.168.7.18
192.168.8.29
192.168.9.105
```
_Note_: querying hostname is not implemented yet
_Note_: please see [custom-domain-name.md](custom-domain-name.md "Custom Domain Name") for more information.


## Develop
Expand Down Expand Up @@ -120,9 +123,18 @@ SERF_AUTH='S3creTT0k3n' ./serf-dns
./serf-dns --serf-auth='S3creTT0k3n'
```

* __custom__: path to custom domain name file
Default: empty
Use environment variable `CUSTOM` or parameter `--custom=`
For example, to load custom domain names from /etc/serf-dns/custom.json:
```
CUSTOM='/etc/serf-dns/custom.json' ./serf-dns
./serf-dns --custom='/etc/serf-dns/custom.json'
```
Please see [custom-domain-name.md](custom-domain-name.md "Custom Domain Name") for more information.

## TODO

* Support hostname query
* Support configuration with file
* Clean and test functions in main.go
* Proper logging
Expand Down
15 changes: 10 additions & 5 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@ import (
)

var (
config = configure.New()
configBind = config.String("bind", ":5327", "Bind with IP address and port")
configDomainName = config.String("domain-name", "serf.", "Domain name")
configSerfRPCAddress = config.String("serf", "127.0.0.1:7373", "Serf RPC Address")
configSerfRPCAuthKey = config.String("serf-auth", "", "Serf RPC auth key")
config = configure.New()
configBind = config.String("bind", ":5327", "Bind with IP address and port")
configDomainName = config.String("domain-name", "serf.", "Domain name")
configSerfRPCAddress = config.String("serf", "127.0.0.1:7373", "Serf RPC Address")
configSerfRPCAuthKey = config.String("serf-auth", "", "Serf RPC auth key")
configCustomDomainNameFile = config.String("custom", "", "Custom domain name file path")

// SerfFilterTable is a global table of serf filters loaded at boot
SerfFilterTable serfFilterTable
)

func init() {
Expand All @@ -25,5 +29,6 @@ Options:
--domain-name Specify domain name (default: serf.)
--serf Serf RPC address (default: 127.0.0.1:7373)
--serf-auth Serf RPC authentication key (default: empty)
--custom Custom domain name file path (default: none)
`
}
21 changes: 21 additions & 0 deletions custom-domain-name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"my-custom-dn-1.serf": {
"name": "^web-[0-5][0-9]",
"status": "alive",
"tags": {
"role": "web"
}
},
"failed.web.serf": {
"name": "^web-.*",
"status": "failed",
"tags": {
"role": "web"
}
},
"us.dc.serf": {
"tags": {
"dc": "us-.*"
}
}
}
45 changes: 45 additions & 0 deletions custom-domain-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Custom Domain Name

## Problem
Using serf-dns with domain name parsed into tags has some limitation:
- cannot use together with name and status
- cannot use regex
- limited to 127 labels, or 63 tags
- limited to 253 characters in full domain name

## Solution
Use custom pre-registered domain name with pre-configured serf filters

For example: configure domain name `my-custom-dn-1.serf` with name `^web-[0-5][0-9]`, and tags `role=web`

Use a JSON file to configure custom domain name, like this:

```json
{
"my-custom-dn-1.serf": {
"name": "^web-[0-5][0-9]",
"status": "alive",
"tags": {
"role": "web"
}
},
"failed.web.serf": {
"name": "^web-.*",
"status": "failed",
"tags": {
"role": "web",
"version": "201605[0-9][0-9].*"
}
},
"us.dc.serf": {
"tags": {
"dc": "us-.*"
}
}
}
```

The JSON file above will register 3 domain names:
- my-custom-dn-1.serf: return hosts named range from web-00 to web-59, still alive and tagged role=web
- failed.web.serf: return hosts whose name start with web-, already dead and tagged role=web and version start with 201605
- us.dc.serf: return hosts in US whose tag dc start with us-. Normally, query to this domain name will return hosts with tag dc=us
20 changes: 20 additions & 0 deletions custom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"encoding/json"
)

type serfFilterTable map[string]serfFilter

func checkCustomDomainNameExistence(domainName string, sftab serfFilterTable) bool {
_, ok := sftab[domainName]
return ok
}

func loadCustomDomainName(data []byte) serfFilterTable {
var sftab serfFilterTable

// TODO: handle error here
json.Unmarshal(data, &sftab)
return sftab
}
77 changes: 77 additions & 0 deletions custom_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"io/ioutil"
"testing"
)

func TestCheckCustomDomainNameExistence(t *testing.T) {
SFTable := serfFilterTable{
"dead.digit.serf.": serfFilter{
Name: "^[0-9].*",
Status: "failed",
},
"digit.name.serf.": serfFilter{
Name: "^[0-9].*",
Status: "alive",
},
"dead.serf.": serfFilter{
Status: "failed",
},
}

for dn := range SFTable {
ok := checkCustomDomainNameExistence(dn, SFTable)
if !ok {
t.Errorf("Failed to find existing custom domain name %s", dn)
}
}

for _, dn := range []string{"not.exist.serf.", "no.good.nope.", "also.not.exists."} {
ok := checkCustomDomainNameExistence(dn, SFTable)
if ok {
t.Errorf("Failed: Custom domain name %s actually does not exist", dn)
}
}
}

func TestLoadCustomDomainName(t *testing.T) {
data, err := ioutil.ReadFile("custom-domain-name.json")
if err != nil {
t.Errorf("Error reading data from custom-domain-name.json: %s", err.Error())
}

expect := serfFilterTable{
"my-custom-dn-1.serf": serfFilter{
Name: "^web-[0-5][0-9]",
Status: "alive",
Tags: map[string]string{
"role": "web",
},
},
"failed.web.serf": serfFilter{
Name: "^web-.*",
Status: "failed",
Tags: map[string]string{
"role": "web",
},
},
"us.dc.serf": serfFilter{
Tags: map[string]string{
"dc": "us-.*",
},
},
}
result := loadCustomDomainName(data)

if len(expect) != len(result) {
t.Errorf("Failed to load custom domain name: result serf filter table is different from the expected one.")
}

for dn, sf := range expect {
resultSF := result[dn]
if sf.Compare(resultSF) != true {
t.Errorf("Failed to load custom domain name: result serf filter table is different from the expected one.")
}
}
}
12 changes: 11 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
Expand All @@ -15,7 +16,7 @@ func handle(writer dns.ResponseWriter, request *dns.Msg, serfClient *serf_client
message.SetReply(request)

for _, question := range request.Question {
filter := parseDomainName(question.Name)
filter := parseDomainName(question.Name, SerfFilterTable)
hosts, err := getSerfMembers(serfClient, filter)
if err != nil {
fmt.Println(err.Error())
Expand All @@ -40,6 +41,15 @@ func serve(net string, address string) {
func main() {
config.Parse()

if *configCustomDomainNameFile != "" {
customDNData, err := ioutil.ReadFile(*configCustomDomainNameFile)
if err != nil {
fmt.Println(err.Error())
} else {
SerfFilterTable = loadCustomDomainName(customDNData)
}
}

serfClient, err := connectSerfAgent(*configSerfRPCAddress, *configSerfRPCAuthKey)
defer closeSerfConnection(serfClient)
if err != nil {
Expand Down
14 changes: 13 additions & 1 deletion parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import (
"strings"
)

func parseDomainName(domainName string) serfFilter {
func parseDomainName(domainName string, sftab serfFilterTable) serfFilter {
customDomainNameExist := checkCustomDomainNameExistence(domainName, sftab)
if customDomainNameExist {
return parseCustomDomainName(domainName, sftab)
}
return parseTagsDomainName(domainName)
}

func parseTagsDomainName(domainName string) serfFilter {
domainName = strings.TrimSuffix(domainName, *configDomainName)

tags := make(map[string]string)
Expand Down Expand Up @@ -33,3 +41,7 @@ func findTag(domainName string) (tagValue, tagName, remain string) {
res := strings.SplitN(domainName, ".", 3)
return res[0], res[1], res[2]
}

func parseCustomDomainName(domainName string, sftab serfFilterTable) serfFilter {
return sftab[domainName]
}
66 changes: 65 additions & 1 deletion parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,46 @@ import (
func TestParseDomainName(t *testing.T) {
config.Parse()

SFTable := serfFilterTable{
"dead.digit.serf.": serfFilter{
Name: "^[0-9].*",
Status: "failed",
},
"digit.name.serf.": serfFilter{
Name: "^[0-9].*",
Status: "alive",
},
"dead.serf.": serfFilter{
Status: "failed",
},
}

for dn, sf := range SFTable {
resultSF := parseDomainName(dn, SFTable)
ok := sf.Compare(resultSF)
if !ok {
t.Errorf("Failed to parse custom domain name %s", dn)
}
}

domainNameSample := "foo.srv.cali.dc.serf."
expect := serfFilter{
Tags: map[string]string{
"srv": "foo",
"dc": "cali",
},
Status: "alive",
}
result := parseDomainName(domainNameSample, SFTable)
ok := expect.Compare(result)
if !ok {
t.Errorf("Failed to parse domain name %s", domainNameSample)
}
}

func TestParseTagsDomainName(t *testing.T) {
config.Parse()

domainNameSample := "foo.srv.cali.dc.serf."
expectedSerfFilter := serfFilter{
Tags: map[string]string{
Expand All @@ -15,7 +55,7 @@ func TestParseDomainName(t *testing.T) {
},
Status: "alive",
}
resultSerfFilter := parseDomainName(domainNameSample)
resultSerfFilter := parseTagsDomainName(domainNameSample)
ok := expectedSerfFilter.Compare(resultSerfFilter)
if !ok {
t.Errorf("Failed to parse domain name %s", domainNameSample)
Expand All @@ -41,3 +81,27 @@ func TestFindTag(t *testing.T) {
t.Errorf("Failed to find tag in domain name")
}
}

func TestParseCustomDomainName(t *testing.T) {
SFTable := serfFilterTable{
"dead.digit.serf.": serfFilter{
Name: "^[0-9].*",
Status: "failed",
},
"digit.name.serf.": serfFilter{
Name: "^[0-9].*",
Status: "alive",
},
"dead.serf.": serfFilter{
Status: "failed",
},
}

for dn, sf := range SFTable {
resultSF := parseCustomDomainName(dn, SFTable)
ok := sf.Compare(resultSF)
if !ok {
t.Errorf("Failed to parse custom domain name %s", dn)
}
}
}

0 comments on commit 09b1a89

Please sign in to comment.