commit
7bd0bd14c8
@ -202,8 +202,8 @@ var (
|
|||||||
defaultClient = "rpc"
|
defaultClient = "rpc"
|
||||||
defaultServer = "rpc"
|
defaultServer = "rpc"
|
||||||
defaultBroker = "http"
|
defaultBroker = "http"
|
||||||
defaultRegistry = "consul"
|
defaultRegistry = "mdns"
|
||||||
defaultSelector = "cache"
|
defaultSelector = "registry"
|
||||||
defaultTransport = "http"
|
defaultTransport = "http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,11 +1,387 @@
|
|||||||
// Package consul provides a consul based registry and is the default discovery system
|
|
||||||
package consul
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
consul "github.com/hashicorp/consul/api"
|
||||||
"github.com/micro/go-micro/registry"
|
"github.com/micro/go-micro/registry"
|
||||||
|
hash "github.com/mitchellh/hashstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewRegistry returns a new consul registry
|
type consulRegistry struct {
|
||||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
Address string
|
||||||
return registry.NewRegistry(opts...)
|
Client *consul.Client
|
||||||
|
opts registry.Options
|
||||||
|
|
||||||
|
// connect enabled
|
||||||
|
connect bool
|
||||||
|
|
||||||
|
queryOptions *consul.QueryOptions
|
||||||
|
|
||||||
|
sync.Mutex
|
||||||
|
register map[string]uint64
|
||||||
|
// lastChecked tracks when a node was last checked as existing in Consul
|
||||||
|
lastChecked map[string]time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDeregisterTTL(t time.Duration) time.Duration {
|
||||||
|
// splay slightly for the watcher?
|
||||||
|
splay := time.Second * 5
|
||||||
|
deregTTL := t + splay
|
||||||
|
|
||||||
|
// consul has a minimum timeout on deregistration of 1 minute.
|
||||||
|
if t < time.Minute {
|
||||||
|
deregTTL = time.Minute + splay
|
||||||
|
}
|
||||||
|
|
||||||
|
return deregTTL
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransport(config *tls.Config) *http.Transport {
|
||||||
|
if config == nil {
|
||||||
|
config = &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t := &http.Transport{
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
|
Dial: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).Dial,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
TLSClientConfig: config,
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(&t, func(tr **http.Transport) {
|
||||||
|
(*tr).CloseIdleConnections()
|
||||||
|
})
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func configure(c *consulRegistry, opts ...registry.Option) {
|
||||||
|
// set opts
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&c.opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// use default config
|
||||||
|
config := consul.DefaultConfig()
|
||||||
|
|
||||||
|
if c.opts.Context != nil {
|
||||||
|
// Use the consul config passed in the options, if available
|
||||||
|
if co, ok := c.opts.Context.Value("consul_config").(*consul.Config); ok {
|
||||||
|
config = co
|
||||||
|
}
|
||||||
|
if cn, ok := c.opts.Context.Value("consul_connect").(bool); ok {
|
||||||
|
c.connect = cn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the consul query options passed in the options, if available
|
||||||
|
if qo, ok := c.opts.Context.Value("consul_query_options").(*consul.QueryOptions); ok && qo != nil {
|
||||||
|
c.queryOptions = qo
|
||||||
|
}
|
||||||
|
if as, ok := c.opts.Context.Value("consul_allow_stale").(bool); ok {
|
||||||
|
c.queryOptions.AllowStale = as
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if there are any addrs
|
||||||
|
if len(c.opts.Addrs) > 0 {
|
||||||
|
addr, port, err := net.SplitHostPort(c.opts.Addrs[0])
|
||||||
|
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
||||||
|
port = "8500"
|
||||||
|
addr = c.opts.Addrs[0]
|
||||||
|
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
||||||
|
} else if err == nil {
|
||||||
|
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requires secure connection?
|
||||||
|
if c.opts.Secure || c.opts.TLSConfig != nil {
|
||||||
|
if config.HttpClient == nil {
|
||||||
|
config.HttpClient = new(http.Client)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Scheme = "https"
|
||||||
|
// We're going to support InsecureSkipVerify
|
||||||
|
config.HttpClient.Transport = newTransport(c.opts.TLSConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set timeout
|
||||||
|
if c.opts.Timeout > 0 {
|
||||||
|
config.HttpClient.Timeout = c.opts.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the client
|
||||||
|
client, _ := consul.NewClient(config)
|
||||||
|
|
||||||
|
// set address/client
|
||||||
|
c.Address = config.Address
|
||||||
|
c.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) Init(opts ...registry.Option) error {
|
||||||
|
configure(c, opts...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) Deregister(s *registry.Service) error {
|
||||||
|
if len(s.Nodes) == 0 {
|
||||||
|
return errors.New("Require at least one node")
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete our hash and time check of the service
|
||||||
|
c.Lock()
|
||||||
|
delete(c.register, s.Name)
|
||||||
|
delete(c.lastChecked, s.Name)
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
node := s.Nodes[0]
|
||||||
|
return c.Client.Agent().ServiceDeregister(node.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
|
||||||
|
if len(s.Nodes) == 0 {
|
||||||
|
return errors.New("Require at least one node")
|
||||||
|
}
|
||||||
|
|
||||||
|
var regTCPCheck bool
|
||||||
|
var regInterval time.Duration
|
||||||
|
|
||||||
|
var options registry.RegisterOptions
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&options)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.opts.Context != nil {
|
||||||
|
if tcpCheckInterval, ok := c.opts.Context.Value("consul_tcp_check").(time.Duration); ok {
|
||||||
|
regTCPCheck = true
|
||||||
|
regInterval = tcpCheckInterval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create hash of service; uint64
|
||||||
|
h, err := hash.Hash(s, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// use first node
|
||||||
|
node := s.Nodes[0]
|
||||||
|
|
||||||
|
// get existing hash and last checked time
|
||||||
|
c.Lock()
|
||||||
|
v, ok := c.register[s.Name]
|
||||||
|
lastChecked := c.lastChecked[s.Name]
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
// if it's already registered and matches then just pass the check
|
||||||
|
if ok && v == h {
|
||||||
|
if options.TTL == time.Duration(0) {
|
||||||
|
// ensure that our service hasn't been deregistered by Consul
|
||||||
|
if time.Since(lastChecked) <= getDeregisterTTL(regInterval) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
services, _, err := c.Client.Health().Checks(s.Name, c.queryOptions)
|
||||||
|
if err == nil {
|
||||||
|
for _, v := range services {
|
||||||
|
if v.ServiceID == node.Id {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the err is nil we're all good, bail out
|
||||||
|
// if not, we don't know what the state is, so full re-register
|
||||||
|
if err := c.Client.Agent().PassTTL("service:"+node.Id, ""); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode the tags
|
||||||
|
tags := encodeMetadata(node.Metadata)
|
||||||
|
tags = append(tags, encodeEndpoints(s.Endpoints)...)
|
||||||
|
tags = append(tags, encodeVersion(s.Version)...)
|
||||||
|
|
||||||
|
var check *consul.AgentServiceCheck
|
||||||
|
|
||||||
|
if regTCPCheck {
|
||||||
|
deregTTL := getDeregisterTTL(regInterval)
|
||||||
|
|
||||||
|
check = &consul.AgentServiceCheck{
|
||||||
|
TCP: fmt.Sprintf("%s:%d", node.Address, node.Port),
|
||||||
|
Interval: fmt.Sprintf("%v", regInterval),
|
||||||
|
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the TTL is greater than 0 create an associated check
|
||||||
|
} else if options.TTL > time.Duration(0) {
|
||||||
|
deregTTL := getDeregisterTTL(options.TTL)
|
||||||
|
|
||||||
|
check = &consul.AgentServiceCheck{
|
||||||
|
TTL: fmt.Sprintf("%v", options.TTL),
|
||||||
|
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the service
|
||||||
|
asr := &consul.AgentServiceRegistration{
|
||||||
|
ID: node.Id,
|
||||||
|
Name: s.Name,
|
||||||
|
Tags: tags,
|
||||||
|
Port: node.Port,
|
||||||
|
Address: node.Address,
|
||||||
|
Check: check,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specify consul connect
|
||||||
|
if c.connect {
|
||||||
|
asr.Connect = &consul.AgentServiceConnect{
|
||||||
|
Native: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Client.Agent().ServiceRegister(asr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// save our hash and time check of the service
|
||||||
|
c.Lock()
|
||||||
|
c.register[s.Name] = h
|
||||||
|
c.lastChecked[s.Name] = time.Now()
|
||||||
|
c.Unlock()
|
||||||
|
|
||||||
|
// if the TTL is 0 we don't mess with the checks
|
||||||
|
if options.TTL == time.Duration(0) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pass the healthcheck
|
||||||
|
return c.Client.Agent().PassTTL("service:"+node.Id, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) {
|
||||||
|
var rsp []*consul.ServiceEntry
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// if we're connect enabled only get connect services
|
||||||
|
if c.connect {
|
||||||
|
rsp, _, err = c.Client.Health().Connect(name, "", false, c.queryOptions)
|
||||||
|
} else {
|
||||||
|
rsp, _, err = c.Client.Health().Service(name, "", false, c.queryOptions)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceMap := map[string]*registry.Service{}
|
||||||
|
|
||||||
|
for _, s := range rsp {
|
||||||
|
if s.Service.Service != name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// version is now a tag
|
||||||
|
version, _ := decodeVersion(s.Service.Tags)
|
||||||
|
// service ID is now the node id
|
||||||
|
id := s.Service.ID
|
||||||
|
// key is always the version
|
||||||
|
key := version
|
||||||
|
|
||||||
|
// address is service address
|
||||||
|
address := s.Service.Address
|
||||||
|
|
||||||
|
// use node address
|
||||||
|
if len(address) == 0 {
|
||||||
|
address = s.Node.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, ok := serviceMap[key]
|
||||||
|
if !ok {
|
||||||
|
svc = ®istry.Service{
|
||||||
|
Endpoints: decodeEndpoints(s.Service.Tags),
|
||||||
|
Name: s.Service.Service,
|
||||||
|
Version: version,
|
||||||
|
}
|
||||||
|
serviceMap[key] = svc
|
||||||
|
}
|
||||||
|
|
||||||
|
var del bool
|
||||||
|
|
||||||
|
for _, check := range s.Checks {
|
||||||
|
// delete the node if the status is critical
|
||||||
|
if check.Status == "critical" {
|
||||||
|
del = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if delete then skip the node
|
||||||
|
if del {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.Nodes = append(svc.Nodes, ®istry.Node{
|
||||||
|
Id: id,
|
||||||
|
Address: address,
|
||||||
|
Port: s.Service.Port,
|
||||||
|
Metadata: decodeMetadata(s.Service.Tags),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var services []*registry.Service
|
||||||
|
for _, service := range serviceMap {
|
||||||
|
services = append(services, service)
|
||||||
|
}
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) ListServices() ([]*registry.Service, error) {
|
||||||
|
rsp, _, err := c.Client.Catalog().Services(c.queryOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var services []*registry.Service
|
||||||
|
|
||||||
|
for service := range rsp {
|
||||||
|
services = append(services, ®istry.Service{Name: service})
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||||
|
return newConsulWatcher(c, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) String() string {
|
||||||
|
return "consul"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *consulRegistry) Options() registry.Options {
|
||||||
|
return c.opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||||
|
cr := &consulRegistry{
|
||||||
|
opts: registry.Options{},
|
||||||
|
register: make(map[string]uint64),
|
||||||
|
lastChecked: make(map[string]time.Time),
|
||||||
|
queryOptions: &consul.QueryOptions{
|
||||||
|
AllowStale: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
configure(cr, opts...)
|
||||||
|
return cr
|
||||||
}
|
}
|
||||||
|
170
registry/consul/encoding.go
Normal file
170
registry/consul/encoding.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/micro/go-micro/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encode(buf []byte) string {
|
||||||
|
var b bytes.Buffer
|
||||||
|
defer b.Reset()
|
||||||
|
|
||||||
|
w := zlib.NewWriter(&b)
|
||||||
|
if _, err := w.Write(buf); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
return hex.EncodeToString(b.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode(d string) []byte {
|
||||||
|
hr, err := hex.DecodeString(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bytes.NewReader(hr)
|
||||||
|
zr, err := zlib.NewReader(br)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rbuf, err := ioutil.ReadAll(zr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return rbuf
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeEndpoints(en []*registry.Endpoint) []string {
|
||||||
|
var tags []string
|
||||||
|
for _, e := range en {
|
||||||
|
if b, err := json.Marshal(e); err == nil {
|
||||||
|
tags = append(tags, "e-"+encode(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeEndpoints(tags []string) []*registry.Endpoint {
|
||||||
|
var en []*registry.Endpoint
|
||||||
|
|
||||||
|
// use the first format you find
|
||||||
|
var ver byte
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if len(tag) == 0 || tag[0] != 'e' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check version
|
||||||
|
if ver > 0 && tag[1] != ver {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *registry.Endpoint
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
// Old encoding was plain
|
||||||
|
if tag[1] == '=' {
|
||||||
|
buf = []byte(tag[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// New encoding is hex
|
||||||
|
if tag[1] == '-' {
|
||||||
|
buf = decode(tag[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(buf, &e); err == nil {
|
||||||
|
en = append(en, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set version
|
||||||
|
ver = tag[1]
|
||||||
|
}
|
||||||
|
return en
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeMetadata(md map[string]string) []string {
|
||||||
|
var tags []string
|
||||||
|
for k, v := range md {
|
||||||
|
if b, err := json.Marshal(map[string]string{
|
||||||
|
k: v,
|
||||||
|
}); err == nil {
|
||||||
|
// new encoding
|
||||||
|
tags = append(tags, "t-"+encode(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMetadata(tags []string) map[string]string {
|
||||||
|
md := make(map[string]string)
|
||||||
|
|
||||||
|
var ver byte
|
||||||
|
|
||||||
|
for _, tag := range tags {
|
||||||
|
if len(tag) == 0 || tag[0] != 't' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check version
|
||||||
|
if ver > 0 && tag[1] != ver {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var kv map[string]string
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
// Old encoding was plain
|
||||||
|
if tag[1] == '=' {
|
||||||
|
buf = []byte(tag[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// New encoding is hex
|
||||||
|
if tag[1] == '-' {
|
||||||
|
buf = decode(tag[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now unmarshal
|
||||||
|
if err := json.Unmarshal(buf, &kv); err == nil {
|
||||||
|
for k, v := range kv {
|
||||||
|
md[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set version
|
||||||
|
ver = tag[1]
|
||||||
|
}
|
||||||
|
return md
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeVersion(v string) []string {
|
||||||
|
return []string{"v-" + encode([]byte(v))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeVersion(tags []string) (string, bool) {
|
||||||
|
for _, tag := range tags {
|
||||||
|
if len(tag) < 2 || tag[0] != 'v' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Old encoding was plain
|
||||||
|
if tag[1] == '=' {
|
||||||
|
return tag[2:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// New encoding is hex
|
||||||
|
if tag[1] == '-' {
|
||||||
|
return string(decode(tag[2:])), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
147
registry/consul/encoding_test.go
Normal file
147
registry/consul/encoding_test.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package consul
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/micro/go-micro/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodingEndpoints(t *testing.T) {
|
||||||
|
eps := []*registry.Endpoint{
|
||||||
|
®istry.Endpoint{
|
||||||
|
Name: "endpoint1",
|
||||||
|
Request: ®istry.Value{
|
||||||
|
Name: "request",
|
||||||
|
Type: "request",
|
||||||
|
},
|
||||||
|
Response: ®istry.Value{
|
||||||
|
Name: "response",
|
||||||
|
Type: "response",
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"foo1": "bar1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
®istry.Endpoint{
|
||||||
|
Name: "endpoint2",
|
||||||
|
Request: ®istry.Value{
|
||||||
|
Name: "request",
|
||||||
|
Type: "request",
|
||||||
|
},
|
||||||
|
Response: ®istry.Value{
|
||||||
|
Name: "response",
|
||||||
|
Type: "response",
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"foo2": "bar2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
®istry.Endpoint{
|
||||||
|
Name: "endpoint3",
|
||||||
|
Request: ®istry.Value{
|
||||||
|
Name: "request",
|
||||||
|
Type: "request",
|
||||||
|
},
|
||||||
|
Response: ®istry.Value{
|
||||||
|
Name: "response",
|
||||||
|
Type: "response",
|
||||||
|
},
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"foo3": "bar3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testEp := func(ep *registry.Endpoint, enc string) {
|
||||||
|
// encode endpoint
|
||||||
|
e := encodeEndpoints([]*registry.Endpoint{ep})
|
||||||
|
|
||||||
|
// check there are two tags; old and new
|
||||||
|
if len(e) != 1 {
|
||||||
|
t.Fatalf("Expected 1 encoded tags, got %v", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check old encoding
|
||||||
|
var seen bool
|
||||||
|
|
||||||
|
for _, en := range e {
|
||||||
|
if en == enc {
|
||||||
|
seen = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !seen {
|
||||||
|
t.Fatalf("Expected %s but not found", enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode
|
||||||
|
d := decodeEndpoints([]string{enc})
|
||||||
|
if len(d) == 0 {
|
||||||
|
t.Fatalf("Expected %v got %v", ep, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check name
|
||||||
|
if d[0].Name != ep.Name {
|
||||||
|
t.Fatalf("Expected ep %s got %s", ep.Name, d[0].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check all the metadata exists
|
||||||
|
for k, v := range ep.Metadata {
|
||||||
|
if gv := d[0].Metadata[k]; gv != v {
|
||||||
|
t.Fatalf("Expected key %s val %s got val %s", k, v, gv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ep := range eps {
|
||||||
|
// JSON encoded
|
||||||
|
jencoded, err := json.Marshal(ep)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEX encoded
|
||||||
|
hencoded := encode(jencoded)
|
||||||
|
// endpoint tag
|
||||||
|
hepTag := "e-" + hencoded
|
||||||
|
testEp(ep, hepTag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodingVersion(t *testing.T) {
|
||||||
|
testData := []struct {
|
||||||
|
decoded string
|
||||||
|
encoded string
|
||||||
|
}{
|
||||||
|
{"1.0.0", "v-789c32d433d03300040000ffff02ce00ee"},
|
||||||
|
{"latest", "v-789cca492c492d2e01040000ffff08cc028e"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, data := range testData {
|
||||||
|
e := encodeVersion(data.decoded)
|
||||||
|
|
||||||
|
if e[0] != data.encoded {
|
||||||
|
t.Fatalf("Expected %s got %s", data.encoded, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, ok := decodeVersion(e)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unexpected %t for %s", ok, data.encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d != data.decoded {
|
||||||
|
t.Fatalf("Expected %s got %s", data.decoded, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
d, ok = decodeVersion([]string{data.encoded})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unexpected %t for %s", ok, data.encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d != data.decoded {
|
||||||
|
t.Fatalf("Expected %s got %s", data.decoded, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package registry
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
consul "github.com/hashicorp/consul/api"
|
consul "github.com/hashicorp/consul/api"
|
||||||
|
"github.com/micro/go-micro/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockRegistry struct {
|
type mockRegistry struct {
|
||||||
@ -56,7 +57,7 @@ func newConsulTestRegistry(r *mockRegistry) (*consulRegistry, func()) {
|
|||||||
return &consulRegistry{
|
return &consulRegistry{
|
||||||
Address: cfg.Address,
|
Address: cfg.Address,
|
||||||
Client: cl,
|
Client: cl,
|
||||||
opts: Options{},
|
opts: registry.Options{},
|
||||||
register: make(map[string]uint64),
|
register: make(map[string]uint64),
|
||||||
lastChecked: make(map[string]time.Time),
|
lastChecked: make(map[string]time.Time),
|
||||||
queryOptions: &consul.QueryOptions{
|
queryOptions: &consul.QueryOptions{
|
@ -1,4 +1,4 @@
|
|||||||
package registry
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
@ -6,23 +6,24 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/watch"
|
"github.com/hashicorp/consul/watch"
|
||||||
|
"github.com/micro/go-micro/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
type consulWatcher struct {
|
type consulWatcher struct {
|
||||||
r *consulRegistry
|
r *consulRegistry
|
||||||
wo WatchOptions
|
wo registry.WatchOptions
|
||||||
wp *watch.Plan
|
wp *watch.Plan
|
||||||
watchers map[string]*watch.Plan
|
watchers map[string]*watch.Plan
|
||||||
|
|
||||||
next chan *Result
|
next chan *registry.Result
|
||||||
exit chan bool
|
exit chan bool
|
||||||
|
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
services map[string][]*Service
|
services map[string][]*registry.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConsulWatcher(cr *consulRegistry, opts ...WatchOption) (Watcher, error) {
|
func newConsulWatcher(cr *consulRegistry, opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||||
var wo WatchOptions
|
var wo registry.WatchOptions
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
o(&wo)
|
o(&wo)
|
||||||
}
|
}
|
||||||
@ -31,9 +32,9 @@ func newConsulWatcher(cr *consulRegistry, opts ...WatchOption) (Watcher, error)
|
|||||||
r: cr,
|
r: cr,
|
||||||
wo: wo,
|
wo: wo,
|
||||||
exit: make(chan bool),
|
exit: make(chan bool),
|
||||||
next: make(chan *Result, 10),
|
next: make(chan *registry.Result, 10),
|
||||||
watchers: make(map[string]*watch.Plan),
|
watchers: make(map[string]*watch.Plan),
|
||||||
services: make(map[string][]*Service),
|
services: make(map[string][]*registry.Service),
|
||||||
}
|
}
|
||||||
|
|
||||||
wp, err := watch.Parse(map[string]interface{}{"type": "services"})
|
wp, err := watch.Parse(map[string]interface{}{"type": "services"})
|
||||||
@ -54,7 +55,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceMap := map[string]*Service{}
|
serviceMap := map[string]*registry.Service{}
|
||||||
serviceName := ""
|
serviceName := ""
|
||||||
|
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
@ -75,7 +76,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
|
|
||||||
svc, ok := serviceMap[key]
|
svc, ok := serviceMap[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
svc = &Service{
|
svc = ®istry.Service{
|
||||||
Endpoints: decodeEndpoints(e.Service.Tags),
|
Endpoints: decodeEndpoints(e.Service.Tags),
|
||||||
Name: e.Service.Service,
|
Name: e.Service.Service,
|
||||||
Version: version,
|
Version: version,
|
||||||
@ -98,7 +99,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
svc.Nodes = append(svc.Nodes, &Node{
|
svc.Nodes = append(svc.Nodes, ®istry.Node{
|
||||||
Id: id,
|
Id: id,
|
||||||
Address: address,
|
Address: address,
|
||||||
Port: e.Service.Port,
|
Port: e.Service.Port,
|
||||||
@ -108,13 +109,13 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
|
|
||||||
cw.RLock()
|
cw.RLock()
|
||||||
// make a copy
|
// make a copy
|
||||||
rservices := make(map[string][]*Service)
|
rservices := make(map[string][]*registry.Service)
|
||||||
for k, v := range cw.services {
|
for k, v := range cw.services {
|
||||||
rservices[k] = v
|
rservices[k] = v
|
||||||
}
|
}
|
||||||
cw.RUnlock()
|
cw.RUnlock()
|
||||||
|
|
||||||
var newServices []*Service
|
var newServices []*registry.Service
|
||||||
|
|
||||||
// serviceMap is the new set of services keyed by name+version
|
// serviceMap is the new set of services keyed by name+version
|
||||||
for _, newService := range serviceMap {
|
for _, newService := range serviceMap {
|
||||||
@ -125,7 +126,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
oldServices, ok := rservices[serviceName]
|
oldServices, ok := rservices[serviceName]
|
||||||
if !ok {
|
if !ok {
|
||||||
// does not exist? then we're creating brand new entries
|
// does not exist? then we're creating brand new entries
|
||||||
cw.next <- &Result{Action: "create", Service: newService}
|
cw.next <- ®istry.Result{Action: "create", Service: newService}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +143,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
// yes? then it's an update
|
// yes? then it's an update
|
||||||
action = "update"
|
action = "update"
|
||||||
|
|
||||||
var nodes []*Node
|
var nodes []*registry.Node
|
||||||
// check the old nodes to see if they've been deleted
|
// check the old nodes to see if they've been deleted
|
||||||
for _, oldNode := range oldService.Nodes {
|
for _, oldNode := range oldService.Nodes {
|
||||||
var seen bool
|
var seen bool
|
||||||
@ -163,11 +164,11 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
if len(nodes) > 0 {
|
if len(nodes) > 0 {
|
||||||
delService := oldService
|
delService := oldService
|
||||||
delService.Nodes = nodes
|
delService.Nodes = nodes
|
||||||
cw.next <- &Result{Action: "delete", Service: delService}
|
cw.next <- ®istry.Result{Action: "delete", Service: delService}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cw.next <- &Result{Action: action, Service: newService}
|
cw.next <- ®istry.Result{Action: action, Service: newService}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check old versions that may not be in new services map
|
// Now check old versions that may not be in new services map
|
||||||
@ -175,7 +176,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
|
|||||||
// old version does not exist in new version map
|
// old version does not exist in new version map
|
||||||
// kill it with fire!
|
// kill it with fire!
|
||||||
if _, ok := serviceMap[old.Version]; !ok {
|
if _, ok := serviceMap[old.Version]; !ok {
|
||||||
cw.next <- &Result{Action: "delete", Service: old}
|
cw.next <- ®istry.Result{Action: "delete", Service: old}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,13 +210,13 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
|
|||||||
wp.Handler = cw.serviceHandler
|
wp.Handler = cw.serviceHandler
|
||||||
go wp.Run(cw.r.Address)
|
go wp.Run(cw.r.Address)
|
||||||
cw.watchers[service] = wp
|
cw.watchers[service] = wp
|
||||||
cw.next <- &Result{Action: "create", Service: &Service{Name: service}}
|
cw.next <- ®istry.Result{Action: "create", Service: ®istry.Service{Name: service}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cw.RLock()
|
cw.RLock()
|
||||||
// make a copy
|
// make a copy
|
||||||
rservices := make(map[string][]*Service)
|
rservices := make(map[string][]*registry.Service)
|
||||||
for k, v := range cw.services {
|
for k, v := range cw.services {
|
||||||
rservices[k] = v
|
rservices[k] = v
|
||||||
}
|
}
|
||||||
@ -235,12 +236,12 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
|
|||||||
if _, ok := services[service]; !ok {
|
if _, ok := services[service]; !ok {
|
||||||
w.Stop()
|
w.Stop()
|
||||||
delete(cw.watchers, service)
|
delete(cw.watchers, service)
|
||||||
cw.next <- &Result{Action: "delete", Service: &Service{Name: service}}
|
cw.next <- ®istry.Result{Action: "delete", Service: ®istry.Service{Name: service}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cw *consulWatcher) Next() (*Result, error) {
|
func (cw *consulWatcher) Next() (*registry.Result, error) {
|
||||||
select {
|
select {
|
||||||
case <-cw.exit:
|
case <-cw.exit:
|
||||||
return nil, errors.New("result chan closed")
|
return nil, errors.New("result chan closed")
|
@ -1,9 +1,10 @@
|
|||||||
package registry
|
package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
|
"github.com/micro/go-micro/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHealthyServiceHandler(t *testing.T) {
|
func TestHealthyServiceHandler(t *testing.T) {
|
||||||
@ -58,8 +59,8 @@ func TestUnhealthyNodeServiceHandler(t *testing.T) {
|
|||||||
func newWatcher() *consulWatcher {
|
func newWatcher() *consulWatcher {
|
||||||
return &consulWatcher{
|
return &consulWatcher{
|
||||||
exit: make(chan bool),
|
exit: make(chan bool),
|
||||||
next: make(chan *Result, 10),
|
next: make(chan *registry.Result, 10),
|
||||||
services: make(map[string][]*Service),
|
services: make(map[string][]*registry.Service),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,386 +0,0 @@
|
|||||||
package registry
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
consul "github.com/hashicorp/consul/api"
|
|
||||||
hash "github.com/mitchellh/hashstructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
type consulRegistry struct {
|
|
||||||
Address string
|
|
||||||
Client *consul.Client
|
|
||||||
opts Options
|
|
||||||
|
|
||||||
// connect enabled
|
|
||||||
connect bool
|
|
||||||
|
|
||||||
queryOptions *consul.QueryOptions
|
|
||||||
|
|
||||||
sync.Mutex
|
|
||||||
register map[string]uint64
|
|
||||||
// lastChecked tracks when a node was last checked as existing in Consul
|
|
||||||
lastChecked map[string]time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeregisterTTL(t time.Duration) time.Duration {
|
|
||||||
// splay slightly for the watcher?
|
|
||||||
splay := time.Second * 5
|
|
||||||
deregTTL := t + splay
|
|
||||||
|
|
||||||
// consul has a minimum timeout on deregistration of 1 minute.
|
|
||||||
if t < time.Minute {
|
|
||||||
deregTTL = time.Minute + splay
|
|
||||||
}
|
|
||||||
|
|
||||||
return deregTTL
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTransport(config *tls.Config) *http.Transport {
|
|
||||||
if config == nil {
|
|
||||||
config = &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &http.Transport{
|
|
||||||
Proxy: http.ProxyFromEnvironment,
|
|
||||||
Dial: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
}).Dial,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
TLSClientConfig: config,
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(&t, func(tr **http.Transport) {
|
|
||||||
(*tr).CloseIdleConnections()
|
|
||||||
})
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
func configure(c *consulRegistry, opts ...Option) {
|
|
||||||
// set opts
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&c.opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// use default config
|
|
||||||
config := consul.DefaultConfig()
|
|
||||||
|
|
||||||
if c.opts.Context != nil {
|
|
||||||
// Use the consul config passed in the options, if available
|
|
||||||
if co, ok := c.opts.Context.Value("consul_config").(*consul.Config); ok {
|
|
||||||
config = co
|
|
||||||
}
|
|
||||||
if cn, ok := c.opts.Context.Value("consul_connect").(bool); ok {
|
|
||||||
c.connect = cn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the consul query options passed in the options, if available
|
|
||||||
if qo, ok := c.opts.Context.Value("consul_query_options").(*consul.QueryOptions); ok && qo != nil {
|
|
||||||
c.queryOptions = qo
|
|
||||||
}
|
|
||||||
if as, ok := c.opts.Context.Value("consul_allow_stale").(bool); ok {
|
|
||||||
c.queryOptions.AllowStale = as
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there are any addrs
|
|
||||||
if len(c.opts.Addrs) > 0 {
|
|
||||||
addr, port, err := net.SplitHostPort(c.opts.Addrs[0])
|
|
||||||
if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" {
|
|
||||||
port = "8500"
|
|
||||||
addr = c.opts.Addrs[0]
|
|
||||||
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
|
||||||
} else if err == nil {
|
|
||||||
config.Address = fmt.Sprintf("%s:%s", addr, port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires secure connection?
|
|
||||||
if c.opts.Secure || c.opts.TLSConfig != nil {
|
|
||||||
if config.HttpClient == nil {
|
|
||||||
config.HttpClient = new(http.Client)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Scheme = "https"
|
|
||||||
// We're going to support InsecureSkipVerify
|
|
||||||
config.HttpClient.Transport = newTransport(c.opts.TLSConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set timeout
|
|
||||||
if c.opts.Timeout > 0 {
|
|
||||||
config.HttpClient.Timeout = c.opts.Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the client
|
|
||||||
client, _ := consul.NewClient(config)
|
|
||||||
|
|
||||||
// set address/client
|
|
||||||
c.Address = config.Address
|
|
||||||
c.Client = client
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConsulRegistry(opts ...Option) Registry {
|
|
||||||
cr := &consulRegistry{
|
|
||||||
opts: Options{},
|
|
||||||
register: make(map[string]uint64),
|
|
||||||
lastChecked: make(map[string]time.Time),
|
|
||||||
queryOptions: &consul.QueryOptions{
|
|
||||||
AllowStale: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
configure(cr, opts...)
|
|
||||||
return cr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) Init(opts ...Option) error {
|
|
||||||
configure(c, opts...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) Deregister(s *Service) error {
|
|
||||||
if len(s.Nodes) == 0 {
|
|
||||||
return errors.New("Require at least one node")
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete our hash and time check of the service
|
|
||||||
c.Lock()
|
|
||||||
delete(c.register, s.Name)
|
|
||||||
delete(c.lastChecked, s.Name)
|
|
||||||
c.Unlock()
|
|
||||||
|
|
||||||
node := s.Nodes[0]
|
|
||||||
return c.Client.Agent().ServiceDeregister(node.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error {
|
|
||||||
if len(s.Nodes) == 0 {
|
|
||||||
return errors.New("Require at least one node")
|
|
||||||
}
|
|
||||||
|
|
||||||
var regTCPCheck bool
|
|
||||||
var regInterval time.Duration
|
|
||||||
|
|
||||||
var options RegisterOptions
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.opts.Context != nil {
|
|
||||||
if tcpCheckInterval, ok := c.opts.Context.Value("consul_tcp_check").(time.Duration); ok {
|
|
||||||
regTCPCheck = true
|
|
||||||
regInterval = tcpCheckInterval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create hash of service; uint64
|
|
||||||
h, err := hash.Hash(s, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// use first node
|
|
||||||
node := s.Nodes[0]
|
|
||||||
|
|
||||||
// get existing hash and last checked time
|
|
||||||
c.Lock()
|
|
||||||
v, ok := c.register[s.Name]
|
|
||||||
lastChecked := c.lastChecked[s.Name]
|
|
||||||
c.Unlock()
|
|
||||||
|
|
||||||
// if it's already registered and matches then just pass the check
|
|
||||||
if ok && v == h {
|
|
||||||
if options.TTL == time.Duration(0) {
|
|
||||||
// ensure that our service hasn't been deregistered by Consul
|
|
||||||
if time.Since(lastChecked) <= getDeregisterTTL(regInterval) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
services, _, err := c.Client.Health().Checks(s.Name, c.queryOptions)
|
|
||||||
if err == nil {
|
|
||||||
for _, v := range services {
|
|
||||||
if v.ServiceID == node.Id {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if the err is nil we're all good, bail out
|
|
||||||
// if not, we don't know what the state is, so full re-register
|
|
||||||
if err := c.Client.Agent().PassTTL("service:"+node.Id, ""); err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// encode the tags
|
|
||||||
tags := encodeMetadata(node.Metadata)
|
|
||||||
tags = append(tags, encodeEndpoints(s.Endpoints)...)
|
|
||||||
tags = append(tags, encodeVersion(s.Version)...)
|
|
||||||
|
|
||||||
var check *consul.AgentServiceCheck
|
|
||||||
|
|
||||||
if regTCPCheck {
|
|
||||||
deregTTL := getDeregisterTTL(regInterval)
|
|
||||||
|
|
||||||
check = &consul.AgentServiceCheck{
|
|
||||||
TCP: fmt.Sprintf("%s:%d", node.Address, node.Port),
|
|
||||||
Interval: fmt.Sprintf("%v", regInterval),
|
|
||||||
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the TTL is greater than 0 create an associated check
|
|
||||||
} else if options.TTL > time.Duration(0) {
|
|
||||||
deregTTL := getDeregisterTTL(options.TTL)
|
|
||||||
|
|
||||||
check = &consul.AgentServiceCheck{
|
|
||||||
TTL: fmt.Sprintf("%v", options.TTL),
|
|
||||||
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// register the service
|
|
||||||
asr := &consul.AgentServiceRegistration{
|
|
||||||
ID: node.Id,
|
|
||||||
Name: s.Name,
|
|
||||||
Tags: tags,
|
|
||||||
Port: node.Port,
|
|
||||||
Address: node.Address,
|
|
||||||
Check: check,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specify consul connect
|
|
||||||
if c.connect {
|
|
||||||
asr.Connect = &consul.AgentServiceConnect{
|
|
||||||
Native: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Client.Agent().ServiceRegister(asr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// save our hash and time check of the service
|
|
||||||
c.Lock()
|
|
||||||
c.register[s.Name] = h
|
|
||||||
c.lastChecked[s.Name] = time.Now()
|
|
||||||
c.Unlock()
|
|
||||||
|
|
||||||
// if the TTL is 0 we don't mess with the checks
|
|
||||||
if options.TTL == time.Duration(0) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pass the healthcheck
|
|
||||||
return c.Client.Agent().PassTTL("service:"+node.Id, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) GetService(name string) ([]*Service, error) {
|
|
||||||
var rsp []*consul.ServiceEntry
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// if we're connect enabled only get connect services
|
|
||||||
if c.connect {
|
|
||||||
rsp, _, err = c.Client.Health().Connect(name, "", false, c.queryOptions)
|
|
||||||
} else {
|
|
||||||
rsp, _, err = c.Client.Health().Service(name, "", false, c.queryOptions)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceMap := map[string]*Service{}
|
|
||||||
|
|
||||||
for _, s := range rsp {
|
|
||||||
if s.Service.Service != name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// version is now a tag
|
|
||||||
version, _ := decodeVersion(s.Service.Tags)
|
|
||||||
// service ID is now the node id
|
|
||||||
id := s.Service.ID
|
|
||||||
// key is always the version
|
|
||||||
key := version
|
|
||||||
|
|
||||||
// address is service address
|
|
||||||
address := s.Service.Address
|
|
||||||
|
|
||||||
// use node address
|
|
||||||
if len(address) == 0 {
|
|
||||||
address = s.Node.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
svc, ok := serviceMap[key]
|
|
||||||
if !ok {
|
|
||||||
svc = &Service{
|
|
||||||
Endpoints: decodeEndpoints(s.Service.Tags),
|
|
||||||
Name: s.Service.Service,
|
|
||||||
Version: version,
|
|
||||||
}
|
|
||||||
serviceMap[key] = svc
|
|
||||||
}
|
|
||||||
|
|
||||||
var del bool
|
|
||||||
|
|
||||||
for _, check := range s.Checks {
|
|
||||||
// delete the node if the status is critical
|
|
||||||
if check.Status == "critical" {
|
|
||||||
del = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if delete then skip the node
|
|
||||||
if del {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
svc.Nodes = append(svc.Nodes, &Node{
|
|
||||||
Id: id,
|
|
||||||
Address: address,
|
|
||||||
Port: s.Service.Port,
|
|
||||||
Metadata: decodeMetadata(s.Service.Tags),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var services []*Service
|
|
||||||
for _, service := range serviceMap {
|
|
||||||
services = append(services, service)
|
|
||||||
}
|
|
||||||
return services, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) ListServices() ([]*Service, error) {
|
|
||||||
rsp, _, err := c.Client.Catalog().Services(c.queryOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var services []*Service
|
|
||||||
|
|
||||||
for service := range rsp {
|
|
||||||
services = append(services, &Service{Name: service})
|
|
||||||
}
|
|
||||||
|
|
||||||
return services, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) Watch(opts ...WatchOption) (Watcher, error) {
|
|
||||||
return newConsulWatcher(c, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) String() string {
|
|
||||||
return "consul"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *consulRegistry) Options() Options {
|
|
||||||
return c.opts
|
|
||||||
}
|
|
@ -6,163 +6,68 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func encode(buf []byte) string {
|
func encode(txt *mdnsTxt) ([]string, error) {
|
||||||
var b bytes.Buffer
|
b, err := json.Marshal(txt)
|
||||||
defer b.Reset()
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
w := zlib.NewWriter(&b)
|
var buf bytes.Buffer
|
||||||
if _, err := w.Write(buf); err != nil {
|
defer buf.Reset()
|
||||||
return ""
|
|
||||||
|
w := zlib.NewWriter(&buf)
|
||||||
|
if _, err := w.Write(b); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
w.Close()
|
w.Close()
|
||||||
|
|
||||||
return hex.EncodeToString(b.Bytes())
|
encoded := hex.EncodeToString(buf.Bytes())
|
||||||
|
|
||||||
|
// individual txt limit
|
||||||
|
if len(encoded) <= 255 {
|
||||||
|
return []string{encoded}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// split encoded string
|
||||||
|
var record []string
|
||||||
|
|
||||||
|
for len(encoded) > 255 {
|
||||||
|
record = append(record, encoded[:255])
|
||||||
|
encoded = encoded[255:]
|
||||||
|
}
|
||||||
|
|
||||||
|
record = append(record, encoded)
|
||||||
|
|
||||||
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decode(d string) []byte {
|
func decode(record []string) (*mdnsTxt, error) {
|
||||||
hr, err := hex.DecodeString(d)
|
encoded := strings.Join(record, "")
|
||||||
|
|
||||||
|
hr, err := hex.DecodeString(encoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
br := bytes.NewReader(hr)
|
br := bytes.NewReader(hr)
|
||||||
zr, err := zlib.NewReader(br)
|
zr, err := zlib.NewReader(br)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rbuf, err := ioutil.ReadAll(zr)
|
rbuf, err := ioutil.ReadAll(zr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return rbuf
|
var txt *mdnsTxt
|
||||||
}
|
|
||||||
|
if err := json.Unmarshal(rbuf, &txt); err != nil {
|
||||||
func encodeEndpoints(en []*Endpoint) []string {
|
return nil, err
|
||||||
var tags []string
|
}
|
||||||
for _, e := range en {
|
|
||||||
if b, err := json.Marshal(e); err == nil {
|
return txt, nil
|
||||||
tags = append(tags, "e-"+encode(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tags
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeEndpoints(tags []string) []*Endpoint {
|
|
||||||
var en []*Endpoint
|
|
||||||
|
|
||||||
// use the first format you find
|
|
||||||
var ver byte
|
|
||||||
|
|
||||||
for _, tag := range tags {
|
|
||||||
if len(tag) == 0 || tag[0] != 'e' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check version
|
|
||||||
if ver > 0 && tag[1] != ver {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var e *Endpoint
|
|
||||||
var buf []byte
|
|
||||||
|
|
||||||
// Old encoding was plain
|
|
||||||
if tag[1] == '=' {
|
|
||||||
buf = []byte(tag[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// New encoding is hex
|
|
||||||
if tag[1] == '-' {
|
|
||||||
buf = decode(tag[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(buf, &e); err == nil {
|
|
||||||
en = append(en, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// set version
|
|
||||||
ver = tag[1]
|
|
||||||
}
|
|
||||||
return en
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeMetadata(md map[string]string) []string {
|
|
||||||
var tags []string
|
|
||||||
for k, v := range md {
|
|
||||||
if b, err := json.Marshal(map[string]string{
|
|
||||||
k: v,
|
|
||||||
}); err == nil {
|
|
||||||
// new encoding
|
|
||||||
tags = append(tags, "t-"+encode(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tags
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeMetadata(tags []string) map[string]string {
|
|
||||||
md := make(map[string]string)
|
|
||||||
|
|
||||||
var ver byte
|
|
||||||
|
|
||||||
for _, tag := range tags {
|
|
||||||
if len(tag) == 0 || tag[0] != 't' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check version
|
|
||||||
if ver > 0 && tag[1] != ver {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var kv map[string]string
|
|
||||||
var buf []byte
|
|
||||||
|
|
||||||
// Old encoding was plain
|
|
||||||
if tag[1] == '=' {
|
|
||||||
buf = []byte(tag[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// New encoding is hex
|
|
||||||
if tag[1] == '-' {
|
|
||||||
buf = decode(tag[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now unmarshal
|
|
||||||
if err := json.Unmarshal(buf, &kv); err == nil {
|
|
||||||
for k, v := range kv {
|
|
||||||
md[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set version
|
|
||||||
ver = tag[1]
|
|
||||||
}
|
|
||||||
return md
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeVersion(v string) []string {
|
|
||||||
return []string{"v-" + encode([]byte(v))}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeVersion(tags []string) (string, bool) {
|
|
||||||
for _, tag := range tags {
|
|
||||||
if len(tag) < 2 || tag[0] != 'v' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old encoding was plain
|
|
||||||
if tag[1] == '=' {
|
|
||||||
return tag[2:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
// New encoding is hex
|
|
||||||
if tag[1] == '-' {
|
|
||||||
return string(decode(tag[2:])), true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEncodingEndpoints(t *testing.T) {
|
func TestEncoding(t *testing.T) {
|
||||||
eps := []*Endpoint{
|
testData := []*mdnsTxt{
|
||||||
|
&mdnsTxt{
|
||||||
|
Version: "1.0.0",
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
Endpoints: []*Endpoint{
|
||||||
&Endpoint{
|
&Endpoint{
|
||||||
Name: "endpoint1",
|
Name: "endpoint1",
|
||||||
Request: &Value{
|
Request: &Value{
|
||||||
@ -22,125 +26,40 @@ func TestEncodingEndpoints(t *testing.T) {
|
|||||||
"foo1": "bar1",
|
"foo1": "bar1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&Endpoint{
|
|
||||||
Name: "endpoint2",
|
|
||||||
Request: &Value{
|
|
||||||
Name: "request",
|
|
||||||
Type: "request",
|
|
||||||
},
|
|
||||||
Response: &Value{
|
|
||||||
Name: "response",
|
|
||||||
Type: "response",
|
|
||||||
},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"foo2": "bar2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&Endpoint{
|
|
||||||
Name: "endpoint3",
|
|
||||||
Request: &Value{
|
|
||||||
Name: "request",
|
|
||||||
Type: "request",
|
|
||||||
},
|
|
||||||
Response: &Value{
|
|
||||||
Name: "response",
|
|
||||||
Type: "response",
|
|
||||||
},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"foo3": "bar3",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testEp := func(ep *Endpoint, enc string) {
|
for _, d := range testData {
|
||||||
// encode endpoint
|
encoded, err := encode(d)
|
||||||
e := encodeEndpoints([]*Endpoint{ep})
|
|
||||||
|
|
||||||
// check there are two tags; old and new
|
|
||||||
if len(e) != 1 {
|
|
||||||
t.Fatalf("Expected 1 encoded tags, got %v", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check old encoding
|
|
||||||
var seen bool
|
|
||||||
|
|
||||||
for _, en := range e {
|
|
||||||
if en == enc {
|
|
||||||
seen = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !seen {
|
|
||||||
t.Fatalf("Expected %s but not found", enc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// decode
|
|
||||||
d := decodeEndpoints([]string{enc})
|
|
||||||
if len(d) == 0 {
|
|
||||||
t.Fatalf("Expected %v got %v", ep, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check name
|
|
||||||
if d[0].Name != ep.Name {
|
|
||||||
t.Fatalf("Expected ep %s got %s", ep.Name, d[0].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check all the metadata exists
|
|
||||||
for k, v := range ep.Metadata {
|
|
||||||
if gv := d[0].Metadata[k]; gv != v {
|
|
||||||
t.Fatalf("Expected key %s val %s got val %s", k, v, gv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ep := range eps {
|
|
||||||
// JSON encoded
|
|
||||||
jencoded, err := json.Marshal(ep)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HEX encoded
|
for _, txt := range encoded {
|
||||||
hencoded := encode(jencoded)
|
if len(txt) > 255 {
|
||||||
// endpoint tag
|
t.Fatalf("One of parts for txt is %d characters", len(txt))
|
||||||
hepTag := "e-" + hencoded
|
|
||||||
testEp(ep, hepTag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodingVersion(t *testing.T) {
|
|
||||||
testData := []struct {
|
|
||||||
decoded string
|
|
||||||
encoded string
|
|
||||||
}{
|
|
||||||
{"1.0.0", "v-789c32d433d03300040000ffff02ce00ee"},
|
|
||||||
{"latest", "v-789cca492c492d2e01040000ffff08cc028e"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, data := range testData {
|
|
||||||
e := encodeVersion(data.decoded)
|
|
||||||
|
|
||||||
if e[0] != data.encoded {
|
|
||||||
t.Fatalf("Expected %s got %s", data.encoded, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, ok := decodeVersion(e)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Unexpected %t for %s", ok, data.encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d != data.decoded {
|
|
||||||
t.Fatalf("Expected %s got %s", data.decoded, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, ok = decodeVersion([]string{data.encoded})
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Unexpected %t for %s", ok, data.encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d != data.decoded {
|
|
||||||
t.Fatalf("Expected %s got %s", data.decoded, d)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded, err := decode(encoded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if decoded.Version != d.Version {
|
||||||
|
t.Fatalf("Expected version %s got %s", d.Version, decoded.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(decoded.Endpoints) != len(d.Endpoints) {
|
||||||
|
t.Fatalf("Expected %d endpoints, got %d", len(d.Endpoints), len(decoded.Endpoints))
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range d.Metadata {
|
||||||
|
if val := decoded.Metadata[k]; val != v {
|
||||||
|
t.Fatalf("Expected %s=%s got %s=%s", k, v, k, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
package mdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/zlib"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encode(txt *mdnsTxt) ([]string, error) {
|
|
||||||
b, err := json.Marshal(txt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
defer buf.Reset()
|
|
||||||
|
|
||||||
w := zlib.NewWriter(&buf)
|
|
||||||
if _, err := w.Write(b); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
encoded := hex.EncodeToString(buf.Bytes())
|
|
||||||
|
|
||||||
// individual txt limit
|
|
||||||
if len(encoded) <= 255 {
|
|
||||||
return []string{encoded}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// split encoded string
|
|
||||||
var record []string
|
|
||||||
|
|
||||||
for len(encoded) > 255 {
|
|
||||||
record = append(record, encoded[:255])
|
|
||||||
encoded = encoded[255:]
|
|
||||||
}
|
|
||||||
|
|
||||||
record = append(record, encoded)
|
|
||||||
|
|
||||||
return record, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decode(record []string) (*mdnsTxt, error) {
|
|
||||||
encoded := strings.Join(record, "")
|
|
||||||
|
|
||||||
hr, err := hex.DecodeString(encoded)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
br := bytes.NewReader(hr)
|
|
||||||
zr, err := zlib.NewReader(br)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
rbuf, err := ioutil.ReadAll(zr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var txt *mdnsTxt
|
|
||||||
|
|
||||||
if err := json.Unmarshal(rbuf, &txt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return txt, nil
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package mdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/micro/go-micro/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncoding(t *testing.T) {
|
|
||||||
testData := []*mdnsTxt{
|
|
||||||
&mdnsTxt{
|
|
||||||
Version: "1.0.0",
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
},
|
|
||||||
Endpoints: []*registry.Endpoint{
|
|
||||||
®istry.Endpoint{
|
|
||||||
Name: "endpoint1",
|
|
||||||
Request: ®istry.Value{
|
|
||||||
Name: "request",
|
|
||||||
Type: "request",
|
|
||||||
},
|
|
||||||
Response: ®istry.Value{
|
|
||||||
Name: "response",
|
|
||||||
Type: "response",
|
|
||||||
},
|
|
||||||
Metadata: map[string]string{
|
|
||||||
"foo1": "bar1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, d := range testData {
|
|
||||||
encoded, err := encode(d)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, txt := range encoded {
|
|
||||||
if len(txt) > 255 {
|
|
||||||
t.Fatalf("One of parts for txt is %d characters", len(txt))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := decode(encoded)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if decoded.Version != d.Version {
|
|
||||||
t.Fatalf("Expected version %s got %s", d.Version, decoded.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decoded.Endpoints) != len(d.Endpoints) {
|
|
||||||
t.Fatalf("Expected %d endpoints, got %d", len(d.Endpoints), len(decoded.Endpoints))
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range d.Metadata {
|
|
||||||
if val := decoded.Metadata[k]; val != v {
|
|
||||||
t.Fatalf("Expected %s=%s got %s=%s", k, v, k, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,339 +1,11 @@
|
|||||||
// Package mdns is a multicast dns registry
|
// Package mdns provides a multicast dns registry
|
||||||
package mdns
|
package mdns
|
||||||
|
|
||||||
/*
|
|
||||||
MDNS is a multicast dns registry for service discovery
|
|
||||||
This creates a zero dependency system which is great
|
|
||||||
where multicast dns is available. This usually depends
|
|
||||||
on the ability to leverage udp and multicast/broadcast.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/micro/go-micro/registry"
|
"github.com/micro/go-micro/registry"
|
||||||
"github.com/micro/mdns"
|
|
||||||
hash "github.com/mitchellh/hashstructure"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type mdnsTxt struct {
|
// NewRegistry returns a new mdns registry
|
||||||
Service string
|
|
||||||
Version string
|
|
||||||
Endpoints []*registry.Endpoint
|
|
||||||
Metadata map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type mdnsEntry struct {
|
|
||||||
hash uint64
|
|
||||||
id string
|
|
||||||
node *mdns.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
type mdnsRegistry struct {
|
|
||||||
opts registry.Options
|
|
||||||
|
|
||||||
sync.Mutex
|
|
||||||
services map[string][]*mdnsEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRegistry(opts ...registry.Option) registry.Registry {
|
|
||||||
options := registry.Options{
|
|
||||||
Timeout: time.Millisecond * 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
return &mdnsRegistry{
|
|
||||||
opts: options,
|
|
||||||
services: make(map[string][]*mdnsEntry),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) Init(opts ...registry.Option) error {
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&m.opts)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) Options() registry.Options {
|
|
||||||
return m.opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) Register(service *registry.Service, opts ...registry.RegisterOption) error {
|
|
||||||
m.Lock()
|
|
||||||
defer m.Unlock()
|
|
||||||
|
|
||||||
entries, ok := m.services[service.Name]
|
|
||||||
// first entry, create wildcard used for list queries
|
|
||||||
if !ok {
|
|
||||||
s, err := mdns.NewMDNSService(
|
|
||||||
service.Name,
|
|
||||||
"_services",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
9999,
|
|
||||||
[]net.IP{net.ParseIP("0.0.0.0")},
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
srv, err := mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{s}})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// append the wildcard entry
|
|
||||||
entries = append(entries, &mdnsEntry{id: "*", node: srv})
|
|
||||||
}
|
|
||||||
|
|
||||||
var gerr error
|
|
||||||
|
|
||||||
for _, node := range service.Nodes {
|
|
||||||
// create hash of service; uint64
|
|
||||||
h, err := hash.Hash(node, nil)
|
|
||||||
if err != nil {
|
|
||||||
gerr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var seen bool
|
|
||||||
var e *mdnsEntry
|
|
||||||
|
|
||||||
for _, entry := range entries {
|
|
||||||
if node.Id == entry.id {
|
|
||||||
seen = true
|
|
||||||
e = entry
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// already registered, continue
|
|
||||||
if seen && e.hash == h {
|
|
||||||
continue
|
|
||||||
// hash doesn't match, shutdown
|
|
||||||
} else if seen {
|
|
||||||
e.node.Shutdown()
|
|
||||||
// doesn't exist
|
|
||||||
} else {
|
|
||||||
e = &mdnsEntry{hash: h}
|
|
||||||
}
|
|
||||||
|
|
||||||
txt, err := encode(&mdnsTxt{
|
|
||||||
Service: service.Name,
|
|
||||||
Version: service.Version,
|
|
||||||
Endpoints: service.Endpoints,
|
|
||||||
Metadata: node.Metadata,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
gerr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// we got here, new node
|
|
||||||
s, err := mdns.NewMDNSService(
|
|
||||||
node.Id,
|
|
||||||
service.Name,
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
node.Port,
|
|
||||||
[]net.IP{net.ParseIP(node.Address)},
|
|
||||||
txt,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
gerr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
srv, err := mdns.NewServer(&mdns.Config{Zone: s})
|
|
||||||
if err != nil {
|
|
||||||
gerr = err
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
e.id = node.Id
|
|
||||||
e.node = srv
|
|
||||||
entries = append(entries, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// save
|
|
||||||
m.services[service.Name] = entries
|
|
||||||
|
|
||||||
return gerr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) Deregister(service *registry.Service) error {
|
|
||||||
m.Lock()
|
|
||||||
defer m.Unlock()
|
|
||||||
|
|
||||||
var newEntries []*mdnsEntry
|
|
||||||
|
|
||||||
// loop existing entries, check if any match, shutdown those that do
|
|
||||||
for _, entry := range m.services[service.Name] {
|
|
||||||
var remove bool
|
|
||||||
|
|
||||||
for _, node := range service.Nodes {
|
|
||||||
if node.Id == entry.id {
|
|
||||||
entry.node.Shutdown()
|
|
||||||
remove = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep it?
|
|
||||||
if !remove {
|
|
||||||
newEntries = append(newEntries, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// last entry is the wildcard for list queries. Remove it.
|
|
||||||
if len(newEntries) == 1 && newEntries[0].id == "*" {
|
|
||||||
newEntries[0].node.Shutdown()
|
|
||||||
delete(m.services, service.Name)
|
|
||||||
} else {
|
|
||||||
m.services[service.Name] = newEntries
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) GetService(service string) ([]*registry.Service, error) {
|
|
||||||
p := mdns.DefaultParams(service)
|
|
||||||
p.Timeout = m.opts.Timeout
|
|
||||||
entryCh := make(chan *mdns.ServiceEntry, 10)
|
|
||||||
p.Entries = entryCh
|
|
||||||
|
|
||||||
exit := make(chan bool)
|
|
||||||
defer close(exit)
|
|
||||||
|
|
||||||
serviceMap := make(map[string]*registry.Service)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e := <-entryCh:
|
|
||||||
// list record so skip
|
|
||||||
if p.Service == "_services" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.TTL == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
txt, err := decode(e.InfoFields)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if txt.Service != service {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
s, ok := serviceMap[txt.Version]
|
|
||||||
if !ok {
|
|
||||||
s = ®istry.Service{
|
|
||||||
Name: txt.Service,
|
|
||||||
Version: txt.Version,
|
|
||||||
Endpoints: txt.Endpoints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Nodes = append(s.Nodes, ®istry.Node{
|
|
||||||
Id: strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."),
|
|
||||||
Address: e.AddrV4.String(),
|
|
||||||
Port: e.Port,
|
|
||||||
Metadata: txt.Metadata,
|
|
||||||
})
|
|
||||||
|
|
||||||
serviceMap[txt.Version] = s
|
|
||||||
case <-exit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := mdns.Query(p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create list and return
|
|
||||||
var services []*registry.Service
|
|
||||||
|
|
||||||
for _, service := range serviceMap {
|
|
||||||
services = append(services, service)
|
|
||||||
}
|
|
||||||
|
|
||||||
return services, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) ListServices() ([]*registry.Service, error) {
|
|
||||||
p := mdns.DefaultParams("_services")
|
|
||||||
p.Timeout = m.opts.Timeout
|
|
||||||
entryCh := make(chan *mdns.ServiceEntry, 10)
|
|
||||||
p.Entries = entryCh
|
|
||||||
|
|
||||||
exit := make(chan bool)
|
|
||||||
defer close(exit)
|
|
||||||
|
|
||||||
serviceMap := make(map[string]bool)
|
|
||||||
var services []*registry.Service
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e := <-entryCh:
|
|
||||||
if e.TTL == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name := strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+".")
|
|
||||||
if !serviceMap[name] {
|
|
||||||
serviceMap[name] = true
|
|
||||||
services = append(services, ®istry.Service{Name: name})
|
|
||||||
}
|
|
||||||
case <-exit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := mdns.Query(p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return services, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
|
||||||
var wo registry.WatchOptions
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&wo)
|
|
||||||
}
|
|
||||||
|
|
||||||
md := &mdnsWatcher{
|
|
||||||
wo: wo,
|
|
||||||
ch: make(chan *mdns.ServiceEntry, 32),
|
|
||||||
exit: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := mdns.Listen(md.ch, md.exit); err != nil {
|
|
||||||
md.Stop()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return md, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mdnsRegistry) String() string {
|
|
||||||
return "mdns"
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||||
return newRegistry(opts...)
|
return registry.NewRegistry(opts...)
|
||||||
}
|
}
|
||||||
|
332
registry/mdns_registry.go
Normal file
332
registry/mdns_registry.go
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// Package mdns is a multicast dns registry
|
||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/micro/mdns"
|
||||||
|
hash "github.com/mitchellh/hashstructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mdnsTxt struct {
|
||||||
|
Service string
|
||||||
|
Version string
|
||||||
|
Endpoints []*Endpoint
|
||||||
|
Metadata map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type mdnsEntry struct {
|
||||||
|
hash uint64
|
||||||
|
id string
|
||||||
|
node *mdns.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
type mdnsRegistry struct {
|
||||||
|
opts Options
|
||||||
|
|
||||||
|
sync.Mutex
|
||||||
|
services map[string][]*mdnsEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRegistry(opts ...Option) Registry {
|
||||||
|
options := Options{
|
||||||
|
Timeout: time.Millisecond * 100,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mdnsRegistry{
|
||||||
|
opts: options,
|
||||||
|
services: make(map[string][]*mdnsEntry),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) Init(opts ...Option) error {
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&m.opts)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) Options() Options {
|
||||||
|
return m.opts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) Register(service *Service, opts ...RegisterOption) error {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
entries, ok := m.services[service.Name]
|
||||||
|
// first entry, create wildcard used for list queries
|
||||||
|
if !ok {
|
||||||
|
s, err := mdns.NewMDNSService(
|
||||||
|
service.Name,
|
||||||
|
"_services",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
9999,
|
||||||
|
[]net.IP{net.ParseIP("0.0.0.0")},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv, err := mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{s}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// append the wildcard entry
|
||||||
|
entries = append(entries, &mdnsEntry{id: "*", node: srv})
|
||||||
|
}
|
||||||
|
|
||||||
|
var gerr error
|
||||||
|
|
||||||
|
for _, node := range service.Nodes {
|
||||||
|
// create hash of service; uint64
|
||||||
|
h, err := hash.Hash(node, nil)
|
||||||
|
if err != nil {
|
||||||
|
gerr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var seen bool
|
||||||
|
var e *mdnsEntry
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
if node.Id == entry.id {
|
||||||
|
seen = true
|
||||||
|
e = entry
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// already registered, continue
|
||||||
|
if seen && e.hash == h {
|
||||||
|
continue
|
||||||
|
// hash doesn't match, shutdown
|
||||||
|
} else if seen {
|
||||||
|
e.node.Shutdown()
|
||||||
|
// doesn't exist
|
||||||
|
} else {
|
||||||
|
e = &mdnsEntry{hash: h}
|
||||||
|
}
|
||||||
|
|
||||||
|
txt, err := encode(&mdnsTxt{
|
||||||
|
Service: service.Name,
|
||||||
|
Version: service.Version,
|
||||||
|
Endpoints: service.Endpoints,
|
||||||
|
Metadata: node.Metadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
gerr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// we got here, new node
|
||||||
|
s, err := mdns.NewMDNSService(
|
||||||
|
node.Id,
|
||||||
|
service.Name,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
node.Port,
|
||||||
|
[]net.IP{net.ParseIP(node.Address)},
|
||||||
|
txt,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
gerr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
srv, err := mdns.NewServer(&mdns.Config{Zone: s})
|
||||||
|
if err != nil {
|
||||||
|
gerr = err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e.id = node.Id
|
||||||
|
e.node = srv
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save
|
||||||
|
m.services[service.Name] = entries
|
||||||
|
|
||||||
|
return gerr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) Deregister(service *Service) error {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
|
||||||
|
var newEntries []*mdnsEntry
|
||||||
|
|
||||||
|
// loop existing entries, check if any match, shutdown those that do
|
||||||
|
for _, entry := range m.services[service.Name] {
|
||||||
|
var remove bool
|
||||||
|
|
||||||
|
for _, node := range service.Nodes {
|
||||||
|
if node.Id == entry.id {
|
||||||
|
entry.node.Shutdown()
|
||||||
|
remove = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep it?
|
||||||
|
if !remove {
|
||||||
|
newEntries = append(newEntries, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last entry is the wildcard for list queries. Remove it.
|
||||||
|
if len(newEntries) == 1 && newEntries[0].id == "*" {
|
||||||
|
newEntries[0].node.Shutdown()
|
||||||
|
delete(m.services, service.Name)
|
||||||
|
} else {
|
||||||
|
m.services[service.Name] = newEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) GetService(service string) ([]*Service, error) {
|
||||||
|
p := mdns.DefaultParams(service)
|
||||||
|
p.Timeout = m.opts.Timeout
|
||||||
|
entryCh := make(chan *mdns.ServiceEntry, 10)
|
||||||
|
p.Entries = entryCh
|
||||||
|
|
||||||
|
exit := make(chan bool)
|
||||||
|
defer close(exit)
|
||||||
|
|
||||||
|
serviceMap := make(map[string]*Service)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case e := <-entryCh:
|
||||||
|
// list record so skip
|
||||||
|
if p.Service == "_services" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.TTL == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
txt, err := decode(e.InfoFields)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if txt.Service != service {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s, ok := serviceMap[txt.Version]
|
||||||
|
if !ok {
|
||||||
|
s = &Service{
|
||||||
|
Name: txt.Service,
|
||||||
|
Version: txt.Version,
|
||||||
|
Endpoints: txt.Endpoints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Nodes = append(s.Nodes, &Node{
|
||||||
|
Id: strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."),
|
||||||
|
Address: e.AddrV4.String(),
|
||||||
|
Port: e.Port,
|
||||||
|
Metadata: txt.Metadata,
|
||||||
|
})
|
||||||
|
|
||||||
|
serviceMap[txt.Version] = s
|
||||||
|
case <-exit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := mdns.Query(p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create list and return
|
||||||
|
var services []*Service
|
||||||
|
|
||||||
|
for _, service := range serviceMap {
|
||||||
|
services = append(services, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) ListServices() ([]*Service, error) {
|
||||||
|
p := mdns.DefaultParams("_services")
|
||||||
|
p.Timeout = m.opts.Timeout
|
||||||
|
entryCh := make(chan *mdns.ServiceEntry, 10)
|
||||||
|
p.Entries = entryCh
|
||||||
|
|
||||||
|
exit := make(chan bool)
|
||||||
|
defer close(exit)
|
||||||
|
|
||||||
|
serviceMap := make(map[string]bool)
|
||||||
|
var services []*Service
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case e := <-entryCh:
|
||||||
|
if e.TTL == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+".")
|
||||||
|
if !serviceMap[name] {
|
||||||
|
serviceMap[name] = true
|
||||||
|
services = append(services, &Service{Name: name})
|
||||||
|
}
|
||||||
|
case <-exit:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := mdns.Query(p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return services, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) Watch(opts ...WatchOption) (Watcher, error) {
|
||||||
|
var wo WatchOptions
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&wo)
|
||||||
|
}
|
||||||
|
|
||||||
|
md := &mdnsWatcher{
|
||||||
|
wo: wo,
|
||||||
|
ch: make(chan *mdns.ServiceEntry, 32),
|
||||||
|
exit: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := mdns.Listen(md.ch, md.exit); err != nil {
|
||||||
|
md.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return md, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mdnsRegistry) String() string {
|
||||||
|
return "mdns"
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRegistry returns a new default registry which is mdns
|
||||||
|
func NewRegistry(opts ...Option) Registry {
|
||||||
|
return newRegistry(opts...)
|
||||||
|
}
|
@ -1,19 +1,17 @@
|
|||||||
package mdns
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/micro/go-micro/registry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMDNS(t *testing.T) {
|
func TestMDNS(t *testing.T) {
|
||||||
testData := []*registry.Service{
|
testData := []*Service{
|
||||||
®istry.Service{
|
&Service{
|
||||||
Name: "test1",
|
Name: "test1",
|
||||||
Version: "1.0.1",
|
Version: "1.0.1",
|
||||||
Nodes: []*registry.Node{
|
Nodes: []*Node{
|
||||||
®istry.Node{
|
&Node{
|
||||||
Id: "test1-1",
|
Id: "test1-1",
|
||||||
Address: "10.0.0.1",
|
Address: "10.0.0.1",
|
||||||
Port: 10001,
|
Port: 10001,
|
||||||
@ -23,11 +21,11 @@ func TestMDNS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
®istry.Service{
|
&Service{
|
||||||
Name: "test2",
|
Name: "test2",
|
||||||
Version: "1.0.2",
|
Version: "1.0.2",
|
||||||
Nodes: []*registry.Node{
|
Nodes: []*Node{
|
||||||
®istry.Node{
|
&Node{
|
||||||
Id: "test2-1",
|
Id: "test2-1",
|
||||||
Address: "10.0.0.2",
|
Address: "10.0.0.2",
|
||||||
Port: 10002,
|
Port: 10002,
|
||||||
@ -37,11 +35,11 @@ func TestMDNS(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
®istry.Service{
|
&Service{
|
||||||
Name: "test3",
|
Name: "test3",
|
||||||
Version: "1.0.3",
|
Version: "1.0.3",
|
||||||
Nodes: []*registry.Node{
|
Nodes: []*Node{
|
||||||
®istry.Node{
|
&Node{
|
||||||
Id: "test3-1",
|
Id: "test3-1",
|
||||||
Address: "10.0.0.3",
|
Address: "10.0.0.3",
|
||||||
Port: 10003,
|
Port: 10003,
|
@ -1,20 +1,19 @@
|
|||||||
package mdns
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/micro/go-micro/registry"
|
|
||||||
"github.com/micro/mdns"
|
"github.com/micro/mdns"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mdnsWatcher struct {
|
type mdnsWatcher struct {
|
||||||
wo registry.WatchOptions
|
wo WatchOptions
|
||||||
ch chan *mdns.ServiceEntry
|
ch chan *mdns.ServiceEntry
|
||||||
exit chan struct{}
|
exit chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mdnsWatcher) Next() (*registry.Result, error) {
|
func (m *mdnsWatcher) Next() (*Result, error) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case e := <-m.ch:
|
case e := <-m.ch:
|
||||||
@ -41,7 +40,7 @@ func (m *mdnsWatcher) Next() (*registry.Result, error) {
|
|||||||
action = "create"
|
action = "create"
|
||||||
}
|
}
|
||||||
|
|
||||||
service := ®istry.Service{
|
service := &Service{
|
||||||
Name: txt.Service,
|
Name: txt.Service,
|
||||||
Version: txt.Version,
|
Version: txt.Version,
|
||||||
Endpoints: txt.Endpoints,
|
Endpoints: txt.Endpoints,
|
||||||
@ -52,14 +51,14 @@ func (m *mdnsWatcher) Next() (*registry.Result, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
service.Nodes = append(service.Nodes, ®istry.Node{
|
service.Nodes = append(service.Nodes, &Node{
|
||||||
Id: strings.TrimSuffix(e.Name, "."+service.Name+".local."),
|
Id: strings.TrimSuffix(e.Name, "."+service.Name+".local."),
|
||||||
Address: e.AddrV4.String(),
|
Address: e.AddrV4.String(),
|
||||||
Port: e.Port,
|
Port: e.Port,
|
||||||
Metadata: txt.Metadata,
|
Metadata: txt.Metadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
return ®istry.Result{
|
return &Result{
|
||||||
Action: action,
|
Action: action,
|
||||||
Service: service,
|
Service: service,
|
||||||
}, nil
|
}, nil
|
@ -26,7 +26,7 @@ type RegisterOption func(*RegisterOptions)
|
|||||||
type WatchOption func(*WatchOptions)
|
type WatchOption func(*WatchOptions)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultRegistry = newConsulRegistry()
|
DefaultRegistry = NewRegistry()
|
||||||
|
|
||||||
// Not found error when GetService is called
|
// Not found error when GetService is called
|
||||||
ErrNotFound = errors.New("not found")
|
ErrNotFound = errors.New("not found")
|
||||||
@ -34,10 +34,6 @@ var (
|
|||||||
ErrWatcherStopped = errors.New("watcher stopped")
|
ErrWatcherStopped = errors.New("watcher stopped")
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRegistry(opts ...Option) Registry {
|
|
||||||
return newConsulRegistry(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register a service node. Additionally supply options such as TTL.
|
// Register a service node. Additionally supply options such as TTL.
|
||||||
func Register(s *Service, opts ...RegisterOption) error {
|
func Register(s *Service, opts ...RegisterOption) error {
|
||||||
return DefaultRegistry.Register(s, opts...)
|
return DefaultRegistry.Register(s, opts...)
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
package mdns
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/micro/go-micro/registry"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWatcher(t *testing.T) {
|
func TestWatcher(t *testing.T) {
|
||||||
testData := []*registry.Service{
|
testData := []*Service{
|
||||||
®istry.Service{
|
&Service{
|
||||||
Name: "test1",
|
Name: "test1",
|
||||||
Version: "1.0.1",
|
Version: "1.0.1",
|
||||||
Nodes: []*registry.Node{
|
Nodes: []*Node{
|
||||||
®istry.Node{
|
&Node{
|
||||||
Id: "test1-1",
|
Id: "test1-1",
|
||||||
Address: "10.0.0.1",
|
Address: "10.0.0.1",
|
||||||
Port: 10001,
|
Port: 10001,
|
||||||
@ -22,11 +20,11 @@ func TestWatcher(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
®istry.Service{
|
&Service{
|
||||||
Name: "test2",
|
Name: "test2",
|
||||||
Version: "1.0.2",
|
Version: "1.0.2",
|
||||||
Nodes: []*registry.Node{
|
Nodes: []*Node{
|
||||||
®istry.Node{
|
&Node{
|
||||||
Id: "test2-1",
|
Id: "test2-1",
|
||||||
Address: "10.0.0.2",
|
Address: "10.0.0.2",
|
||||||
Port: 10002,
|
Port: 10002,
|
||||||
@ -36,11 +34,11 @@ func TestWatcher(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
®istry.Service{
|
&Service{
|
||||||
Name: "test3",
|
Name: "test3",
|
||||||
Version: "1.0.3",
|
Version: "1.0.3",
|
||||||
Nodes: []*registry.Node{
|
Nodes: []*Node{
|
||||||
®istry.Node{
|
&Node{
|
||||||
Id: "test3-1",
|
Id: "test3-1",
|
||||||
Address: "10.0.0.3",
|
Address: "10.0.0.3",
|
||||||
Port: 10003,
|
Port: 10003,
|
||||||
@ -52,7 +50,7 @@ func TestWatcher(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testFn := func(service, s *registry.Service) {
|
testFn := func(service, s *Service) {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
t.Fatalf("Expected one result for %s got nil", service.Name)
|
t.Fatalf("Expected one result for %s got nil", service.Name)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user