mirror of
				https://github.com/go-micro/go-micro.git
				synced 2025-10-30 23:27:41 +02:00 
			
		
		
		
	Set MDNS as default registry
This commit is contained in:
		| @@ -202,8 +202,8 @@ var ( | ||||
| 	defaultClient    = "rpc" | ||||
| 	defaultServer    = "rpc" | ||||
| 	defaultBroker    = "http" | ||||
| 	defaultRegistry  = "consul" | ||||
| 	defaultSelector  = "cache" | ||||
| 	defaultRegistry  = "mdns" | ||||
| 	defaultSelector  = "registry" | ||||
| 	defaultTransport = "http" | ||||
| ) | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,387 @@ | ||||
| // Package consul provides a consul based registry and is the default discovery system | ||||
| package consul | ||||
|  | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"runtime" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	consul "github.com/hashicorp/consul/api" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	hash "github.com/mitchellh/hashstructure" | ||||
| ) | ||||
|  | ||||
| // NewRegistry returns a new consul registry | ||||
| func NewRegistry(opts ...registry.Option) registry.Registry { | ||||
| 	return registry.NewRegistry(opts...) | ||||
| type consulRegistry struct { | ||||
| 	Address string | ||||
| 	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 ( | ||||
| 	"bytes" | ||||
| @@ -10,6 +10,7 @@ import ( | ||||
| 	"time" | ||||
| 
 | ||||
| 	consul "github.com/hashicorp/consul/api" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
| 
 | ||||
| type mockRegistry struct { | ||||
| @@ -56,7 +57,7 @@ func newConsulTestRegistry(r *mockRegistry) (*consulRegistry, func()) { | ||||
| 	return &consulRegistry{ | ||||
| 			Address:     cfg.Address, | ||||
| 			Client:      cl, | ||||
| 			opts:        Options{}, | ||||
| 			opts:        registry.Options{}, | ||||
| 			register:    make(map[string]uint64), | ||||
| 			lastChecked: make(map[string]time.Time), | ||||
| 			queryOptions: &consul.QueryOptions{ | ||||
| @@ -1,4 +1,4 @@ | ||||
| package registry | ||||
| package consul | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| @@ -6,23 +6,24 @@ import ( | ||||
| 
 | ||||
| 	"github.com/hashicorp/consul/api" | ||||
| 	"github.com/hashicorp/consul/watch" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
| 
 | ||||
| type consulWatcher struct { | ||||
| 	r        *consulRegistry | ||||
| 	wo       WatchOptions | ||||
| 	wo       registry.WatchOptions | ||||
| 	wp       *watch.Plan | ||||
| 	watchers map[string]*watch.Plan | ||||
| 
 | ||||
| 	next chan *Result | ||||
| 	next chan *registry.Result | ||||
| 	exit chan bool | ||||
| 
 | ||||
| 	sync.RWMutex | ||||
| 	services map[string][]*Service | ||||
| 	services map[string][]*registry.Service | ||||
| } | ||||
| 
 | ||||
| func newConsulWatcher(cr *consulRegistry, opts ...WatchOption) (Watcher, error) { | ||||
| 	var wo WatchOptions | ||||
| func newConsulWatcher(cr *consulRegistry, opts ...registry.WatchOption) (registry.Watcher, error) { | ||||
| 	var wo registry.WatchOptions | ||||
| 	for _, o := range opts { | ||||
| 		o(&wo) | ||||
| 	} | ||||
| @@ -31,9 +32,9 @@ func newConsulWatcher(cr *consulRegistry, opts ...WatchOption) (Watcher, error) | ||||
| 		r:        cr, | ||||
| 		wo:       wo, | ||||
| 		exit:     make(chan bool), | ||||
| 		next:     make(chan *Result, 10), | ||||
| 		next:     make(chan *registry.Result, 10), | ||||
| 		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"}) | ||||
| @@ -54,7 +55,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	serviceMap := map[string]*Service{} | ||||
| 	serviceMap := map[string]*registry.Service{} | ||||
| 	serviceName := "" | ||||
| 
 | ||||
| 	for _, e := range entries { | ||||
| @@ -75,7 +76,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 
 | ||||
| 		svc, ok := serviceMap[key] | ||||
| 		if !ok { | ||||
| 			svc = &Service{ | ||||
| 			svc = ®istry.Service{ | ||||
| 				Endpoints: decodeEndpoints(e.Service.Tags), | ||||
| 				Name:      e.Service.Service, | ||||
| 				Version:   version, | ||||
| @@ -98,7 +99,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 			continue | ||||
| 		} | ||||
| 
 | ||||
| 		svc.Nodes = append(svc.Nodes, &Node{ | ||||
| 		svc.Nodes = append(svc.Nodes, ®istry.Node{ | ||||
| 			Id:       id, | ||||
| 			Address:  address, | ||||
| 			Port:     e.Service.Port, | ||||
| @@ -108,13 +109,13 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 
 | ||||
| 	cw.RLock() | ||||
| 	// make a copy | ||||
| 	rservices := make(map[string][]*Service) | ||||
| 	rservices := make(map[string][]*registry.Service) | ||||
| 	for k, v := range cw.services { | ||||
| 		rservices[k] = v | ||||
| 	} | ||||
| 	cw.RUnlock() | ||||
| 
 | ||||
| 	var newServices []*Service | ||||
| 	var newServices []*registry.Service | ||||
| 
 | ||||
| 	// serviceMap is the new set of services keyed by name+version | ||||
| 	for _, newService := range serviceMap { | ||||
| @@ -125,7 +126,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 		oldServices, ok := rservices[serviceName] | ||||
| 		if !ok { | ||||
| 			// 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 | ||||
| 		} | ||||
| 
 | ||||
| @@ -142,7 +143,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 			// yes? then it's an update | ||||
| 			action = "update" | ||||
| 
 | ||||
| 			var nodes []*Node | ||||
| 			var nodes []*registry.Node | ||||
| 			// check the old nodes to see if they've been deleted | ||||
| 			for _, oldNode := range oldService.Nodes { | ||||
| 				var seen bool | ||||
| @@ -163,11 +164,11 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 			if len(nodes) > 0 { | ||||
| 				delService := oldService | ||||
| 				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 | ||||
| @@ -175,7 +176,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) { | ||||
| 		// old version does not exist in new version map | ||||
| 		// kill it with fire! | ||||
| 		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 | ||||
| 			go wp.Run(cw.r.Address) | ||||
| 			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() | ||||
| 	// make a copy | ||||
| 	rservices := make(map[string][]*Service) | ||||
| 	rservices := make(map[string][]*registry.Service) | ||||
| 	for k, v := range cw.services { | ||||
| 		rservices[k] = v | ||||
| 	} | ||||
| @@ -235,12 +236,12 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) { | ||||
| 		if _, ok := services[service]; !ok { | ||||
| 			w.Stop() | ||||
| 			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 { | ||||
| 	case <-cw.exit: | ||||
| 		return nil, errors.New("result chan closed") | ||||
| @@ -1,9 +1,10 @@ | ||||
| package registry | ||||
| package consul | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/hashicorp/consul/api" | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
| 
 | ||||
| func TestHealthyServiceHandler(t *testing.T) { | ||||
| @@ -58,8 +59,8 @@ func TestUnhealthyNodeServiceHandler(t *testing.T) { | ||||
| func newWatcher() *consulWatcher { | ||||
| 	return &consulWatcher{ | ||||
| 		exit:     make(chan bool), | ||||
| 		next:     make(chan *Result, 10), | ||||
| 		services: make(map[string][]*Service), | ||||
| 		next:     make(chan *registry.Result, 10), | ||||
| 		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/json" | ||||
| 	"io/ioutil" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| func encode(buf []byte) string { | ||||
| 	var b bytes.Buffer | ||||
| 	defer b.Reset() | ||||
| func encode(txt *mdnsTxt) ([]string, error) { | ||||
| 	b, err := json.Marshal(txt) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	w := zlib.NewWriter(&b) | ||||
| 	if _, err := w.Write(buf); err != nil { | ||||
| 		return "" | ||||
| 	var buf bytes.Buffer | ||||
| 	defer buf.Reset() | ||||
|  | ||||
| 	w := zlib.NewWriter(&buf) | ||||
| 	if _, err := w.Write(b); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	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 { | ||||
| 	hr, err := hex.DecodeString(d) | ||||
| func decode(record []string) (*mdnsTxt, error) { | ||||
| 	encoded := strings.Join(record, "") | ||||
|  | ||||
| 	hr, err := hex.DecodeString(encoded) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	br := bytes.NewReader(hr) | ||||
| 	zr, err := zlib.NewReader(br) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	rbuf, err := ioutil.ReadAll(zr) | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return rbuf | ||||
| } | ||||
| 	var txt *mdnsTxt | ||||
|  | ||||
| func encodeEndpoints(en []*Endpoint) []string { | ||||
| 	var tags []string | ||||
| 	for _, e := range en { | ||||
| 		if b, err := json.Marshal(e); err == nil { | ||||
| 			tags = append(tags, "e-"+encode(b)) | ||||
| 		} | ||||
| 	if err := json.Unmarshal(rbuf, &txt); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	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 | ||||
|  | ||||
| 	return txt, nil | ||||
| } | ||||
|   | ||||
| @@ -1,146 +1,65 @@ | ||||
| package registry | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestEncodingEndpoints(t *testing.T) { | ||||
| 	eps := []*Endpoint{ | ||||
| 		&Endpoint{ | ||||
| 			Name: "endpoint1", | ||||
| 			Request: &Value{ | ||||
| 				Name: "request", | ||||
| 				Type: "request", | ||||
| 			}, | ||||
| 			Response: &Value{ | ||||
| 				Name: "response", | ||||
| 				Type: "response", | ||||
| 			}, | ||||
| func TestEncoding(t *testing.T) { | ||||
| 	testData := []*mdnsTxt{ | ||||
| 		&mdnsTxt{ | ||||
| 			Version: "1.0.0", | ||||
| 			Metadata: map[string]string{ | ||||
| 				"foo1": "bar1", | ||||
| 				"foo": "bar", | ||||
| 			}, | ||||
| 		}, | ||||
| 		&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", | ||||
| 			Endpoints: []*Endpoint{ | ||||
| 				&Endpoint{ | ||||
| 					Name: "endpoint1", | ||||
| 					Request: &Value{ | ||||
| 						Name: "request", | ||||
| 						Type: "request", | ||||
| 					}, | ||||
| 					Response: &Value{ | ||||
| 						Name: "response", | ||||
| 						Type: "response", | ||||
| 					}, | ||||
| 					Metadata: map[string]string{ | ||||
| 						"foo1": "bar1", | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	testEp := func(ep *Endpoint, enc string) { | ||||
| 		// encode endpoint | ||||
| 		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) | ||||
| 	for _, d := range testData { | ||||
| 		encoded, err := encode(d) | ||||
| 		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) | ||||
| 		} | ||||
| 		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,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 | ||||
|  | ||||
| /* | ||||
| 	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 ( | ||||
| 	"net" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/mdns" | ||||
| 	hash "github.com/mitchellh/hashstructure" | ||||
| ) | ||||
|  | ||||
| type mdnsTxt struct { | ||||
| 	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" | ||||
| } | ||||
|  | ||||
| // NewRegistry returns a new mdns 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 ( | ||||
| 	"testing" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
| 
 | ||||
| func TestMDNS(t *testing.T) { | ||||
| 	testData := []*registry.Service{ | ||||
| 		®istry.Service{ | ||||
| 	testData := []*Service{ | ||||
| 		&Service{ | ||||
| 			Name:    "test1", | ||||
| 			Version: "1.0.1", | ||||
| 			Nodes: []*registry.Node{ | ||||
| 				®istry.Node{ | ||||
| 			Nodes: []*Node{ | ||||
| 				&Node{ | ||||
| 					Id:      "test1-1", | ||||
| 					Address: "10.0.0.1", | ||||
| 					Port:    10001, | ||||
| @@ -23,11 +21,11 @@ func TestMDNS(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		®istry.Service{ | ||||
| 		&Service{ | ||||
| 			Name:    "test2", | ||||
| 			Version: "1.0.2", | ||||
| 			Nodes: []*registry.Node{ | ||||
| 				®istry.Node{ | ||||
| 			Nodes: []*Node{ | ||||
| 				&Node{ | ||||
| 					Id:      "test2-1", | ||||
| 					Address: "10.0.0.2", | ||||
| 					Port:    10002, | ||||
| @@ -37,11 +35,11 @@ func TestMDNS(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		®istry.Service{ | ||||
| 		&Service{ | ||||
| 			Name:    "test3", | ||||
| 			Version: "1.0.3", | ||||
| 			Nodes: []*registry.Node{ | ||||
| 				®istry.Node{ | ||||
| 			Nodes: []*Node{ | ||||
| 				&Node{ | ||||
| 					Id:      "test3-1", | ||||
| 					Address: "10.0.0.3", | ||||
| 					Port:    10003, | ||||
| @@ -1,20 +1,19 @@ | ||||
| package mdns | ||||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| 	"github.com/micro/mdns" | ||||
| ) | ||||
| 
 | ||||
| type mdnsWatcher struct { | ||||
| 	wo   registry.WatchOptions | ||||
| 	wo   WatchOptions | ||||
| 	ch   chan *mdns.ServiceEntry | ||||
| 	exit chan struct{} | ||||
| } | ||||
| 
 | ||||
| func (m *mdnsWatcher) Next() (*registry.Result, error) { | ||||
| func (m *mdnsWatcher) Next() (*Result, error) { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case e := <-m.ch: | ||||
| @@ -41,7 +40,7 @@ func (m *mdnsWatcher) Next() (*registry.Result, error) { | ||||
| 				action = "create" | ||||
| 			} | ||||
| 
 | ||||
| 			service := ®istry.Service{ | ||||
| 			service := &Service{ | ||||
| 				Name:      txt.Service, | ||||
| 				Version:   txt.Version, | ||||
| 				Endpoints: txt.Endpoints, | ||||
| @@ -52,14 +51,14 @@ func (m *mdnsWatcher) Next() (*registry.Result, error) { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			service.Nodes = append(service.Nodes, ®istry.Node{ | ||||
| 			service.Nodes = append(service.Nodes, &Node{ | ||||
| 				Id:       strings.TrimSuffix(e.Name, "."+service.Name+".local."), | ||||
| 				Address:  e.AddrV4.String(), | ||||
| 				Port:     e.Port, | ||||
| 				Metadata: txt.Metadata, | ||||
| 			}) | ||||
| 
 | ||||
| 			return ®istry.Result{ | ||||
| 			return &Result{ | ||||
| 				Action:  action, | ||||
| 				Service: service, | ||||
| 			}, nil | ||||
| @@ -26,7 +26,7 @@ type RegisterOption func(*RegisterOptions) | ||||
| type WatchOption func(*WatchOptions) | ||||
|  | ||||
| var ( | ||||
| 	DefaultRegistry = newConsulRegistry() | ||||
| 	DefaultRegistry = NewRegistry() | ||||
|  | ||||
| 	// Not found error when GetService is called | ||||
| 	ErrNotFound = errors.New("not found") | ||||
| @@ -34,10 +34,6 @@ var ( | ||||
| 	ErrWatcherStopped = errors.New("watcher stopped") | ||||
| ) | ||||
|  | ||||
| func NewRegistry(opts ...Option) Registry { | ||||
| 	return newConsulRegistry(opts...) | ||||
| } | ||||
|  | ||||
| // Register a service node. Additionally supply options such as TTL. | ||||
| func Register(s *Service, opts ...RegisterOption) error { | ||||
| 	return DefaultRegistry.Register(s, opts...) | ||||
|   | ||||
| @@ -1,18 +1,16 @@ | ||||
| package mdns | ||||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/micro/go-micro/registry" | ||||
| ) | ||||
| 
 | ||||
| func TestWatcher(t *testing.T) { | ||||
| 	testData := []*registry.Service{ | ||||
| 		®istry.Service{ | ||||
| 	testData := []*Service{ | ||||
| 		&Service{ | ||||
| 			Name:    "test1", | ||||
| 			Version: "1.0.1", | ||||
| 			Nodes: []*registry.Node{ | ||||
| 				®istry.Node{ | ||||
| 			Nodes: []*Node{ | ||||
| 				&Node{ | ||||
| 					Id:      "test1-1", | ||||
| 					Address: "10.0.0.1", | ||||
| 					Port:    10001, | ||||
| @@ -22,11 +20,11 @@ func TestWatcher(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		®istry.Service{ | ||||
| 		&Service{ | ||||
| 			Name:    "test2", | ||||
| 			Version: "1.0.2", | ||||
| 			Nodes: []*registry.Node{ | ||||
| 				®istry.Node{ | ||||
| 			Nodes: []*Node{ | ||||
| 				&Node{ | ||||
| 					Id:      "test2-1", | ||||
| 					Address: "10.0.0.2", | ||||
| 					Port:    10002, | ||||
| @@ -36,11 +34,11 @@ func TestWatcher(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		®istry.Service{ | ||||
| 		&Service{ | ||||
| 			Name:    "test3", | ||||
| 			Version: "1.0.3", | ||||
| 			Nodes: []*registry.Node{ | ||||
| 				®istry.Node{ | ||||
| 			Nodes: []*Node{ | ||||
| 				&Node{ | ||||
| 					Id:      "test3-1", | ||||
| 					Address: "10.0.0.3", | ||||
| 					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 { | ||||
| 			t.Fatalf("Expected one result for %s got nil", service.Name) | ||||
| 
 | ||||
		Reference in New Issue
	
	Block a user