v3 refactor (#1868)

* Move to v3

Co-authored-by: Ben Toogood <bentoogood@gmail.com>
This commit is contained in:
Asim Aslam
2020-07-27 13:22:00 +01:00
committed by GitHub
parent 9dfeb98111
commit 563768b58a
424 changed files with 6383 additions and 22490 deletions

View File

@@ -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

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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 {

View File

@@ -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 = &registry.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, &registry.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, &registry.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 := &registry.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, &registry.Node{
Id: strings.TrimSuffix(e.Name, suffix),
Address: fmt.Sprintf("%s:%d", addr, e.Port),
Metadata: txt.Metadata,
})
return &registry.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...)
}

View File

@@ -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: &registry.Value{
Name: "request",
Type: "request",
},
Response: &Value{
Response: &registry.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
View 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)
}
}

View File

@@ -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...)
}

View File

@@ -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 (

View File

@@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/micro/go-micro/v2/registry"
"github.com/micro/go-micro/v3/registry"
)
var (

View File

@@ -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{}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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 &registryService{
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 &registryServiceWatch{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 := &registryHandler{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, &registryWatchStream{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)
}

View File

@@ -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;
}

View File

@@ -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),
}
}

View File

@@ -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, &registry.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 = &registry.Value{
Name: ep.Request.Name,
Type: ep.Request.Type,
Values: toValues(ep.Request.Values),
}
}
if ep.Response != nil {
response = &registry.Value{
Name: ep.Response.Name,
Type: ep.Response.Type,
Values: toValues(ep.Response.Values),
}
}
endpoints = append(endpoints, &registry.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, &registry.Node{
Id: node.Id,
Address: node.Address,
Metadata: node.Metadata,
})
}
return &registry.Service{
Name: s.Name,
Version: s.Version,
Metadata: s.Metadata,
Endpoints: endpoints,
Nodes: nodes,
}
}

View File

@@ -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 &registry.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),
}
}