v3 refactor (#1868)
* Move to v3 Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
6
registry/cache/cache.go
vendored
6
registry/cache/cache.go
vendored
@@ -7,9 +7,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
util "github.com/micro/go-micro/v2/util/registry"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
util "github.com/micro/go-micro/v3/util/registry"
|
||||
)
|
||||
|
||||
// Cache is the registry cache interface
|
||||
|
@@ -17,8 +17,8 @@ import (
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
hash "github.com/mitchellh/hashstructure"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@@ -3,7 +3,7 @@ package etcd
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
type etcdWatcher struct {
|
||||
|
@@ -1,23 +1,769 @@
|
||||
// Package mdns provides a multicast dns registry
|
||||
package mdns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
"github.com/micro/go-micro/v3/util/mdns"
|
||||
)
|
||||
|
||||
// NewRegistry returns a new mdns registry
|
||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||
return registry.NewRegistry(opts...)
|
||||
const (
|
||||
// every service is written to the global domain so * domain queries work, e.g.
|
||||
// calling mdns.List(registry.ListDomain("*")) will list the services across all
|
||||
// domains
|
||||
globalDomain = "global"
|
||||
)
|
||||
|
||||
type mdnsTxt struct {
|
||||
Service string
|
||||
Version string
|
||||
Endpoints []*registry.Endpoint
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Domain sets the mdnsDomain
|
||||
func Domain(d string) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
type mdnsEntry struct {
|
||||
id string
|
||||
node *mdns.Server
|
||||
}
|
||||
|
||||
// services are a key/value map, with the service name as a key and the value being a
|
||||
// slice of mdns entries, representing the nodes with a single _services entry to be
|
||||
// used for listing
|
||||
type services map[string][]*mdnsEntry
|
||||
|
||||
// mdsRegistry is a multicast dns registry
|
||||
type mdnsRegistry struct {
|
||||
opts registry.Options
|
||||
|
||||
// the top level domains, these can be overriden using options
|
||||
defaultDomain string
|
||||
globalDomain string
|
||||
|
||||
sync.Mutex
|
||||
domains map[string]services
|
||||
|
||||
mtx sync.RWMutex
|
||||
|
||||
// watchers
|
||||
watchers map[string]*mdnsWatcher
|
||||
|
||||
// listener
|
||||
listener chan *mdns.ServiceEntry
|
||||
}
|
||||
|
||||
type mdnsWatcher struct {
|
||||
id string
|
||||
wo registry.WatchOptions
|
||||
ch chan *mdns.ServiceEntry
|
||||
exit chan struct{}
|
||||
// the mdns domain
|
||||
domain string
|
||||
// the registry
|
||||
registry *mdnsRegistry
|
||||
}
|
||||
|
||||
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)
|
||||
defer func() {
|
||||
if closeErr := w.Close(); closeErr != nil {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("[mdns] registry close encoding writer err: %v", closeErr)
|
||||
}
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, "mdns.domain", d)
|
||||
}()
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func newRegistry(opts ...registry.Option) registry.Registry {
|
||||
options := registry.Options{
|
||||
Context: context.Background(),
|
||||
Timeout: time.Millisecond * 100,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// set the domain
|
||||
defaultDomain := registry.DefaultDomain
|
||||
if d, ok := options.Context.Value("mdns.domain").(string); ok {
|
||||
defaultDomain = d
|
||||
}
|
||||
|
||||
return &mdnsRegistry{
|
||||
defaultDomain: defaultDomain,
|
||||
globalDomain: globalDomain,
|
||||
opts: options,
|
||||
domains: make(map[string]services),
|
||||
watchers: make(map[string]*mdnsWatcher),
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// createServiceMDNSEntry will create a new wildcard mdns entry for the service in the
|
||||
// given domain. This wildcard mdns entry is used when listing services.
|
||||
func createServiceMDNSEntry(name, domain string) (*mdnsEntry, error) {
|
||||
ip := net.ParseIP("0.0.0.0")
|
||||
|
||||
s, err := mdns.NewMDNSService(name, "_services", domain+".", "", 9999, []net.IP{ip}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv, err := mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{MDNSService: s}, LocalhostChecking: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mdnsEntry{id: "*", node: srv}, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) getMdnsEntries(domain, serviceName string) ([]*mdnsEntry, error) {
|
||||
entries, ok := m.domains[domain][serviceName]
|
||||
if ok {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// create the wildcard entry used for list queries in this domain
|
||||
entry, err := createServiceMDNSEntry(serviceName, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*mdnsEntry{entry}, nil
|
||||
}
|
||||
|
||||
func registerService(service *registry.Service, entries []*mdnsEntry, options registry.RegisterOptions) ([]*mdnsEntry, error) {
|
||||
var lastError error
|
||||
for _, node := range service.Nodes {
|
||||
var seen bool
|
||||
|
||||
for _, entry := range entries {
|
||||
if node.Id == entry.id {
|
||||
seen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// this node has already been registered, continue
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
|
||||
txt, err := encode(&mdnsTxt{
|
||||
Service: service.Name,
|
||||
Version: service.Version,
|
||||
Endpoints: service.Endpoints,
|
||||
Metadata: node.Metadata,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
host, pt, err := net.SplitHostPort(node.Address)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
port, _ := strconv.Atoi(pt)
|
||||
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("[mdns] registry create new service with ip: %s for: %s", net.ParseIP(host).String(), host)
|
||||
}
|
||||
// we got here, new node
|
||||
s, err := mdns.NewMDNSService(
|
||||
node.Id,
|
||||
service.Name,
|
||||
options.Domain+".",
|
||||
"",
|
||||
port,
|
||||
[]net.IP{net.ParseIP(host)},
|
||||
txt,
|
||||
)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
srv, err := mdns.NewServer(&mdns.Config{Zone: s, LocalhostChecking: true})
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
entries = append(entries, &mdnsEntry{id: node.Id, node: srv})
|
||||
}
|
||||
|
||||
return entries, lastError
|
||||
}
|
||||
|
||||
func createGlobalDomainService(service *registry.Service, options registry.RegisterOptions) *registry.Service {
|
||||
srv := *service
|
||||
srv.Nodes = nil
|
||||
|
||||
for _, n := range service.Nodes {
|
||||
node := n
|
||||
|
||||
// set the original domain in node metadata
|
||||
if node.Metadata == nil {
|
||||
node.Metadata = map[string]string{"domain": options.Domain}
|
||||
} else {
|
||||
node.Metadata["domain"] = options.Domain
|
||||
}
|
||||
|
||||
srv.Nodes = append(srv.Nodes, node)
|
||||
}
|
||||
|
||||
return &srv
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Register(service *registry.Service, opts ...registry.RegisterOption) error {
|
||||
m.Lock()
|
||||
|
||||
// parse the options
|
||||
var options registry.RegisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
|
||||
// create the domain in the memory store if it doesn't yet exist
|
||||
if _, ok := m.domains[options.Domain]; !ok {
|
||||
m.domains[options.Domain] = make(services)
|
||||
}
|
||||
|
||||
entries, err := m.getMdnsEntries(options.Domain, service.Name)
|
||||
if err != nil {
|
||||
m.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
entries, gerr := registerService(service, entries, options)
|
||||
|
||||
// save the mdns entry
|
||||
m.domains[options.Domain][service.Name] = entries
|
||||
m.Unlock()
|
||||
|
||||
// register in the global Domain so it can be queried as one
|
||||
if options.Domain != m.globalDomain {
|
||||
srv := createGlobalDomainService(service, options)
|
||||
if err := m.Register(srv, append(opts, registry.RegisterDomain(m.globalDomain))...); err != nil {
|
||||
gerr = err
|
||||
}
|
||||
}
|
||||
|
||||
return gerr
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Deregister(service *registry.Service, opts ...registry.DeregisterOption) error {
|
||||
// parse the options
|
||||
var options registry.DeregisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
|
||||
// register in the global Domain
|
||||
var err error
|
||||
if options.Domain != m.globalDomain {
|
||||
defer func() {
|
||||
err = m.Deregister(service, append(opts, registry.DeregisterDomain(m.globalDomain))...)
|
||||
}()
|
||||
}
|
||||
|
||||
// we want to unlock before we call deregister on the global domain, so it's important this unlock
|
||||
// is applied after the defer m.Deregister is called above
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// the service wasn't registered, we can safely exist
|
||||
if _, ok := m.domains[options.Domain]; !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// loop existing entries, check if any match, shutdown those that do
|
||||
var newEntries []*mdnsEntry
|
||||
for _, entry := range m.domains[options.Domain][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)
|
||||
}
|
||||
}
|
||||
|
||||
// we have no new entries, we can exit
|
||||
if len(newEntries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we have more than one entry remaining, we can exit
|
||||
if len(newEntries) > 1 {
|
||||
m.domains[options.Domain][service.Name] = newEntries
|
||||
return err
|
||||
}
|
||||
|
||||
// our remaining entry is not a wildcard, we can exit
|
||||
if len(newEntries) == 1 && newEntries[0].id != "*" {
|
||||
m.domains[options.Domain][service.Name] = newEntries
|
||||
return err
|
||||
}
|
||||
|
||||
// last entry is the wildcard for list queries. Remove it.
|
||||
newEntries[0].node.Shutdown()
|
||||
delete(m.domains[options.Domain], service.Name)
|
||||
|
||||
// check to see if we can delete the domain entry
|
||||
if len(m.domains[options.Domain]) == 0 {
|
||||
delete(m.domains, options.Domain)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) GetService(service string, opts ...registry.GetOption) ([]*registry.Service, error) {
|
||||
// parse the options
|
||||
var options registry.GetOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
if options.Domain == registry.WildcardDomain {
|
||||
options.Domain = m.globalDomain
|
||||
}
|
||||
|
||||
serviceMap := make(map[string]*registry.Service)
|
||||
entries := make(chan *mdns.ServiceEntry, 10)
|
||||
done := make(chan bool)
|
||||
|
||||
p := mdns.DefaultParams(service)
|
||||
// set context with timeout
|
||||
var cancel context.CancelFunc
|
||||
p.Context, cancel = context.WithTimeout(context.Background(), m.opts.Timeout)
|
||||
defer cancel()
|
||||
// set entries channel
|
||||
p.Entries = entries
|
||||
// set the domain
|
||||
p.Domain = options.Domain
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-entries:
|
||||
// list record so skip
|
||||
if e.Name == "_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,
|
||||
}
|
||||
}
|
||||
addr := ""
|
||||
// prefer ipv4 addrs
|
||||
if len(e.AddrV4) > 0 {
|
||||
addr = e.AddrV4.String()
|
||||
// else use ipv6
|
||||
} else if len(e.AddrV6) > 0 {
|
||||
addr = "[" + e.AddrV6.String() + "]"
|
||||
} else {
|
||||
if logger.V(logger.InfoLevel, logger.DefaultLogger) {
|
||||
logger.Infof("[mdns]: invalid endpoint received: %v", e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
s.Nodes = append(s.Nodes, ®istry.Node{
|
||||
Id: strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."),
|
||||
Address: fmt.Sprintf("%s:%d", addr, e.Port),
|
||||
Metadata: txt.Metadata,
|
||||
})
|
||||
|
||||
serviceMap[txt.Version] = s
|
||||
case <-p.Context.Done():
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// execute the query
|
||||
if err := mdns.Query(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait for completion
|
||||
<-done
|
||||
|
||||
// create list and return
|
||||
services := make([]*registry.Service, 0, len(serviceMap))
|
||||
|
||||
for _, service := range serviceMap {
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) ListServices(opts ...registry.ListOption) ([]*registry.Service, error) {
|
||||
// parse the options
|
||||
var options registry.ListOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
if options.Domain == registry.WildcardDomain {
|
||||
options.Domain = m.globalDomain
|
||||
}
|
||||
|
||||
serviceMap := make(map[string]bool)
|
||||
entries := make(chan *mdns.ServiceEntry, 10)
|
||||
done := make(chan bool)
|
||||
|
||||
p := mdns.DefaultParams("_services")
|
||||
// set context with timeout
|
||||
var cancel context.CancelFunc
|
||||
p.Context, cancel = context.WithTimeout(context.Background(), m.opts.Timeout)
|
||||
defer cancel()
|
||||
// set entries channel
|
||||
p.Entries = entries
|
||||
// set domain
|
||||
p.Domain = options.Domain
|
||||
|
||||
var services []*registry.Service
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-entries:
|
||||
if e.TTL == 0 {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(e.Name, p.Domain+".") {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+".")
|
||||
if !serviceMap[name] {
|
||||
serviceMap[name] = true
|
||||
services = append(services, ®istry.Service{Name: name})
|
||||
}
|
||||
case <-p.Context.Done():
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// execute query
|
||||
if err := mdns.Query(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait till done
|
||||
<-done
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||
var wo registry.WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&wo)
|
||||
}
|
||||
if len(wo.Domain) == 0 {
|
||||
wo.Domain = m.defaultDomain
|
||||
}
|
||||
if wo.Domain == registry.WildcardDomain {
|
||||
wo.Domain = m.globalDomain
|
||||
}
|
||||
|
||||
md := &mdnsWatcher{
|
||||
id: uuid.New().String(),
|
||||
wo: wo,
|
||||
ch: make(chan *mdns.ServiceEntry, 32),
|
||||
exit: make(chan struct{}),
|
||||
domain: wo.Domain,
|
||||
registry: m,
|
||||
}
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// save the watcher
|
||||
m.watchers[md.id] = md
|
||||
|
||||
// check of the listener exists
|
||||
if m.listener != nil {
|
||||
return md, nil
|
||||
}
|
||||
|
||||
// start the listener
|
||||
go func() {
|
||||
// go to infinity
|
||||
for {
|
||||
m.mtx.Lock()
|
||||
|
||||
// just return if there are no watchers
|
||||
if len(m.watchers) == 0 {
|
||||
m.listener = nil
|
||||
m.mtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// check existing listener
|
||||
if m.listener != nil {
|
||||
m.mtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// reset the listener
|
||||
exit := make(chan struct{})
|
||||
ch := make(chan *mdns.ServiceEntry, 32)
|
||||
m.listener = ch
|
||||
|
||||
m.mtx.Unlock()
|
||||
|
||||
// send messages to the watchers
|
||||
go func() {
|
||||
send := func(w *mdnsWatcher, e *mdns.ServiceEntry) {
|
||||
select {
|
||||
case w.ch <- e:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-exit:
|
||||
return
|
||||
case e, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m.mtx.RLock()
|
||||
// send service entry to all watchers
|
||||
for _, w := range m.watchers {
|
||||
send(w, e)
|
||||
}
|
||||
m.mtx.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// start listening, blocking call
|
||||
mdns.Listen(ch, exit)
|
||||
|
||||
// mdns.Listen has unblocked
|
||||
// kill the saved listener
|
||||
m.mtx.Lock()
|
||||
m.listener = nil
|
||||
close(ch)
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return md, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) String() string {
|
||||
return "mdns"
|
||||
}
|
||||
|
||||
func (m *mdnsWatcher) Next() (*registry.Result, error) {
|
||||
for {
|
||||
select {
|
||||
case e := <-m.ch:
|
||||
txt, err := decode(e.InfoFields)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(txt.Service) == 0 || len(txt.Version) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter watch options
|
||||
// wo.Service: Only keep services we care about
|
||||
if len(m.wo.Service) > 0 && txt.Service != m.wo.Service {
|
||||
continue
|
||||
}
|
||||
var action string
|
||||
if e.TTL == 0 {
|
||||
action = "delete"
|
||||
} else {
|
||||
action = "create"
|
||||
}
|
||||
|
||||
service := ®istry.Service{
|
||||
Name: txt.Service,
|
||||
Version: txt.Version,
|
||||
Endpoints: txt.Endpoints,
|
||||
Metadata: txt.Metadata,
|
||||
}
|
||||
|
||||
// skip anything without the domain we care about
|
||||
suffix := fmt.Sprintf(".%s.%s.", service.Name, m.domain)
|
||||
if !strings.HasSuffix(e.Name, suffix) {
|
||||
continue
|
||||
}
|
||||
|
||||
var addr string
|
||||
if len(e.AddrV4) > 0 {
|
||||
addr = e.AddrV4.String()
|
||||
} else if len(e.AddrV6) > 0 {
|
||||
addr = "[" + e.AddrV6.String() + "]"
|
||||
} else {
|
||||
addr = e.Addr.String()
|
||||
}
|
||||
|
||||
service.Nodes = append(service.Nodes, ®istry.Node{
|
||||
Id: strings.TrimSuffix(e.Name, suffix),
|
||||
Address: fmt.Sprintf("%s:%d", addr, e.Port),
|
||||
Metadata: txt.Metadata,
|
||||
})
|
||||
|
||||
return ®istry.Result{
|
||||
Action: action,
|
||||
Service: service,
|
||||
}, nil
|
||||
case <-m.exit:
|
||||
return nil, registry.ErrWatcherStopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mdnsWatcher) Stop() {
|
||||
select {
|
||||
case <-m.exit:
|
||||
return
|
||||
default:
|
||||
close(m.exit)
|
||||
// remove self from the registry
|
||||
m.registry.mtx.Lock()
|
||||
delete(m.registry.watchers, m.id)
|
||||
m.registry.mtx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegistry returns a new default registry which is mdns
|
||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||
return newRegistry(opts...)
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
package registry
|
||||
package mdns
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
func TestMDNS(t *testing.T) {
|
||||
@@ -12,11 +14,11 @@ func TestMDNS(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testData := []*Service{
|
||||
testData := []*registry.Service{
|
||||
{
|
||||
Name: "test1",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*Node{
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "test1-1",
|
||||
Address: "10.0.0.1:10001",
|
||||
@@ -29,7 +31,7 @@ func TestMDNS(t *testing.T) {
|
||||
{
|
||||
Name: "test2",
|
||||
Version: "1.0.2",
|
||||
Nodes: []*Node{
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "test2-1",
|
||||
Address: "10.0.0.2:10002",
|
||||
@@ -42,7 +44,7 @@ func TestMDNS(t *testing.T) {
|
||||
{
|
||||
Name: "test3",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*Node{
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "test3-1",
|
||||
Address: "10.0.0.3:10003",
|
||||
@@ -56,10 +58,10 @@ func TestMDNS(t *testing.T) {
|
||||
|
||||
travis := os.Getenv("TRAVIS")
|
||||
|
||||
var opts []Option
|
||||
var opts []registry.Option
|
||||
|
||||
if travis == "true" {
|
||||
opts = append(opts, Timeout(time.Millisecond*100))
|
||||
opts = append(opts, registry.Timeout(time.Millisecond*100))
|
||||
}
|
||||
|
||||
// new registry
|
||||
@@ -144,14 +146,14 @@ func TestEncoding(t *testing.T) {
|
||||
Metadata: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Endpoints: []*Endpoint{
|
||||
Endpoints: []*registry.Endpoint{
|
||||
{
|
||||
Name: "endpoint1",
|
||||
Request: &Value{
|
||||
Request: ®istry.Value{
|
||||
Name: "request",
|
||||
Type: "request",
|
||||
},
|
||||
Response: &Value{
|
||||
Response: ®istry.Value{
|
||||
Name: "response",
|
||||
Type: "response",
|
||||
},
|
||||
@@ -202,11 +204,11 @@ func TestWatcher(t *testing.T) {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testData := []*Service{
|
||||
testData := []*registry.Service{
|
||||
{
|
||||
Name: "test1",
|
||||
Version: "1.0.1",
|
||||
Nodes: []*Node{
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "test1-1",
|
||||
Address: "10.0.0.1:10001",
|
||||
@@ -219,7 +221,7 @@ func TestWatcher(t *testing.T) {
|
||||
{
|
||||
Name: "test2",
|
||||
Version: "1.0.2",
|
||||
Nodes: []*Node{
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "test2-1",
|
||||
Address: "10.0.0.2:10002",
|
||||
@@ -232,7 +234,7 @@ func TestWatcher(t *testing.T) {
|
||||
{
|
||||
Name: "test3",
|
||||
Version: "1.0.3",
|
||||
Nodes: []*Node{
|
||||
Nodes: []*registry.Node{
|
||||
{
|
||||
Id: "test3-1",
|
||||
Address: "10.0.0.3:10003",
|
||||
@@ -244,7 +246,7 @@ func TestWatcher(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
testFn := func(service, s *Service) {
|
||||
testFn := func(service, s *registry.Service) {
|
||||
if s == nil {
|
||||
t.Fatalf("Expected one result for %s got nil", service.Name)
|
||||
|
||||
@@ -275,10 +277,10 @@ func TestWatcher(t *testing.T) {
|
||||
|
||||
travis := os.Getenv("TRAVIS")
|
||||
|
||||
var opts []Option
|
||||
var opts []registry.Option
|
||||
|
||||
if travis == "true" {
|
||||
opts = append(opts, Timeout(time.Millisecond*100))
|
||||
opts = append(opts, registry.Timeout(time.Millisecond*100))
|
||||
}
|
||||
|
||||
// new registry
|
18
registry/mdns/options.go
Normal file
18
registry/mdns/options.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Package mdns provides a multicast dns registry
|
||||
package mdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
// Domain sets the mdnsDomain
|
||||
func Domain(d string) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
o.Context = context.WithValue(o.Context, "mdns.domain", d)
|
||||
}
|
||||
}
|
@@ -1,768 +0,0 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/util/mdns"
|
||||
)
|
||||
|
||||
const (
|
||||
// every service is written to the global domain so * domain queries work, e.g.
|
||||
// calling mdns.List(registry.ListDomain("*")) will list the services across all
|
||||
// domains
|
||||
globalDomain = "global"
|
||||
)
|
||||
|
||||
type mdnsTxt struct {
|
||||
Service string
|
||||
Version string
|
||||
Endpoints []*Endpoint
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
type mdnsEntry struct {
|
||||
id string
|
||||
node *mdns.Server
|
||||
}
|
||||
|
||||
// services are a key/value map, with the service name as a key and the value being a
|
||||
// slice of mdns entries, representing the nodes with a single _services entry to be
|
||||
// used for listing
|
||||
type services map[string][]*mdnsEntry
|
||||
|
||||
// mdsRegistry is a multicast dns registry
|
||||
type mdnsRegistry struct {
|
||||
opts Options
|
||||
|
||||
// the top level domains, these can be overriden using options
|
||||
defaultDomain string
|
||||
globalDomain string
|
||||
|
||||
sync.Mutex
|
||||
domains map[string]services
|
||||
|
||||
mtx sync.RWMutex
|
||||
|
||||
// watchers
|
||||
watchers map[string]*mdnsWatcher
|
||||
|
||||
// listener
|
||||
listener chan *mdns.ServiceEntry
|
||||
}
|
||||
|
||||
type mdnsWatcher struct {
|
||||
id string
|
||||
wo WatchOptions
|
||||
ch chan *mdns.ServiceEntry
|
||||
exit chan struct{}
|
||||
// the mdns domain
|
||||
domain string
|
||||
// the registry
|
||||
registry *mdnsRegistry
|
||||
}
|
||||
|
||||
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)
|
||||
defer func() {
|
||||
if closeErr := w.Close(); closeErr != nil {
|
||||
if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
|
||||
logger.Errorf("[mdns] registry close encoding writer err: %v", closeErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func newRegistry(opts ...Option) Registry {
|
||||
options := Options{
|
||||
Context: context.Background(),
|
||||
Timeout: time.Millisecond * 100,
|
||||
}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// set the domain
|
||||
defaultDomain := DefaultDomain
|
||||
if d, ok := options.Context.Value("mdns.domain").(string); ok {
|
||||
defaultDomain = d
|
||||
}
|
||||
|
||||
return &mdnsRegistry{
|
||||
defaultDomain: defaultDomain,
|
||||
globalDomain: globalDomain,
|
||||
opts: options,
|
||||
domains: make(map[string]services),
|
||||
watchers: make(map[string]*mdnsWatcher),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Init(opts ...Option) error {
|
||||
for _, o := range opts {
|
||||
o(&m.opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Options() Options {
|
||||
return m.opts
|
||||
}
|
||||
|
||||
// createServiceMDNSEntry will create a new wildcard mdns entry for the service in the
|
||||
// given domain. This wildcard mdns entry is used when listing services.
|
||||
func createServiceMDNSEntry(name, domain string) (*mdnsEntry, error) {
|
||||
ip := net.ParseIP("0.0.0.0")
|
||||
|
||||
s, err := mdns.NewMDNSService(name, "_services", domain+".", "", 9999, []net.IP{ip}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv, err := mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{MDNSService: s}, LocalhostChecking: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &mdnsEntry{id: "*", node: srv}, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) getMdnsEntries(domain, serviceName string) ([]*mdnsEntry, error) {
|
||||
entries, ok := m.domains[domain][serviceName]
|
||||
if ok {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// create the wildcard entry used for list queries in this domain
|
||||
entry, err := createServiceMDNSEntry(serviceName, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []*mdnsEntry{entry}, nil
|
||||
}
|
||||
|
||||
func registerService(service *Service, entries []*mdnsEntry, options RegisterOptions) ([]*mdnsEntry, error) {
|
||||
var lastError error
|
||||
for _, node := range service.Nodes {
|
||||
var seen bool
|
||||
|
||||
for _, entry := range entries {
|
||||
if node.Id == entry.id {
|
||||
seen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// this node has already been registered, continue
|
||||
if seen {
|
||||
continue
|
||||
}
|
||||
|
||||
txt, err := encode(&mdnsTxt{
|
||||
Service: service.Name,
|
||||
Version: service.Version,
|
||||
Endpoints: service.Endpoints,
|
||||
Metadata: node.Metadata,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
host, pt, err := net.SplitHostPort(node.Address)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
port, _ := strconv.Atoi(pt)
|
||||
|
||||
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
||||
logger.Debugf("[mdns] registry create new service with ip: %s for: %s", net.ParseIP(host).String(), host)
|
||||
}
|
||||
// we got here, new node
|
||||
s, err := mdns.NewMDNSService(
|
||||
node.Id,
|
||||
service.Name,
|
||||
options.Domain+".",
|
||||
"",
|
||||
port,
|
||||
[]net.IP{net.ParseIP(host)},
|
||||
txt,
|
||||
)
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
srv, err := mdns.NewServer(&mdns.Config{Zone: s, LocalhostChecking: true})
|
||||
if err != nil {
|
||||
lastError = err
|
||||
continue
|
||||
}
|
||||
|
||||
entries = append(entries, &mdnsEntry{id: node.Id, node: srv})
|
||||
}
|
||||
|
||||
return entries, lastError
|
||||
}
|
||||
|
||||
func createGlobalDomainService(service *Service, options RegisterOptions) *Service {
|
||||
srv := *service
|
||||
srv.Nodes = nil
|
||||
|
||||
for _, n := range service.Nodes {
|
||||
node := n
|
||||
|
||||
// set the original domain in node metadata
|
||||
if node.Metadata == nil {
|
||||
node.Metadata = map[string]string{"domain": options.Domain}
|
||||
} else {
|
||||
node.Metadata["domain"] = options.Domain
|
||||
}
|
||||
|
||||
srv.Nodes = append(srv.Nodes, node)
|
||||
}
|
||||
|
||||
return &srv
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Register(service *Service, opts ...RegisterOption) error {
|
||||
m.Lock()
|
||||
|
||||
// parse the options
|
||||
var options RegisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
|
||||
// create the domain in the memory store if it doesn't yet exist
|
||||
if _, ok := m.domains[options.Domain]; !ok {
|
||||
m.domains[options.Domain] = make(services)
|
||||
}
|
||||
|
||||
entries, err := m.getMdnsEntries(options.Domain, service.Name)
|
||||
if err != nil {
|
||||
m.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
entries, gerr := registerService(service, entries, options)
|
||||
|
||||
// save the mdns entry
|
||||
m.domains[options.Domain][service.Name] = entries
|
||||
m.Unlock()
|
||||
|
||||
// register in the global Domain so it can be queried as one
|
||||
if options.Domain != m.globalDomain {
|
||||
srv := createGlobalDomainService(service, options)
|
||||
if err := m.Register(srv, append(opts, RegisterDomain(m.globalDomain))...); err != nil {
|
||||
gerr = err
|
||||
}
|
||||
}
|
||||
|
||||
return gerr
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Deregister(service *Service, opts ...DeregisterOption) error {
|
||||
// parse the options
|
||||
var options DeregisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
|
||||
// register in the global Domain
|
||||
var err error
|
||||
if options.Domain != m.globalDomain {
|
||||
defer func() {
|
||||
err = m.Deregister(service, append(opts, DeregisterDomain(m.globalDomain))...)
|
||||
}()
|
||||
}
|
||||
|
||||
// we want to unlock before we call deregister on the global domain, so it's important this unlock
|
||||
// is applied after the defer m.Deregister is called above
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// the service wasn't registered, we can safely exist
|
||||
if _, ok := m.domains[options.Domain]; !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
// loop existing entries, check if any match, shutdown those that do
|
||||
var newEntries []*mdnsEntry
|
||||
for _, entry := range m.domains[options.Domain][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)
|
||||
}
|
||||
}
|
||||
|
||||
// we have no new entries, we can exit
|
||||
if len(newEntries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we have more than one entry remaining, we can exit
|
||||
if len(newEntries) > 1 {
|
||||
m.domains[options.Domain][service.Name] = newEntries
|
||||
return err
|
||||
}
|
||||
|
||||
// our remaining entry is not a wildcard, we can exit
|
||||
if len(newEntries) == 1 && newEntries[0].id != "*" {
|
||||
m.domains[options.Domain][service.Name] = newEntries
|
||||
return err
|
||||
}
|
||||
|
||||
// last entry is the wildcard for list queries. Remove it.
|
||||
newEntries[0].node.Shutdown()
|
||||
delete(m.domains[options.Domain], service.Name)
|
||||
|
||||
// check to see if we can delete the domain entry
|
||||
if len(m.domains[options.Domain]) == 0 {
|
||||
delete(m.domains, options.Domain)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) GetService(service string, opts ...GetOption) ([]*Service, error) {
|
||||
// parse the options
|
||||
var options GetOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
if options.Domain == WildcardDomain {
|
||||
options.Domain = m.globalDomain
|
||||
}
|
||||
|
||||
serviceMap := make(map[string]*Service)
|
||||
entries := make(chan *mdns.ServiceEntry, 10)
|
||||
done := make(chan bool)
|
||||
|
||||
p := mdns.DefaultParams(service)
|
||||
// set context with timeout
|
||||
var cancel context.CancelFunc
|
||||
p.Context, cancel = context.WithTimeout(context.Background(), m.opts.Timeout)
|
||||
defer cancel()
|
||||
// set entries channel
|
||||
p.Entries = entries
|
||||
// set the domain
|
||||
p.Domain = options.Domain
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-entries:
|
||||
// list record so skip
|
||||
if e.Name == "_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,
|
||||
}
|
||||
}
|
||||
addr := ""
|
||||
// prefer ipv4 addrs
|
||||
if len(e.AddrV4) > 0 {
|
||||
addr = e.AddrV4.String()
|
||||
// else use ipv6
|
||||
} else if len(e.AddrV6) > 0 {
|
||||
addr = "[" + e.AddrV6.String() + "]"
|
||||
} else {
|
||||
if logger.V(logger.InfoLevel, logger.DefaultLogger) {
|
||||
logger.Infof("[mdns]: invalid endpoint received: %v", e)
|
||||
}
|
||||
continue
|
||||
}
|
||||
s.Nodes = append(s.Nodes, &Node{
|
||||
Id: strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."),
|
||||
Address: fmt.Sprintf("%s:%d", addr, e.Port),
|
||||
Metadata: txt.Metadata,
|
||||
})
|
||||
|
||||
serviceMap[txt.Version] = s
|
||||
case <-p.Context.Done():
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// execute the query
|
||||
if err := mdns.Query(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait for completion
|
||||
<-done
|
||||
|
||||
// create list and return
|
||||
services := make([]*Service, 0, len(serviceMap))
|
||||
|
||||
for _, service := range serviceMap {
|
||||
services = append(services, service)
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) ListServices(opts ...ListOption) ([]*Service, error) {
|
||||
// parse the options
|
||||
var options ListOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if len(options.Domain) == 0 {
|
||||
options.Domain = m.defaultDomain
|
||||
}
|
||||
if options.Domain == WildcardDomain {
|
||||
options.Domain = m.globalDomain
|
||||
}
|
||||
|
||||
serviceMap := make(map[string]bool)
|
||||
entries := make(chan *mdns.ServiceEntry, 10)
|
||||
done := make(chan bool)
|
||||
|
||||
p := mdns.DefaultParams("_services")
|
||||
// set context with timeout
|
||||
var cancel context.CancelFunc
|
||||
p.Context, cancel = context.WithTimeout(context.Background(), m.opts.Timeout)
|
||||
defer cancel()
|
||||
// set entries channel
|
||||
p.Entries = entries
|
||||
// set domain
|
||||
p.Domain = options.Domain
|
||||
|
||||
var services []*Service
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-entries:
|
||||
if e.TTL == 0 {
|
||||
continue
|
||||
}
|
||||
if !strings.HasSuffix(e.Name, p.Domain+".") {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+".")
|
||||
if !serviceMap[name] {
|
||||
serviceMap[name] = true
|
||||
services = append(services, &Service{Name: name})
|
||||
}
|
||||
case <-p.Context.Done():
|
||||
close(done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// execute query
|
||||
if err := mdns.Query(p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// wait till done
|
||||
<-done
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) Watch(opts ...WatchOption) (Watcher, error) {
|
||||
var wo WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&wo)
|
||||
}
|
||||
if len(wo.Domain) == 0 {
|
||||
wo.Domain = m.defaultDomain
|
||||
}
|
||||
if wo.Domain == WildcardDomain {
|
||||
wo.Domain = m.globalDomain
|
||||
}
|
||||
|
||||
md := &mdnsWatcher{
|
||||
id: uuid.New().String(),
|
||||
wo: wo,
|
||||
ch: make(chan *mdns.ServiceEntry, 32),
|
||||
exit: make(chan struct{}),
|
||||
domain: wo.Domain,
|
||||
registry: m,
|
||||
}
|
||||
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
// save the watcher
|
||||
m.watchers[md.id] = md
|
||||
|
||||
// check of the listener exists
|
||||
if m.listener != nil {
|
||||
return md, nil
|
||||
}
|
||||
|
||||
// start the listener
|
||||
go func() {
|
||||
// go to infinity
|
||||
for {
|
||||
m.mtx.Lock()
|
||||
|
||||
// just return if there are no watchers
|
||||
if len(m.watchers) == 0 {
|
||||
m.listener = nil
|
||||
m.mtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// check existing listener
|
||||
if m.listener != nil {
|
||||
m.mtx.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// reset the listener
|
||||
exit := make(chan struct{})
|
||||
ch := make(chan *mdns.ServiceEntry, 32)
|
||||
m.listener = ch
|
||||
|
||||
m.mtx.Unlock()
|
||||
|
||||
// send messages to the watchers
|
||||
go func() {
|
||||
send := func(w *mdnsWatcher, e *mdns.ServiceEntry) {
|
||||
select {
|
||||
case w.ch <- e:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-exit:
|
||||
return
|
||||
case e, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m.mtx.RLock()
|
||||
// send service entry to all watchers
|
||||
for _, w := range m.watchers {
|
||||
send(w, e)
|
||||
}
|
||||
m.mtx.RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// start listening, blocking call
|
||||
mdns.Listen(ch, exit)
|
||||
|
||||
// mdns.Listen has unblocked
|
||||
// kill the saved listener
|
||||
m.mtx.Lock()
|
||||
m.listener = nil
|
||||
close(ch)
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return md, nil
|
||||
}
|
||||
|
||||
func (m *mdnsRegistry) String() string {
|
||||
return "mdns"
|
||||
}
|
||||
|
||||
func (m *mdnsWatcher) Next() (*Result, error) {
|
||||
for {
|
||||
select {
|
||||
case e := <-m.ch:
|
||||
txt, err := decode(e.InfoFields)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(txt.Service) == 0 || len(txt.Version) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Filter watch options
|
||||
// wo.Service: Only keep services we care about
|
||||
if len(m.wo.Service) > 0 && txt.Service != m.wo.Service {
|
||||
continue
|
||||
}
|
||||
var action string
|
||||
if e.TTL == 0 {
|
||||
action = "delete"
|
||||
} else {
|
||||
action = "create"
|
||||
}
|
||||
|
||||
service := &Service{
|
||||
Name: txt.Service,
|
||||
Version: txt.Version,
|
||||
Endpoints: txt.Endpoints,
|
||||
Metadata: txt.Metadata,
|
||||
}
|
||||
|
||||
// skip anything without the domain we care about
|
||||
suffix := fmt.Sprintf(".%s.%s.", service.Name, m.domain)
|
||||
if !strings.HasSuffix(e.Name, suffix) {
|
||||
continue
|
||||
}
|
||||
|
||||
var addr string
|
||||
if len(e.AddrV4) > 0 {
|
||||
addr = e.AddrV4.String()
|
||||
} else if len(e.AddrV6) > 0 {
|
||||
addr = "[" + e.AddrV6.String() + "]"
|
||||
} else {
|
||||
addr = e.Addr.String()
|
||||
}
|
||||
|
||||
service.Nodes = append(service.Nodes, &Node{
|
||||
Id: strings.TrimSuffix(e.Name, suffix),
|
||||
Address: fmt.Sprintf("%s:%d", addr, e.Port),
|
||||
Metadata: txt.Metadata,
|
||||
})
|
||||
|
||||
return &Result{
|
||||
Action: action,
|
||||
Service: service,
|
||||
}, nil
|
||||
case <-m.exit:
|
||||
return nil, ErrWatcherStopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mdnsWatcher) Stop() {
|
||||
select {
|
||||
case <-m.exit:
|
||||
return
|
||||
default:
|
||||
close(m.exit)
|
||||
// remove self from the registry
|
||||
m.registry.mtx.Lock()
|
||||
delete(m.registry.watchers, m.id)
|
||||
m.registry.mtx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// NewRegistry returns a new default registry which is mdns
|
||||
func NewRegistry(opts ...Option) Registry {
|
||||
return newRegistry(opts...)
|
||||
}
|
@@ -7,8 +7,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/micro/go-micro/v2/logger"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/logger"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@@ -3,7 +3,7 @@ package memory
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
type servicesKey struct{}
|
||||
|
@@ -3,7 +3,7 @@ package memory
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
func serviceToRecord(s *registry.Service, ttl time.Duration) *record {
|
||||
|
@@ -3,7 +3,7 @@ package memory
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
type Watcher struct {
|
||||
|
@@ -3,7 +3,7 @@ package memory
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
"github.com/micro/go-micro/v3/registry"
|
||||
)
|
||||
|
||||
func TestWatcher(t *testing.T) {
|
||||
|
@@ -13,8 +13,6 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultRegistry = NewRegistry()
|
||||
|
||||
// Not found error when GetService is called
|
||||
ErrNotFound = errors.New("service not found")
|
||||
// Watcher stopped error when watcher is stopped
|
||||
@@ -73,32 +71,3 @@ type DeregisterOption func(*DeregisterOptions)
|
||||
type GetOption func(*GetOptions)
|
||||
|
||||
type ListOption func(*ListOptions)
|
||||
|
||||
// Register a service node. Additionally supply options such as TTL.
|
||||
func Register(s *Service, opts ...RegisterOption) error {
|
||||
return DefaultRegistry.Register(s, opts...)
|
||||
}
|
||||
|
||||
// Deregister a service node
|
||||
func Deregister(s *Service) error {
|
||||
return DefaultRegistry.Deregister(s)
|
||||
}
|
||||
|
||||
// Retrieve a service. A slice is returned since we separate Name/Version.
|
||||
func GetService(name string) ([]*Service, error) {
|
||||
return DefaultRegistry.GetService(name)
|
||||
}
|
||||
|
||||
// List the services. Only returns service names
|
||||
func ListServices() ([]*Service, error) {
|
||||
return DefaultRegistry.ListServices()
|
||||
}
|
||||
|
||||
// Watch returns a watcher which allows you to track updates to the registry.
|
||||
func Watch(opts ...WatchOption) (Watcher, error) {
|
||||
return DefaultRegistry.Watch(opts...)
|
||||
}
|
||||
|
||||
func String() string {
|
||||
return DefaultRegistry.String()
|
||||
}
|
||||
|
@@ -1,21 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
)
|
||||
|
||||
type clientKey struct{}
|
||||
|
||||
// WithClient sets the RPC client
|
||||
func WithClient(c client.Client) registry.Option {
|
||||
return func(o *registry.Options) {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
|
||||
o.Context = context.WithValue(o.Context, clientKey{}, c)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,236 +0,0 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: registry/service/proto/registry.proto
|
||||
|
||||
package go_micro_registry
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/micro/go-micro/v2/api"
|
||||
client "github.com/micro/go-micro/v2/client"
|
||||
server "github.com/micro/go-micro/v2/server"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ api.Endpoint
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Api Endpoints for Registry service
|
||||
|
||||
func NewRegistryEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Registry service
|
||||
|
||||
type RegistryService interface {
|
||||
GetService(ctx context.Context, in *GetRequest, opts ...client.CallOption) (*GetResponse, error)
|
||||
Register(ctx context.Context, in *Service, opts ...client.CallOption) (*EmptyResponse, error)
|
||||
Deregister(ctx context.Context, in *Service, opts ...client.CallOption) (*EmptyResponse, error)
|
||||
ListServices(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error)
|
||||
Watch(ctx context.Context, in *WatchRequest, opts ...client.CallOption) (Registry_WatchService, error)
|
||||
}
|
||||
|
||||
type registryService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewRegistryService(name string, c client.Client) RegistryService {
|
||||
return ®istryService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *registryService) GetService(ctx context.Context, in *GetRequest, opts ...client.CallOption) (*GetResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Registry.GetService", in)
|
||||
out := new(GetResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *registryService) Register(ctx context.Context, in *Service, opts ...client.CallOption) (*EmptyResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Registry.Register", in)
|
||||
out := new(EmptyResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *registryService) Deregister(ctx context.Context, in *Service, opts ...client.CallOption) (*EmptyResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Registry.Deregister", in)
|
||||
out := new(EmptyResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *registryService) ListServices(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Registry.ListServices", in)
|
||||
out := new(ListResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *registryService) Watch(ctx context.Context, in *WatchRequest, opts ...client.CallOption) (Registry_WatchService, error) {
|
||||
req := c.c.NewRequest(c.name, "Registry.Watch", &WatchRequest{})
|
||||
stream, err := c.c.Stream(ctx, req, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := stream.Send(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ®istryServiceWatch{stream}, nil
|
||||
}
|
||||
|
||||
type Registry_WatchService interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Recv() (*Result, error)
|
||||
}
|
||||
|
||||
type registryServiceWatch struct {
|
||||
stream client.Stream
|
||||
}
|
||||
|
||||
func (x *registryServiceWatch) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *registryServiceWatch) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *registryServiceWatch) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *registryServiceWatch) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *registryServiceWatch) Recv() (*Result, error) {
|
||||
m := new(Result)
|
||||
err := x.stream.Recv(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Server API for Registry service
|
||||
|
||||
type RegistryHandler interface {
|
||||
GetService(context.Context, *GetRequest, *GetResponse) error
|
||||
Register(context.Context, *Service, *EmptyResponse) error
|
||||
Deregister(context.Context, *Service, *EmptyResponse) error
|
||||
ListServices(context.Context, *ListRequest, *ListResponse) error
|
||||
Watch(context.Context, *WatchRequest, Registry_WatchStream) error
|
||||
}
|
||||
|
||||
func RegisterRegistryHandler(s server.Server, hdlr RegistryHandler, opts ...server.HandlerOption) error {
|
||||
type registry interface {
|
||||
GetService(ctx context.Context, in *GetRequest, out *GetResponse) error
|
||||
Register(ctx context.Context, in *Service, out *EmptyResponse) error
|
||||
Deregister(ctx context.Context, in *Service, out *EmptyResponse) error
|
||||
ListServices(ctx context.Context, in *ListRequest, out *ListResponse) error
|
||||
Watch(ctx context.Context, stream server.Stream) error
|
||||
}
|
||||
type Registry struct {
|
||||
registry
|
||||
}
|
||||
h := ®istryHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Registry{h}, opts...))
|
||||
}
|
||||
|
||||
type registryHandler struct {
|
||||
RegistryHandler
|
||||
}
|
||||
|
||||
func (h *registryHandler) GetService(ctx context.Context, in *GetRequest, out *GetResponse) error {
|
||||
return h.RegistryHandler.GetService(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *registryHandler) Register(ctx context.Context, in *Service, out *EmptyResponse) error {
|
||||
return h.RegistryHandler.Register(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *registryHandler) Deregister(ctx context.Context, in *Service, out *EmptyResponse) error {
|
||||
return h.RegistryHandler.Deregister(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *registryHandler) ListServices(ctx context.Context, in *ListRequest, out *ListResponse) error {
|
||||
return h.RegistryHandler.ListServices(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *registryHandler) Watch(ctx context.Context, stream server.Stream) error {
|
||||
m := new(WatchRequest)
|
||||
if err := stream.Recv(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return h.RegistryHandler.Watch(ctx, m, ®istryWatchStream{stream})
|
||||
}
|
||||
|
||||
type Registry_WatchStream interface {
|
||||
Context() context.Context
|
||||
SendMsg(interface{}) error
|
||||
RecvMsg(interface{}) error
|
||||
Close() error
|
||||
Send(*Result) error
|
||||
}
|
||||
|
||||
type registryWatchStream struct {
|
||||
stream server.Stream
|
||||
}
|
||||
|
||||
func (x *registryWatchStream) Close() error {
|
||||
return x.stream.Close()
|
||||
}
|
||||
|
||||
func (x *registryWatchStream) Context() context.Context {
|
||||
return x.stream.Context()
|
||||
}
|
||||
|
||||
func (x *registryWatchStream) SendMsg(m interface{}) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
||||
|
||||
func (x *registryWatchStream) RecvMsg(m interface{}) error {
|
||||
return x.stream.Recv(m)
|
||||
}
|
||||
|
||||
func (x *registryWatchStream) Send(m *Result) error {
|
||||
return x.stream.Send(m)
|
||||
}
|
@@ -1,101 +0,0 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package go.micro.registry;
|
||||
|
||||
service Registry {
|
||||
rpc GetService(GetRequest) returns (GetResponse) {};
|
||||
rpc Register(Service) returns (EmptyResponse) {};
|
||||
rpc Deregister(Service) returns (EmptyResponse) {};
|
||||
rpc ListServices(ListRequest) returns (ListResponse) {};
|
||||
rpc Watch(WatchRequest) returns (stream Result) {};
|
||||
}
|
||||
|
||||
// Service represents a go-micro service
|
||||
message Service {
|
||||
string name = 1;
|
||||
string version = 2;
|
||||
map<string,string> metadata = 3;
|
||||
repeated Endpoint endpoints = 4;
|
||||
repeated Node nodes = 5;
|
||||
Options options = 6;
|
||||
}
|
||||
|
||||
// Node represents the node the service is on
|
||||
message Node {
|
||||
string id = 1;
|
||||
string address = 2;
|
||||
int64 port = 3;
|
||||
map<string,string> metadata = 4;
|
||||
}
|
||||
|
||||
// Endpoint is a endpoint provided by a service
|
||||
message Endpoint {
|
||||
string name = 1;
|
||||
Value request = 2;
|
||||
Value response = 3;
|
||||
map<string, string> metadata = 4;
|
||||
}
|
||||
|
||||
// Value is an opaque value for a request or response
|
||||
message Value {
|
||||
string name = 1;
|
||||
string type = 2;
|
||||
repeated Value values = 3;
|
||||
}
|
||||
|
||||
// Options are registry options
|
||||
message Options {
|
||||
int64 ttl = 1;
|
||||
string domain = 2;
|
||||
}
|
||||
|
||||
// Result is returns by the watcher
|
||||
message Result {
|
||||
string action = 1; // create, update, delete
|
||||
Service service = 2;
|
||||
int64 timestamp = 3; // unix timestamp
|
||||
}
|
||||
|
||||
message EmptyResponse {}
|
||||
|
||||
message GetRequest {
|
||||
string service = 1;
|
||||
Options options = 2;
|
||||
}
|
||||
|
||||
message GetResponse {
|
||||
repeated Service services = 1;
|
||||
}
|
||||
|
||||
message ListRequest {
|
||||
Options options = 1;
|
||||
}
|
||||
|
||||
message ListResponse {
|
||||
repeated Service services = 1;
|
||||
}
|
||||
|
||||
message WatchRequest {
|
||||
// service is optional
|
||||
string service = 1;
|
||||
Options options = 2;
|
||||
}
|
||||
|
||||
// EventType defines the type of event
|
||||
enum EventType {
|
||||
Create = 0;
|
||||
Delete = 1;
|
||||
Update = 2;
|
||||
}
|
||||
|
||||
// Event is registry event
|
||||
message Event {
|
||||
// Event Id
|
||||
string id = 1;
|
||||
// type of event
|
||||
EventType type = 2;
|
||||
// unix timestamp of event
|
||||
int64 timestamp = 3;
|
||||
// service entry
|
||||
Service service = 4;
|
||||
}
|
@@ -1,216 +0,0 @@
|
||||
// Package service uses the registry service
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/micro/go-micro/v2/client"
|
||||
"github.com/micro/go-micro/v2/client/grpc"
|
||||
"github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
pb "github.com/micro/go-micro/v2/registry/service/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
// The default service name
|
||||
DefaultService = "go.micro.registry"
|
||||
)
|
||||
|
||||
type serviceRegistry struct {
|
||||
opts registry.Options
|
||||
// name of the registry
|
||||
name string
|
||||
// address
|
||||
address []string
|
||||
// client to call registry
|
||||
client pb.RegistryService
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) callOpts() []client.CallOption {
|
||||
var opts []client.CallOption
|
||||
|
||||
// set registry address
|
||||
if len(s.address) > 0 {
|
||||
opts = append(opts, client.WithAddress(s.address...))
|
||||
}
|
||||
|
||||
// set timeout
|
||||
if s.opts.Timeout > time.Duration(0) {
|
||||
opts = append(opts, client.WithRequestTimeout(s.opts.Timeout))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) Init(opts ...registry.Option) error {
|
||||
for _, o := range opts {
|
||||
o(&s.opts)
|
||||
}
|
||||
|
||||
if len(s.opts.Addrs) > 0 {
|
||||
s.address = s.opts.Addrs
|
||||
}
|
||||
|
||||
// extract the client from the context, fallback to grpc
|
||||
var cli client.Client
|
||||
if c, ok := s.opts.Context.Value(clientKey{}).(client.Client); ok {
|
||||
cli = c
|
||||
} else {
|
||||
cli = grpc.NewClient()
|
||||
}
|
||||
|
||||
s.client = pb.NewRegistryService(DefaultService, cli)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) Options() registry.Options {
|
||||
return s.opts
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) Register(srv *registry.Service, opts ...registry.RegisterOption) error {
|
||||
var options registry.RegisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.TODO()
|
||||
}
|
||||
|
||||
// encode srv into protobuf and pack TTL and domain into it
|
||||
pbSrv := ToProto(srv)
|
||||
pbSrv.Options.Ttl = int64(options.TTL.Seconds())
|
||||
pbSrv.Options.Domain = options.Domain
|
||||
|
||||
// register the service
|
||||
_, err := s.client.Register(options.Context, pbSrv, s.callOpts()...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) Deregister(srv *registry.Service, opts ...registry.DeregisterOption) error {
|
||||
var options registry.DeregisterOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.TODO()
|
||||
}
|
||||
|
||||
// encode srv into protobuf and pack domain into it
|
||||
pbSrv := ToProto(srv)
|
||||
pbSrv.Options.Domain = options.Domain
|
||||
|
||||
// deregister the service
|
||||
_, err := s.client.Deregister(options.Context, pbSrv, s.callOpts()...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) GetService(name string, opts ...registry.GetOption) ([]*registry.Service, error) {
|
||||
var options registry.GetOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.TODO()
|
||||
}
|
||||
|
||||
rsp, err := s.client.GetService(options.Context, &pb.GetRequest{
|
||||
Service: name, Options: &pb.Options{Domain: options.Domain},
|
||||
}, s.callOpts()...)
|
||||
|
||||
if verr, ok := err.(*errors.Error); ok && verr.Code == 404 {
|
||||
return nil, registry.ErrNotFound
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
services := make([]*registry.Service, 0, len(rsp.Services))
|
||||
for _, service := range rsp.Services {
|
||||
services = append(services, ToService(service))
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) ListServices(opts ...registry.ListOption) ([]*registry.Service, error) {
|
||||
var options registry.ListOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.TODO()
|
||||
}
|
||||
|
||||
req := &pb.ListRequest{Options: &pb.Options{Domain: options.Domain}}
|
||||
rsp, err := s.client.ListServices(options.Context, req, s.callOpts()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
services := make([]*registry.Service, 0, len(rsp.Services))
|
||||
for _, service := range rsp.Services {
|
||||
services = append(services, ToService(service))
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) Watch(opts ...registry.WatchOption) (registry.Watcher, error) {
|
||||
var options registry.WatchOptions
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
if options.Context == nil {
|
||||
options.Context = context.TODO()
|
||||
}
|
||||
|
||||
stream, err := s.client.Watch(options.Context, &pb.WatchRequest{
|
||||
Service: options.Service, Options: &pb.Options{Domain: options.Domain},
|
||||
}, s.callOpts()...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newWatcher(stream), nil
|
||||
}
|
||||
|
||||
func (s *serviceRegistry) String() string {
|
||||
return "service"
|
||||
}
|
||||
|
||||
// NewRegistry returns a new registry service client
|
||||
func NewRegistry(opts ...registry.Option) registry.Registry {
|
||||
var options registry.Options
|
||||
for _, o := range opts {
|
||||
o(&options)
|
||||
}
|
||||
|
||||
// the registry address
|
||||
addrs := options.Addrs
|
||||
if len(addrs) == 0 {
|
||||
addrs = []string{"127.0.0.1:8000"}
|
||||
}
|
||||
|
||||
if options.Context == nil {
|
||||
options.Context = context.TODO()
|
||||
}
|
||||
|
||||
// extract the client from the context, fallback to grpc
|
||||
var cli client.Client
|
||||
if c, ok := options.Context.Value(clientKey{}).(client.Client); ok {
|
||||
cli = c
|
||||
} else {
|
||||
cli = grpc.NewClient()
|
||||
}
|
||||
|
||||
// service name. TODO: accept option
|
||||
name := DefaultService
|
||||
|
||||
return &serviceRegistry{
|
||||
opts: options,
|
||||
name: name,
|
||||
address: addrs,
|
||||
client: pb.NewRegistryService(name, cli),
|
||||
}
|
||||
}
|
@@ -1,134 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
pb "github.com/micro/go-micro/v2/registry/service/proto"
|
||||
)
|
||||
|
||||
func values(v []*registry.Value) []*pb.Value {
|
||||
if len(v) == 0 {
|
||||
return []*pb.Value{}
|
||||
}
|
||||
|
||||
vs := make([]*pb.Value, 0, len(v))
|
||||
for _, vi := range v {
|
||||
vs = append(vs, &pb.Value{
|
||||
Name: vi.Name,
|
||||
Type: vi.Type,
|
||||
Values: values(vi.Values),
|
||||
})
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
func toValues(v []*pb.Value) []*registry.Value {
|
||||
if len(v) == 0 {
|
||||
return []*registry.Value{}
|
||||
}
|
||||
|
||||
vs := make([]*registry.Value, 0, len(v))
|
||||
for _, vi := range v {
|
||||
vs = append(vs, ®istry.Value{
|
||||
Name: vi.Name,
|
||||
Type: vi.Type,
|
||||
Values: toValues(vi.Values),
|
||||
})
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
func ToProto(s *registry.Service) *pb.Service {
|
||||
endpoints := make([]*pb.Endpoint, 0, len(s.Endpoints))
|
||||
for _, ep := range s.Endpoints {
|
||||
var request, response *pb.Value
|
||||
|
||||
if ep.Request != nil {
|
||||
request = &pb.Value{
|
||||
Name: ep.Request.Name,
|
||||
Type: ep.Request.Type,
|
||||
Values: values(ep.Request.Values),
|
||||
}
|
||||
}
|
||||
|
||||
if ep.Response != nil {
|
||||
response = &pb.Value{
|
||||
Name: ep.Response.Name,
|
||||
Type: ep.Response.Type,
|
||||
Values: values(ep.Response.Values),
|
||||
}
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, &pb.Endpoint{
|
||||
Name: ep.Name,
|
||||
Request: request,
|
||||
Response: response,
|
||||
Metadata: ep.Metadata,
|
||||
})
|
||||
}
|
||||
|
||||
nodes := make([]*pb.Node, 0, len(s.Nodes))
|
||||
|
||||
for _, node := range s.Nodes {
|
||||
nodes = append(nodes, &pb.Node{
|
||||
Id: node.Id,
|
||||
Address: node.Address,
|
||||
Metadata: node.Metadata,
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.Service{
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Metadata: s.Metadata,
|
||||
Endpoints: endpoints,
|
||||
Nodes: nodes,
|
||||
Options: new(pb.Options),
|
||||
}
|
||||
}
|
||||
|
||||
func ToService(s *pb.Service) *registry.Service {
|
||||
endpoints := make([]*registry.Endpoint, 0, len(s.Endpoints))
|
||||
for _, ep := range s.Endpoints {
|
||||
var request, response *registry.Value
|
||||
|
||||
if ep.Request != nil {
|
||||
request = ®istry.Value{
|
||||
Name: ep.Request.Name,
|
||||
Type: ep.Request.Type,
|
||||
Values: toValues(ep.Request.Values),
|
||||
}
|
||||
}
|
||||
|
||||
if ep.Response != nil {
|
||||
response = ®istry.Value{
|
||||
Name: ep.Response.Name,
|
||||
Type: ep.Response.Type,
|
||||
Values: toValues(ep.Response.Values),
|
||||
}
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, ®istry.Endpoint{
|
||||
Name: ep.Name,
|
||||
Request: request,
|
||||
Response: response,
|
||||
Metadata: ep.Metadata,
|
||||
})
|
||||
}
|
||||
|
||||
nodes := make([]*registry.Node, 0, len(s.Nodes))
|
||||
for _, node := range s.Nodes {
|
||||
nodes = append(nodes, ®istry.Node{
|
||||
Id: node.Id,
|
||||
Address: node.Address,
|
||||
Metadata: node.Metadata,
|
||||
})
|
||||
}
|
||||
|
||||
return ®istry.Service{
|
||||
Name: s.Name,
|
||||
Version: s.Version,
|
||||
Metadata: s.Metadata,
|
||||
Endpoints: endpoints,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/micro/go-micro/v2/registry"
|
||||
pb "github.com/micro/go-micro/v2/registry/service/proto"
|
||||
)
|
||||
|
||||
type serviceWatcher struct {
|
||||
stream pb.Registry_WatchService
|
||||
closed chan bool
|
||||
}
|
||||
|
||||
func (s *serviceWatcher) Next() (*registry.Result, error) {
|
||||
// check if closed
|
||||
select {
|
||||
case <-s.closed:
|
||||
return nil, registry.ErrWatcherStopped
|
||||
default:
|
||||
}
|
||||
|
||||
r, err := s.stream.Recv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ®istry.Result{
|
||||
Action: r.Action,
|
||||
Service: ToService(r.Service),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *serviceWatcher) Stop() {
|
||||
select {
|
||||
case <-s.closed:
|
||||
return
|
||||
default:
|
||||
close(s.closed)
|
||||
s.stream.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func newWatcher(stream pb.Registry_WatchService) registry.Watcher {
|
||||
return &serviceWatcher{
|
||||
stream: stream,
|
||||
closed: make(chan bool),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user