diff --git a/.gitignore b/.gitignore index 608e9d0d..500d68ca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Develop tools +/.vscode/ +/.idea/ + # Binaries for programs and plugins *.exe *.exe~ diff --git a/README.zh-cn.md b/README.zh-cn.md index b5599821..b770c8bd 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -32,5 +32,5 @@ Go Micro把分布式系统的各种细节抽象出来。下面是它的主要特 ## 快速上手 -更多关于架构、安装的资料可以查看[文档](https://micro.mu/docs/go-micro_cn.html)。 +更多关于架构、安装的资料可以查看[文档](https://micro.mu/docs/cn/)。 diff --git a/client/grpc/codec.go b/client/grpc/codec.go index e7891eef..c792abbd 100644 --- a/client/grpc/codec.go +++ b/client/grpc/codec.go @@ -30,6 +30,7 @@ var ( "application/proto": protoCodec{}, "application/protobuf": protoCodec{}, "application/octet-stream": protoCodec{}, + "application/grpc": protoCodec{}, "application/grpc+json": jsonCodec{}, "application/grpc+proto": protoCodec{}, "application/grpc+bytes": bytesCodec{}, diff --git a/client/rpc_client.go b/client/rpc_client.go index 754e4329..2fe51934 100644 --- a/client/rpc_client.go +++ b/client/rpc_client.go @@ -96,7 +96,15 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, } } - c, err := r.pool.Get(address, transport.WithTimeout(opts.DialTimeout)) + dOpts := []transport.DialOption{ + transport.WithStream(), + } + + if opts.DialTimeout >= 0 { + dOpts = append(dOpts, transport.WithTimeout(opts.DialTimeout)) + } + + c, err := r.pool.Get(address, dOpts...) if err != nil { return errors.InternalServerError("go.micro.client", "connection error: %v", err) } diff --git a/config/source/consul/README.md b/config/source/consul/README.md index 2ba45e5b..28006fc4 100644 --- a/config/source/consul/README.md +++ b/config/source/consul/README.md @@ -44,6 +44,6 @@ Load the source into config // Create new config conf := config.NewConfig() -// Load file source +// Load consul source conf.Load(consulSource) ``` diff --git a/config/source/env/README.md b/config/source/env/README.md index 25bfa07a..61a18f8d 100644 --- a/config/source/env/README.md +++ b/config/source/env/README.md @@ -91,6 +91,6 @@ Load the source into config // Create new config conf := config.NewConfig() -// Load file source +// Load env source conf.Load(src) ``` diff --git a/config/source/flag/README.md b/config/source/flag/README.md index fb78bae2..79e5cb54 100644 --- a/config/source/flag/README.md +++ b/config/source/flag/README.md @@ -42,6 +42,6 @@ Load the source into config // Create new config conf := config.NewConfig() -// Load file source +// Load flag source conf.Load(flagSource) ``` diff --git a/config/source/memory/README.md b/config/source/memory/README.md index 7f1a4008..adef980b 100644 --- a/config/source/memory/README.md +++ b/config/source/memory/README.md @@ -39,6 +39,6 @@ Load the source into config // Create new config conf := config.NewConfig() -// Load file source +// Load memory source conf.Load(memorySource) ``` diff --git a/network/default.go b/network/default.go new file mode 100644 index 00000000..23c924f6 --- /dev/null +++ b/network/default.go @@ -0,0 +1,1005 @@ +package network + +import ( + "container/list" + "errors" + "sync" + "time" + + "github.com/golang/protobuf/proto" + "github.com/micro/go-micro/client" + rtr "github.com/micro/go-micro/client/selector/router" + pbNet "github.com/micro/go-micro/network/proto" + "github.com/micro/go-micro/proxy" + "github.com/micro/go-micro/router" + pbRtr "github.com/micro/go-micro/router/proto" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/tunnel" + tun "github.com/micro/go-micro/tunnel/transport" + "github.com/micro/go-micro/util/log" +) + +var ( + // NetworkChannel is the name of the tunnel channel for passing network messages + NetworkChannel = "network" + // ControlChannel is the name of the tunnel channel for passing control message + ControlChannel = "control" + // DefaultLink is default network link + DefaultLink = "network" +) + +var ( + // ErrMsgUnknown is returned when unknown message is attempted to send or receive + ErrMsgUnknown = errors.New("unknown message") + // ErrClientNotFound is returned when client for tunnel channel could not be found + ErrClientNotFound = errors.New("client not found") +) + +// node is network node +type node struct { + sync.RWMutex + // id is node id + id string + // address is node address + address string + // neighbours maps the node neighbourhood + neighbours map[string]*node + // network returns the node network + network Network + // lastSeen stores the time the node has been seen last time + lastSeen time.Time +} + +// Id is node ide +func (n *node) Id() string { + return n.id +} + +// Address returns node address +func (n *node) Address() string { + return n.address +} + +// Network returns node network +func (n *node) Network() Network { + return n.network +} + +// Neighbourhood returns node neighbourhood +func (n *node) Neighbourhood() []Node { + var nodes []Node + n.RLock() + for _, neighbourNode := range n.neighbours { + // make a copy of the node + n := &node{ + id: neighbourNode.id, + address: neighbourNode.address, + network: neighbourNode.network, + } + // NOTE: we do not care about neighbour's neighbours + nodes = append(nodes, n) + } + n.RUnlock() + + return nodes +} + +// network implements Network interface +type network struct { + // node is network node + *node + // options configure the network + options Options + // rtr is network router + router.Router + // prx is network proxy + proxy.Proxy + // tun is network tunnel + tunnel.Tunnel + // server is network server + server server.Server + // client is network client + client client.Client + + // tunClient is a map of tunnel clients keyed over tunnel channel names + tunClient map[string]transport.Client + + sync.RWMutex + // connected marks the network as connected + connected bool + // closed closes the network + closed chan bool +} + +// newNetwork returns a new network node +func newNetwork(opts ...Option) Network { + options := DefaultOptions() + + for _, o := range opts { + o(&options) + } + + // init tunnel address to the network bind address + options.Tunnel.Init( + tunnel.Address(options.Address), + tunnel.Nodes(options.Nodes...), + ) + + // init router Id to the network id + options.Router.Init( + router.Id(options.Id), + ) + + // create tunnel client with tunnel transport + tunTransport := tun.NewTransport( + tun.WithTunnel(options.Tunnel), + ) + + // server is network server + server := server.NewServer( + server.Id(options.Id), + server.Address(options.Address), + server.Name(options.Name), + server.Transport(tunTransport), + ) + + // client is network client + client := client.NewClient( + client.Transport(tunTransport), + client.Selector( + rtr.NewSelector( + rtr.WithRouter(options.Router), + ), + ), + ) + + network := &network{ + node: &node{ + id: options.Id, + address: options.Address, + neighbours: make(map[string]*node), + }, + options: options, + Router: options.Router, + Proxy: options.Proxy, + Tunnel: options.Tunnel, + server: server, + client: client, + tunClient: make(map[string]transport.Client), + } + + network.node.network = network + + return network +} + +// Options returns network options +func (n *network) Options() Options { + n.Lock() + options := n.options + n.Unlock() + + return options +} + +// Name returns network name +func (n *network) Name() string { + return n.options.Name +} + +// Address returns network bind address +func (n *network) Address() string { + return n.Tunnel.Address() +} + +// resolveNodes resolves network nodes to addresses +func (n *network) resolveNodes() ([]string, error) { + // resolve the network address to network nodes + records, err := n.options.Resolver.Resolve(n.options.Name) + if err != nil { + return nil, err + } + + nodeMap := make(map[string]bool) + + // collect network node addresses + var nodes []string + for _, record := range records { + nodes = append(nodes, record.Address) + nodeMap[record.Address] = true + } + + // append seed nodes if we have them + for _, node := range n.options.Nodes { + if _, ok := nodeMap[node]; !ok { + nodes = append(nodes, node) + } + } + + return nodes, nil +} + +// resolve continuously resolves network nodes and initializes network tunnel with resolved addresses +func (n *network) resolve() { + resolve := time.NewTicker(ResolveTime) + defer resolve.Stop() + + for { + select { + case <-n.closed: + return + case <-resolve.C: + nodes, err := n.resolveNodes() + if err != nil { + log.Debugf("Network failed to resolve nodes: %v", err) + continue + } + // initialize the tunnel + n.Tunnel.Init( + tunnel.Nodes(nodes...), + ) + } + } +} + +// handleNetConn handles network announcement messages +func (n *network) handleNetConn(sess tunnel.Session, msg chan *transport.Message) { + for { + m := new(transport.Message) + if err := sess.Recv(m); err != nil { + // TODO: should we bail here? + log.Debugf("Network tunnel [%s] receive error: %v", NetworkChannel, err) + return + } + + select { + case msg <- m: + case <-n.closed: + return + } + } +} + +// acceptNetConn accepts connections from NetworkChannel +func (n *network) acceptNetConn(l tunnel.Listener, recv chan *transport.Message) { + for { + // accept a connection + conn, err := l.Accept() + if err != nil { + // TODO: handle this + log.Debugf("Network tunnel [%s] accept error: %v", NetworkChannel, err) + return + } + + select { + case <-n.closed: + return + default: + // go handle NetworkChannel connection + go n.handleNetConn(conn, recv) + } + } +} + +// processNetChan processes messages received on NetworkChannel +func (n *network) processNetChan(client transport.Client, listener tunnel.Listener) { + // receive network message queue + recv := make(chan *transport.Message, 128) + + // accept NetworkChannel connections + go n.acceptNetConn(listener, recv) + + for { + select { + case m := <-recv: + // switch on type of message and take action + switch m.Header["Micro-Method"] { + case "connect": + // mark the time the message has been received + now := time.Now() + pbNetConnect := &pbNet.Connect{} + if err := proto.Unmarshal(m.Body, pbNetConnect); err != nil { + log.Debugf("Network tunnel [%s] connect unmarshal error: %v", NetworkChannel, err) + continue + } + // don't process your own messages + if pbNetConnect.Node.Id == n.options.Id { + continue + } + n.Lock() + log.Debugf("Network received connect message from: %s", pbNetConnect.Node.Id) + // if the entry already exists skip adding it + if neighbour, ok := n.neighbours[pbNetConnect.Node.Id]; ok { + // update lastSeen timestamp + if n.neighbours[pbNetConnect.Node.Id].lastSeen.Before(now) { + neighbour.lastSeen = now + } + n.Unlock() + continue + } + // add a new neighbour + // NOTE: new node does not have any neighbours + n.neighbours[pbNetConnect.Node.Id] = &node{ + id: pbNetConnect.Node.Id, + address: pbNetConnect.Node.Address, + neighbours: make(map[string]*node), + lastSeen: now, + } + n.Unlock() + // advertise yourself to the network + if err := n.sendMsg("neighbour", NetworkChannel); err != nil { + log.Debugf("Network failed to advertise neighbours: %v", err) + } + // advertise all the routes when a new node has connected + if err := n.Router.Solicit(); err != nil { + log.Debugf("Network failed to solicit routes: %s", err) + } + case "neighbour": + // mark the time the message has been received + now := time.Now() + pbNetNeighbour := &pbNet.Neighbour{} + if err := proto.Unmarshal(m.Body, pbNetNeighbour); err != nil { + log.Debugf("Network tunnel [%s] neighbour unmarshal error: %v", NetworkChannel, err) + continue + } + // don't process your own messages + if pbNetNeighbour.Node.Id == n.options.Id { + continue + } + n.Lock() + log.Debugf("Network received neighbour message from: %s", pbNetNeighbour.Node.Id) + // only add the neighbour if it is NOT already in node's list of neighbours + _, exists := n.neighbours[pbNetNeighbour.Node.Id] + if !exists { + n.neighbours[pbNetNeighbour.Node.Id] = &node{ + id: pbNetNeighbour.Node.Id, + address: pbNetNeighbour.Node.Address, + neighbours: make(map[string]*node), + lastSeen: now, + } + } + // update lastSeen timestamp + if n.neighbours[pbNetNeighbour.Node.Id].lastSeen.Before(now) { + n.neighbours[pbNetNeighbour.Node.Id].lastSeen = now + } + // update/store the neighbour node neighbours + // NOTE: * we do NOT update lastSeen time for the neighbours of the neighbour + // * even though we are NOT interested in neighbours of neighbours here + // we still allocate the map of neighbours for each of them + for _, pbNeighbour := range pbNetNeighbour.Neighbours { + neighbourNode := &node{ + id: pbNeighbour.Id, + address: pbNeighbour.Address, + neighbours: make(map[string]*node), + } + n.neighbours[pbNetNeighbour.Node.Id].neighbours[pbNeighbour.Id] = neighbourNode + } + n.Unlock() + // send a solicit message when discovering a new node + // NOTE: we need to send the solicit message here after the Lock is released as sendMsg locks, too + if !exists { + if err := n.sendMsg("solicit", ControlChannel); err != nil { + log.Debugf("Network failed to send solicit message: %s", err) + } + } + case "close": + pbNetClose := &pbNet.Close{} + if err := proto.Unmarshal(m.Body, pbNetClose); err != nil { + log.Debugf("Network tunnel [%s] close unmarshal error: %v", NetworkChannel, err) + continue + } + // don't process your own messages + if pbNetClose.Node.Id == n.options.Id { + continue + } + n.Lock() + log.Debugf("Network received close message from: %s", pbNetClose.Node.Id) + if err := n.pruneNode(pbNetClose.Node.Id); err != nil { + log.Debugf("Network failed to prune the node %s: %v", pbNetClose.Node.Id, err) + continue + } + n.Unlock() + } + case <-n.closed: + return + } + } +} + +// sendMsg sends a message to the tunnel channel +func (n *network) sendMsg(msgType string, channel string) error { + node := &pbNet.Node{ + Id: n.options.Id, + Address: n.options.Address, + } + + var protoMsg proto.Message + + switch msgType { + case "connect": + protoMsg = &pbNet.Connect{ + Node: node, + } + case "close": + protoMsg = &pbNet.Close{ + Node: node, + } + case "solicit": + protoMsg = &pbNet.Solicit{ + Node: node, + } + case "neighbour": + n.RLock() + nodes := make([]*pbNet.Node, len(n.neighbours)) + i := 0 + for id := range n.neighbours { + nodes[i] = &pbNet.Node{ + Id: id, + Address: n.neighbours[id].address, + } + i++ + } + n.RUnlock() + protoMsg = &pbNet.Neighbour{ + Node: node, + Neighbours: nodes, + } + default: + return ErrMsgUnknown + } + + body, err := proto.Marshal(protoMsg) + if err != nil { + return err + } + // create transport message and chuck it down the pipe + m := transport.Message{ + Header: map[string]string{ + "Micro-Method": msgType, + }, + Body: body, + } + + n.RLock() + client, ok := n.tunClient[channel] + if !ok { + n.RUnlock() + return ErrClientNotFound + } + n.RUnlock() + + log.Debugf("Network sending %s message from: %s", msgType, node.Id) + if err := client.Send(&m); err != nil { + return err + } + + return nil +} + +// announce announces node neighbourhood to the network +func (n *network) announce(client transport.Client) { + announce := time.NewTicker(AnnounceTime) + defer announce.Stop() + + for { + select { + case <-n.closed: + return + case <-announce.C: + // advertise yourself to the network + if err := n.sendMsg("neighbour", NetworkChannel); err != nil { + log.Debugf("Network failed to advertise neighbours: %v", err) + continue + } + } + } +} + +// pruneNode removes a node with given id from the list of neighbours. It also removes all routes originted by this node. +// NOTE: this method is not thread-safe; when calling it make sure you lock the particular code segment +func (n *network) pruneNode(id string) error { + delete(n.neighbours, id) + // lookup all the routes originated at this node + q := router.NewQuery( + router.QueryRouter(id), + ) + routes, err := n.Router.Table().Query(q) + if err != nil && err != router.ErrRouteNotFound { + return err + } + // delete the found routes + log.Logf("Network deleting routes originated by router: %s", id) + for _, route := range routes { + if err := n.Router.Table().Delete(route); err != nil && err != router.ErrRouteNotFound { + return err + } + } + + return nil +} + +// prune the nodes that have not been seen for certain period of time defined by PruneTime +// Additionally, prune also removes all the routes originated by these nodes +func (n *network) prune() { + prune := time.NewTicker(PruneTime) + defer prune.Stop() + + for { + select { + case <-n.closed: + return + case <-prune.C: + n.Lock() + for id, node := range n.neighbours { + if id == n.options.Id { + continue + } + if time.Since(node.lastSeen) > PruneTime { + log.Debugf("Network deleting node %s: reached prune time threshold", id) + if err := n.pruneNode(id); err != nil { + log.Debugf("Network failed to prune the node %s: %v", id, err) + continue + } + } + } + n.Unlock() + } + } +} + +// handleCtrlConn handles ControlChannel connections +func (n *network) handleCtrlConn(sess tunnel.Session, msg chan *transport.Message) { + for { + m := new(transport.Message) + if err := sess.Recv(m); err != nil { + // TODO: should we bail here? + log.Debugf("Network tunnel advert receive error: %v", err) + return + } + + select { + case msg <- m: + case <-n.closed: + return + } + } +} + +// acceptCtrlConn accepts connections from ControlChannel +func (n *network) acceptCtrlConn(l tunnel.Listener, recv chan *transport.Message) { + for { + // accept a connection + conn, err := l.Accept() + if err != nil { + // TODO: handle this + log.Debugf("Network tunnel [%s] accept error: %v", ControlChannel, err) + return + } + + select { + case <-n.closed: + return + default: + // go handle ControlChannel connection + go n.handleCtrlConn(conn, recv) + } + } +} + +// setRouteMetric calculates metric of the route and updates it in place +// - Local route metric is 1 +// - Routes with ID of adjacent neighbour are 10 +// - Routes of neighbours of the advertiser are 100 +// - Routes beyond your neighbourhood are 1000 +func (n *network) setRouteMetric(route *router.Route) { + // we are the origin of the route + if route.Router == n.options.Id { + route.Metric = 1 + return + } + + n.RLock() + // check if the route origin is our neighbour + if _, ok := n.neighbours[route.Router]; ok { + route.Metric = 10 + n.RUnlock() + return + } + + // check if the route origin is the neighbour of our neighbour + for _, node := range n.neighbours { + for id := range node.neighbours { + if route.Router == id { + route.Metric = 100 + n.RUnlock() + return + } + } + } + n.RUnlock() + + // the origin of the route is beyond our neighbourhood + route.Metric = 1000 +} + +// processCtrlChan processes messages received on ControlChannel +func (n *network) processCtrlChan(client transport.Client, listener tunnel.Listener) { + // receive control message queue + recv := make(chan *transport.Message, 128) + + // accept ControlChannel cconnections + go n.acceptCtrlConn(listener, recv) + + for { + select { + case m := <-recv: + // switch on type of message and take action + switch m.Header["Micro-Method"] { + case "advert": + now := time.Now() + pbRtrAdvert := &pbRtr.Advert{} + if err := proto.Unmarshal(m.Body, pbRtrAdvert); err != nil { + log.Debugf("Network fail to unmarshal advert message: %v", err) + continue + } + // don't process your own messages + if pbRtrAdvert.Id == n.options.Id { + continue + } + // loookup advertising node in our neighbourhood + n.RLock() + log.Debugf("Network received advert message from: %s", pbRtrAdvert.Id) + advertNode, ok := n.neighbours[pbRtrAdvert.Id] + if !ok { + // advertising node has not been registered as our neighbour, yet + // let's add it to the map of our neighbours + advertNode = &node{ + id: pbRtrAdvert.Id, + neighbours: make(map[string]*node), + lastSeen: now, + } + n.neighbours[pbRtrAdvert.Id] = advertNode + // send a solicit message when discovering a new node + if err := n.sendMsg("solicit", NetworkChannel); err != nil { + log.Debugf("Network failed to send solicit message: %s", err) + } + } + n.RUnlock() + + var events []*router.Event + for _, event := range pbRtrAdvert.Events { + // set the address of the advertising node + // we know Route.Gateway is the address of advertNode + // NOTE: this is true only when advertNode had not been registered + // as our neighbour when we received the advert from it + if advertNode.address == "" { + advertNode.address = event.Route.Gateway + } + // if advertising node id is not the same as Route.Router + // we know the advertising node is not the origin of the route + if advertNode.id != event.Route.Router { + // if the origin router is not in the advertising node neighbourhood + // we can't rule out potential routing loops so we bail here + if _, ok := advertNode.neighbours[event.Route.Router]; !ok { + continue + } + } + route := router.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: event.Route.Gateway, + Network: event.Route.Network, + Router: event.Route.Router, + Link: event.Route.Link, + Metric: int(event.Route.Metric), + } + // set the route metric + n.setRouteMetric(&route) + // throw away metric bigger than 1000 + if route.Metric > 1000 { + log.Debugf("Network route metric %d dropping node: %s", route.Metric, route.Router) + continue + } + // create router event + e := &router.Event{ + Type: router.EventType(event.Type), + Timestamp: time.Unix(0, pbRtrAdvert.Timestamp), + Route: route, + } + events = append(events, e) + } + advert := &router.Advert{ + Id: pbRtrAdvert.Id, + Type: router.AdvertType(pbRtrAdvert.Type), + Timestamp: time.Unix(0, pbRtrAdvert.Timestamp), + TTL: time.Duration(pbRtrAdvert.Ttl), + Events: events, + } + + if err := n.Router.Process(advert); err != nil { + log.Debugf("Network failed to process advert %s: %v", advert.Id, err) + continue + } + case "solicit": + pbNetSolicit := &pbNet.Solicit{} + if err := proto.Unmarshal(m.Body, pbNetSolicit); err != nil { + log.Debugf("Network fail to unmarshal solicit message: %v", err) + continue + } + log.Debugf("Network received solicit message from: %s", pbNetSolicit.Node.Id) + // don't process your own messages + if pbNetSolicit.Node.Id == n.options.Id { + continue + } + // advertise all the routes when a new node has connected + if err := n.Router.Solicit(); err != nil { + log.Debugf("Network failed to solicit routes: %s", err) + } + } + case <-n.closed: + return + } + } +} + +// advertise advertises routes to the network +func (n *network) advertise(client transport.Client, advertChan <-chan *router.Advert) { + for { + select { + // process local adverts and randomly fire them at other nodes + case advert := <-advertChan: + // create a proto advert + var events []*pbRtr.Event + for _, event := range advert.Events { + // NOTE: we override the Gateway and Link fields here + route := &pbRtr.Route{ + Service: event.Route.Service, + Address: event.Route.Address, + Gateway: n.options.Address, + Network: event.Route.Network, + Router: event.Route.Router, + Link: DefaultLink, + Metric: int64(event.Route.Metric), + } + e := &pbRtr.Event{ + Type: pbRtr.EventType(event.Type), + Timestamp: event.Timestamp.UnixNano(), + Route: route, + } + events = append(events, e) + } + pbRtrAdvert := &pbRtr.Advert{ + Id: advert.Id, + Type: pbRtr.AdvertType(advert.Type), + Timestamp: advert.Timestamp.UnixNano(), + Events: events, + } + body, err := proto.Marshal(pbRtrAdvert) + if err != nil { + // TODO: should we bail here? + log.Debugf("Network failed to marshal advert message: %v", err) + continue + } + // create transport message and chuck it down the pipe + m := transport.Message{ + Header: map[string]string{ + "Micro-Method": "advert", + }, + Body: body, + } + + log.Debugf("Network sending advert message from: %s", pbRtrAdvert.Id) + if err := client.Send(&m); err != nil { + log.Debugf("Network failed to send advert %s: %v", pbRtrAdvert.Id, err) + continue + } + case <-n.closed: + return + } + } +} + +// Connect connects the network +func (n *network) Connect() error { + n.Lock() + // return if already connected + if n.connected { + return nil + } + + // try to resolve network nodes + nodes, err := n.resolveNodes() + if err != nil { + log.Debugf("Network failed to resolve nodes: %v", err) + } + + // connect network tunnel + if err := n.Tunnel.Connect(); err != nil { + return err + } + + // initialize the tunnel to resolved nodes + n.Tunnel.Init( + tunnel.Nodes(nodes...), + ) + + // dial into ControlChannel to send route adverts + ctrlClient, err := n.Tunnel.Dial(ControlChannel, tunnel.DialMulticast()) + if err != nil { + return err + } + + n.tunClient[ControlChannel] = ctrlClient + + // listen on ControlChannel + ctrlListener, err := n.Tunnel.Listen(ControlChannel) + if err != nil { + return err + } + + // dial into NetworkChannel to send network messages + netClient, err := n.Tunnel.Dial(NetworkChannel, tunnel.DialMulticast()) + if err != nil { + return err + } + + n.tunClient[NetworkChannel] = netClient + + // listen on NetworkChannel + netListener, err := n.Tunnel.Listen(NetworkChannel) + if err != nil { + return err + } + + // create closed channel + n.closed = make(chan bool) + + // start the router + if err := n.options.Router.Start(); err != nil { + return err + } + + // start advertising routes + advertChan, err := n.options.Router.Advertise() + if err != nil { + return err + } + + // start the server + if err := n.server.Start(); err != nil { + return err + } + n.Unlock() + + // send connect message to NetworkChannel + // NOTE: in theory we could do this as soon as + // Dial to NetworkChannel succeeds, but instead + // we initialize all other node resources first + if err := n.sendMsg("connect", NetworkChannel); err != nil { + log.Debugf("Network failed to send connect message: %s", err) + } + + // go resolving network nodes + go n.resolve() + // broadcast neighbourhood + go n.announce(netClient) + // prune stale nodes + go n.prune() + // listen to network messages + go n.processNetChan(netClient, netListener) + // advertise service routes + go n.advertise(ctrlClient, advertChan) + // accept and process routes + go n.processCtrlChan(ctrlClient, ctrlListener) + + n.Lock() + n.connected = true + n.Unlock() + + return nil +} + +// Nodes returns a list of all network nodes +func (n *network) Nodes() []Node { + //track the visited nodes + visited := make(map[string]*node) + // queue of the nodes to visit + queue := list.New() + + // we need to freeze the network graph here + // otherwise we might get invalid results + n.RLock() + defer n.RUnlock() + + // push network node to the back of queue + queue.PushBack(n.node) + // mark the node as visited + visited[n.node.id] = n.node + + // keep iterating over the queue until its empty + for queue.Len() > 0 { + // pop the node from the front of the queue + qnode := queue.Front() + // iterate through all of its neighbours + // mark the visited nodes; enqueue the non-visted + for id, node := range qnode.Value.(*node).neighbours { + if _, ok := visited[id]; !ok { + visited[id] = node + queue.PushBack(node) + } + } + // remove the node from the queue + queue.Remove(qnode) + } + + var nodes []Node + // collect all the nodes and return them + for _, node := range visited { + nodes = append(nodes, node) + } + + return nodes +} + +func (n *network) close() error { + // stop the server + if err := n.server.Stop(); err != nil { + return err + } + + // stop the router + if err := n.Router.Stop(); err != nil { + return err + } + + // close the tunnel + if err := n.Tunnel.Close(); err != nil { + return err + } + + return nil +} + +// Close closes network connection +func (n *network) Close() error { + // lock this operation + n.Lock() + + if !n.connected { + n.Unlock() + return nil + } + + select { + case <-n.closed: + n.Unlock() + return nil + default: + // TODO: send close message to the network channel + close(n.closed) + // set connected to false + n.connected = false + + // unlock the lock otherwise we'll deadlock sending the close + n.Unlock() + + // send close message only if we managed to connect to NetworkChannel + log.Debugf("Sending close message from: %s", n.options.Id) + if err := n.sendMsg("close", NetworkChannel); err != nil { + log.Debugf("Network failed to send close message: %s", err) + } + } + + return n.close() +} + +// Client returns network client +func (n *network) Client() client.Client { + return n.client +} + +// Server returns network server +func (n *network) Server() server.Server { + return n.server +} diff --git a/network/handler/handler.go b/network/handler/handler.go new file mode 100644 index 00000000..1d8d74a8 --- /dev/null +++ b/network/handler/handler.go @@ -0,0 +1,111 @@ +// Package handler implements network RPC handler +package handler + +import ( + "context" + "sort" + + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/network" + pbNet "github.com/micro/go-micro/network/proto" + pbRtr "github.com/micro/go-micro/router/proto" +) + +// Network implements network handler +type Network struct { + Network network.Network +} + +// ListRoutes returns a list of routing table routes +func (n *Network) ListRoutes(ctx context.Context, req *pbRtr.Request, resp *pbRtr.ListResponse) error { + routes, err := n.Network.Options().Router.Table().List() + if err != nil { + return errors.InternalServerError("go.micro.network", "failed to list routes: %s", err) + } + + var respRoutes []*pbRtr.Route + for _, route := range routes { + respRoute := &pbRtr.Route{ + Service: route.Service, + Address: route.Address, + Gateway: route.Gateway, + Network: route.Network, + Router: route.Router, + Link: route.Link, + Metric: int64(route.Metric), + } + respRoutes = append(respRoutes, respRoute) + } + + resp.Routes = respRoutes + + return nil +} + +// ListNodes returns a list of all accessible nodes in the network +func (n *Network) ListNodes(ctx context.Context, req *pbNet.ListRequest, resp *pbNet.ListResponse) error { + nodes := n.Network.Nodes() + + var respNodes []*pbNet.Node + for _, node := range nodes { + respNode := &pbNet.Node{ + Id: node.Id(), + Address: node.Address(), + } + respNodes = append(respNodes, respNode) + } + + resp.Nodes = respNodes + + return nil +} + +// Neighbourhood returns a list of immediate neighbours +func (n *Network) Neighbourhood(ctx context.Context, req *pbNet.NeighbourhoodRequest, resp *pbNet.NeighbourhoodResponse) error { + // extract the id of the node to query + id := req.Id + // if no id is passed, we assume local node + if id == "" { + id = n.Network.Id() + } + + // get all the nodes in the network + nodes := n.Network.Nodes() + + // sort the slice of nodes + sort.Slice(nodes, func(i, j int) bool { return nodes[i].Id() <= nodes[j].Id() }) + // find a node with a given id + i := sort.Search(len(nodes), func(j int) bool { return nodes[j].Id() >= id }) + + var neighbours []*pbNet.Node + // collect all the nodes in the neighbourhood of the found node + if i < len(nodes) && nodes[i].Id() == id { + for _, neighbour := range nodes[i].Neighbourhood() { + // don't return yourself in response + if neighbour.Id() == n.Network.Id() { + continue + } + pbNeighbour := &pbNet.Node{ + Id: neighbour.Id(), + Address: neighbour.Address(), + } + neighbours = append(neighbours, pbNeighbour) + } + } + + // requested neighbourhood node + node := &pbNet.Node{ + Id: nodes[i].Id(), + Address: nodes[i].Address(), + } + + // creaate neighbourhood answer + neighbourhood := &pbNet.Neighbour{ + Node: node, + Neighbours: neighbours, + } + + resp.Neighbourhood = neighbourhood + + return nil +} diff --git a/network/network.go b/network/network.go index 15d8155f..5a9b7f6a 100644 --- a/network/network.go +++ b/network/network.go @@ -1,2 +1,60 @@ // Package network is for creating internetworks package network + +import ( + "time" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/server" +) + +var ( + // DefaultName is default network name + DefaultName = "go.micro" + // DefaultAddress is default network address + DefaultAddress = ":0" + // ResolveTime defines time interval to periodically resolve network nodes + ResolveTime = 1 * time.Minute + // AnnounceTime defines time interval to periodically announce node neighbours + AnnounceTime = 30 * time.Second + // PruneTime defines time interval to periodically check nodes that need to be pruned + // due to their not announcing their presence within this time interval + PruneTime = 90 * time.Second +) + +// Node is network node +type Node interface { + // Id is node id + Id() string + // Address is node bind address + Address() string + // Neighbourhood is node neighbourhood + Neighbourhood() []Node + // Network is the network node is in + Network() Network +} + +// Network is micro network +type Network interface { + // Node is network node + Node + // Options returns the network options + Options() Options + // Name of the network + Name() string + // Connect starts the resolver and tunnel server + Connect() error + // Nodes returns list of network nodes + Nodes() []Node + // Close stops the tunnel and resolving + Close() error + // Client is micro client + Client() client.Client + // Server is micro server + Server() server.Server +} + +// NewNetwork returns a new network interface +func NewNetwork(opts ...Option) Network { + return newNetwork(opts...) +} diff --git a/network/options.go b/network/options.go new file mode 100644 index 00000000..e670cf4c --- /dev/null +++ b/network/options.go @@ -0,0 +1,103 @@ +package network + +import ( + "github.com/google/uuid" + "github.com/micro/go-micro/network/resolver" + "github.com/micro/go-micro/network/resolver/registry" + "github.com/micro/go-micro/proxy" + "github.com/micro/go-micro/proxy/mucp" + "github.com/micro/go-micro/router" + "github.com/micro/go-micro/tunnel" +) + +type Option func(*Options) + +// Options configure network +type Options struct { + // Id of the node + Id string + // Name of the network + Name string + // Address to bind to + Address string + // Nodes is a list of seed nodes + Nodes []string + // Tunnel is network tunnel + Tunnel tunnel.Tunnel + // Router is network router + Router router.Router + // Proxy is network proxy + Proxy proxy.Proxy + // Resolver is network resolver + Resolver resolver.Resolver +} + +// Id sets the id of the network node +func Id(id string) Option { + return func(o *Options) { + o.Id = id + } +} + +// Name sets the network name +func Name(n string) Option { + return func(o *Options) { + o.Name = n + } +} + +// Address sets the network address +func Address(a string) Option { + return func(o *Options) { + o.Address = a + } +} + +// Nodes is a list of seed nodes used along +// with resolved node +func Nodes(n ...string) Option { + return func(o *Options) { + o.Nodes = n + } +} + +// Tunnel sets the network tunnel +func Tunnel(t tunnel.Tunnel) Option { + return func(o *Options) { + o.Tunnel = t + } +} + +// Router sets the network router +func Router(r router.Router) Option { + return func(o *Options) { + o.Router = r + } +} + +// Proxy sets the network proxy +func Proxy(p proxy.Proxy) Option { + return func(o *Options) { + o.Proxy = p + } +} + +// Resolver is the network resolver +func Resolver(r resolver.Resolver) Option { + return func(o *Options) { + o.Resolver = r + } +} + +// DefaultOptions returns network default options +func DefaultOptions() Options { + return Options{ + Id: uuid.New().String(), + Name: DefaultName, + Address: DefaultAddress, + Tunnel: tunnel.NewTunnel(), + Router: router.DefaultRouter, + Proxy: mucp.NewProxy(), + Resolver: ®istry.Resolver{}, + } +} diff --git a/network/proto/network.micro.go b/network/proto/network.micro.go new file mode 100644 index 00000000..c114ee96 --- /dev/null +++ b/network/proto/network.micro.go @@ -0,0 +1,126 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: network.proto + +package go_micro_network + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + proto1 "github.com/micro/go-micro/router/proto" + math "math" +) + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/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 _ context.Context +var _ client.Option +var _ server.Option + +// Client API for Network service + +type NetworkService interface { + ListRoutes(ctx context.Context, in *proto1.Request, opts ...client.CallOption) (*proto1.ListResponse, error) + ListNodes(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) + Neighbourhood(ctx context.Context, in *NeighbourhoodRequest, opts ...client.CallOption) (*NeighbourhoodResponse, error) +} + +type networkService struct { + c client.Client + name string +} + +func NewNetworkService(name string, c client.Client) NetworkService { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "go.micro.network" + } + return &networkService{ + c: c, + name: name, + } +} + +func (c *networkService) ListRoutes(ctx context.Context, in *proto1.Request, opts ...client.CallOption) (*proto1.ListResponse, error) { + req := c.c.NewRequest(c.name, "Network.ListRoutes", in) + out := new(proto1.ListResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkService) ListNodes(ctx context.Context, in *ListRequest, opts ...client.CallOption) (*ListResponse, error) { + req := c.c.NewRequest(c.name, "Network.ListNodes", in) + out := new(ListResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkService) Neighbourhood(ctx context.Context, in *NeighbourhoodRequest, opts ...client.CallOption) (*NeighbourhoodResponse, error) { + req := c.c.NewRequest(c.name, "Network.Neighbourhood", in) + out := new(NeighbourhoodResponse) + err := c.c.Call(ctx, req, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Network service + +type NetworkHandler interface { + ListRoutes(context.Context, *proto1.Request, *proto1.ListResponse) error + ListNodes(context.Context, *ListRequest, *ListResponse) error + Neighbourhood(context.Context, *NeighbourhoodRequest, *NeighbourhoodResponse) error +} + +func RegisterNetworkHandler(s server.Server, hdlr NetworkHandler, opts ...server.HandlerOption) error { + type network interface { + ListRoutes(ctx context.Context, in *proto1.Request, out *proto1.ListResponse) error + ListNodes(ctx context.Context, in *ListRequest, out *ListResponse) error + Neighbourhood(ctx context.Context, in *NeighbourhoodRequest, out *NeighbourhoodResponse) error + } + type Network struct { + network + } + h := &networkHandler{hdlr} + return s.Handle(s.NewHandler(&Network{h}, opts...)) +} + +type networkHandler struct { + NetworkHandler +} + +func (h *networkHandler) ListRoutes(ctx context.Context, in *proto1.Request, out *proto1.ListResponse) error { + return h.NetworkHandler.ListRoutes(ctx, in, out) +} + +func (h *networkHandler) ListNodes(ctx context.Context, in *ListRequest, out *ListResponse) error { + return h.NetworkHandler.ListNodes(ctx, in, out) +} + +func (h *networkHandler) Neighbourhood(ctx context.Context, in *NeighbourhoodRequest, out *NeighbourhoodResponse) error { + return h.NetworkHandler.Neighbourhood(ctx, in, out) +} diff --git a/network/proto/network.pb.go b/network/proto/network.pb.go new file mode 100644 index 00000000..dc6c0d48 --- /dev/null +++ b/network/proto/network.pb.go @@ -0,0 +1,438 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: network.proto + +package go_micro_network + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + _ "github.com/micro/go-micro/router/proto" + math "math" +) + +// 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 + +// Empty request +type ListRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListRequest) Reset() { *m = ListRequest{} } +func (m *ListRequest) String() string { return proto.CompactTextString(m) } +func (*ListRequest) ProtoMessage() {} +func (*ListRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{0} +} + +func (m *ListRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListRequest.Unmarshal(m, b) +} +func (m *ListRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListRequest.Marshal(b, m, deterministic) +} +func (m *ListRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListRequest.Merge(m, src) +} +func (m *ListRequest) XXX_Size() int { + return xxx_messageInfo_ListRequest.Size(m) +} +func (m *ListRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ListRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ListRequest proto.InternalMessageInfo + +// ListResponse is returned by ListNodes and ListNeighbours +type ListResponse struct { + Nodes []*Node `protobuf:"bytes,1,rep,name=nodes,proto3" json:"nodes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListResponse) Reset() { *m = ListResponse{} } +func (m *ListResponse) String() string { return proto.CompactTextString(m) } +func (*ListResponse) ProtoMessage() {} +func (*ListResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{1} +} + +func (m *ListResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListResponse.Unmarshal(m, b) +} +func (m *ListResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListResponse.Marshal(b, m, deterministic) +} +func (m *ListResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListResponse.Merge(m, src) +} +func (m *ListResponse) XXX_Size() int { + return xxx_messageInfo_ListResponse.Size(m) +} +func (m *ListResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListResponse proto.InternalMessageInfo + +func (m *ListResponse) GetNodes() []*Node { + if m != nil { + return m.Nodes + } + return nil +} + +// NeighbourhoodRequest is sent to query node neighbourhood +type NeighbourhoodRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NeighbourhoodRequest) Reset() { *m = NeighbourhoodRequest{} } +func (m *NeighbourhoodRequest) String() string { return proto.CompactTextString(m) } +func (*NeighbourhoodRequest) ProtoMessage() {} +func (*NeighbourhoodRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{2} +} + +func (m *NeighbourhoodRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NeighbourhoodRequest.Unmarshal(m, b) +} +func (m *NeighbourhoodRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NeighbourhoodRequest.Marshal(b, m, deterministic) +} +func (m *NeighbourhoodRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_NeighbourhoodRequest.Merge(m, src) +} +func (m *NeighbourhoodRequest) XXX_Size() int { + return xxx_messageInfo_NeighbourhoodRequest.Size(m) +} +func (m *NeighbourhoodRequest) XXX_DiscardUnknown() { + xxx_messageInfo_NeighbourhoodRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_NeighbourhoodRequest proto.InternalMessageInfo + +func (m *NeighbourhoodRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +// NeighbourhoodResponse contains node neighbourhood hierarchy +type NeighbourhoodResponse struct { + Neighbourhood *Neighbour `protobuf:"bytes,1,opt,name=neighbourhood,proto3" json:"neighbourhood,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NeighbourhoodResponse) Reset() { *m = NeighbourhoodResponse{} } +func (m *NeighbourhoodResponse) String() string { return proto.CompactTextString(m) } +func (*NeighbourhoodResponse) ProtoMessage() {} +func (*NeighbourhoodResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{3} +} + +func (m *NeighbourhoodResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NeighbourhoodResponse.Unmarshal(m, b) +} +func (m *NeighbourhoodResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NeighbourhoodResponse.Marshal(b, m, deterministic) +} +func (m *NeighbourhoodResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_NeighbourhoodResponse.Merge(m, src) +} +func (m *NeighbourhoodResponse) XXX_Size() int { + return xxx_messageInfo_NeighbourhoodResponse.Size(m) +} +func (m *NeighbourhoodResponse) XXX_DiscardUnknown() { + xxx_messageInfo_NeighbourhoodResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_NeighbourhoodResponse proto.InternalMessageInfo + +func (m *NeighbourhoodResponse) GetNeighbourhood() *Neighbour { + if m != nil { + return m.Neighbourhood + } + return nil +} + +// Node is network node +type Node struct { + // node ide + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // node address + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Node) Reset() { *m = Node{} } +func (m *Node) String() string { return proto.CompactTextString(m) } +func (*Node) ProtoMessage() {} +func (*Node) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{4} +} + +func (m *Node) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Node.Unmarshal(m, b) +} +func (m *Node) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Node.Marshal(b, m, deterministic) +} +func (m *Node) XXX_Merge(src proto.Message) { + xxx_messageInfo_Node.Merge(m, src) +} +func (m *Node) XXX_Size() int { + return xxx_messageInfo_Node.Size(m) +} +func (m *Node) XXX_DiscardUnknown() { + xxx_messageInfo_Node.DiscardUnknown(m) +} + +var xxx_messageInfo_Node proto.InternalMessageInfo + +func (m *Node) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Node) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +// Connect is sent when the node connects to the network +type Connect struct { + // network mode + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Connect) Reset() { *m = Connect{} } +func (m *Connect) String() string { return proto.CompactTextString(m) } +func (*Connect) ProtoMessage() {} +func (*Connect) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{5} +} + +func (m *Connect) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Connect.Unmarshal(m, b) +} +func (m *Connect) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Connect.Marshal(b, m, deterministic) +} +func (m *Connect) XXX_Merge(src proto.Message) { + xxx_messageInfo_Connect.Merge(m, src) +} +func (m *Connect) XXX_Size() int { + return xxx_messageInfo_Connect.Size(m) +} +func (m *Connect) XXX_DiscardUnknown() { + xxx_messageInfo_Connect.DiscardUnknown(m) +} + +var xxx_messageInfo_Connect proto.InternalMessageInfo + +func (m *Connect) GetNode() *Node { + if m != nil { + return m.Node + } + return nil +} + +// Close is sent when the node disconnects from the network +type Close struct { + // network node + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Close) Reset() { *m = Close{} } +func (m *Close) String() string { return proto.CompactTextString(m) } +func (*Close) ProtoMessage() {} +func (*Close) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{6} +} + +func (m *Close) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Close.Unmarshal(m, b) +} +func (m *Close) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Close.Marshal(b, m, deterministic) +} +func (m *Close) XXX_Merge(src proto.Message) { + xxx_messageInfo_Close.Merge(m, src) +} +func (m *Close) XXX_Size() int { + return xxx_messageInfo_Close.Size(m) +} +func (m *Close) XXX_DiscardUnknown() { + xxx_messageInfo_Close.DiscardUnknown(m) +} + +var xxx_messageInfo_Close proto.InternalMessageInfo + +func (m *Close) GetNode() *Node { + if m != nil { + return m.Node + } + return nil +} + +// Solicit is sent when requesting route advertisement from the network nodes +type Solicit struct { + // network node + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Solicit) Reset() { *m = Solicit{} } +func (m *Solicit) String() string { return proto.CompactTextString(m) } +func (*Solicit) ProtoMessage() {} +func (*Solicit) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{7} +} + +func (m *Solicit) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Solicit.Unmarshal(m, b) +} +func (m *Solicit) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Solicit.Marshal(b, m, deterministic) +} +func (m *Solicit) XXX_Merge(src proto.Message) { + xxx_messageInfo_Solicit.Merge(m, src) +} +func (m *Solicit) XXX_Size() int { + return xxx_messageInfo_Solicit.Size(m) +} +func (m *Solicit) XXX_DiscardUnknown() { + xxx_messageInfo_Solicit.DiscardUnknown(m) +} + +var xxx_messageInfo_Solicit proto.InternalMessageInfo + +func (m *Solicit) GetNode() *Node { + if m != nil { + return m.Node + } + return nil +} + +// Neighbour is used to nnounce node neighbourhood +type Neighbour struct { + // network node + Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"` + // neighbours + Neighbours []*Node `protobuf:"bytes,3,rep,name=neighbours,proto3" json:"neighbours,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Neighbour) Reset() { *m = Neighbour{} } +func (m *Neighbour) String() string { return proto.CompactTextString(m) } +func (*Neighbour) ProtoMessage() {} +func (*Neighbour) Descriptor() ([]byte, []int) { + return fileDescriptor_8571034d60397816, []int{8} +} + +func (m *Neighbour) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Neighbour.Unmarshal(m, b) +} +func (m *Neighbour) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Neighbour.Marshal(b, m, deterministic) +} +func (m *Neighbour) XXX_Merge(src proto.Message) { + xxx_messageInfo_Neighbour.Merge(m, src) +} +func (m *Neighbour) XXX_Size() int { + return xxx_messageInfo_Neighbour.Size(m) +} +func (m *Neighbour) XXX_DiscardUnknown() { + xxx_messageInfo_Neighbour.DiscardUnknown(m) +} + +var xxx_messageInfo_Neighbour proto.InternalMessageInfo + +func (m *Neighbour) GetNode() *Node { + if m != nil { + return m.Node + } + return nil +} + +func (m *Neighbour) GetNeighbours() []*Node { + if m != nil { + return m.Neighbours + } + return nil +} + +func init() { + proto.RegisterType((*ListRequest)(nil), "go.micro.network.ListRequest") + proto.RegisterType((*ListResponse)(nil), "go.micro.network.ListResponse") + proto.RegisterType((*NeighbourhoodRequest)(nil), "go.micro.network.NeighbourhoodRequest") + proto.RegisterType((*NeighbourhoodResponse)(nil), "go.micro.network.NeighbourhoodResponse") + proto.RegisterType((*Node)(nil), "go.micro.network.Node") + proto.RegisterType((*Connect)(nil), "go.micro.network.Connect") + proto.RegisterType((*Close)(nil), "go.micro.network.Close") + proto.RegisterType((*Solicit)(nil), "go.micro.network.Solicit") + proto.RegisterType((*Neighbour)(nil), "go.micro.network.Neighbour") +} + +func init() { proto.RegisterFile("network.proto", fileDescriptor_8571034d60397816) } + +var fileDescriptor_8571034d60397816 = []byte{ + // 360 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0x41, 0x4f, 0xf2, 0x40, + 0x10, 0xfd, 0x28, 0xf0, 0x35, 0x0c, 0x1f, 0x5f, 0xcc, 0x46, 0x4d, 0x53, 0x83, 0x21, 0x7b, 0x40, + 0x62, 0xb4, 0x18, 0x08, 0x9e, 0xbc, 0x18, 0x0e, 0x5e, 0x08, 0x87, 0x7a, 0xf3, 0x66, 0xbb, 0x9b, + 0xb2, 0x11, 0x3a, 0xb8, 0xbb, 0x8d, 0x7f, 0xc0, 0x1f, 0x6e, 0xba, 0x5d, 0xb0, 0x80, 0x60, 0xb8, + 0x75, 0xe6, 0xbd, 0x37, 0x6f, 0xa7, 0xfb, 0x16, 0x5a, 0x29, 0xd7, 0x1f, 0x28, 0xdf, 0x82, 0xa5, + 0x44, 0x8d, 0xe4, 0x24, 0xc1, 0x60, 0x21, 0x62, 0x89, 0x81, 0xed, 0xfb, 0xc3, 0x44, 0xe8, 0x59, + 0x16, 0x05, 0x31, 0x2e, 0xfa, 0x06, 0xe9, 0x27, 0x78, 0x5b, 0x7c, 0x48, 0xcc, 0x34, 0x97, 0x7d, + 0xa3, 0xb4, 0x45, 0x31, 0x86, 0xb6, 0xa0, 0x39, 0x11, 0x4a, 0x87, 0xfc, 0x3d, 0xe3, 0x4a, 0xd3, + 0x07, 0xf8, 0x57, 0x94, 0x6a, 0x89, 0xa9, 0xe2, 0xe4, 0x06, 0xea, 0x29, 0x32, 0xae, 0xbc, 0x4a, + 0xa7, 0xda, 0x6b, 0x0e, 0xce, 0x83, 0x6d, 0xd7, 0x60, 0x8a, 0x8c, 0x87, 0x05, 0x89, 0x76, 0xe1, + 0x74, 0xca, 0x45, 0x32, 0x8b, 0x30, 0x93, 0x33, 0x44, 0x66, 0xa7, 0x92, 0xff, 0xe0, 0x08, 0xe6, + 0x55, 0x3a, 0x95, 0x5e, 0x23, 0x74, 0x04, 0xa3, 0x2f, 0x70, 0xb6, 0xc5, 0xb3, 0x76, 0x8f, 0xf9, + 0x96, 0x25, 0xc0, 0x68, 0x9a, 0x83, 0x8b, 0x1f, 0x6c, 0x57, 0xb4, 0x70, 0x53, 0x41, 0xef, 0xa0, + 0x96, 0x1f, 0x69, 0xdb, 0x93, 0x78, 0xe0, 0xbe, 0x32, 0x26, 0xb9, 0x52, 0x9e, 0x63, 0x9a, 0xab, + 0x92, 0x8e, 0xc0, 0x1d, 0x63, 0x9a, 0xf2, 0x58, 0x93, 0x6b, 0xa8, 0xe5, 0x9b, 0x58, 0xdb, 0x7d, + 0xdb, 0x1a, 0x0e, 0x1d, 0x42, 0x7d, 0x3c, 0x47, 0xc5, 0x8f, 0x12, 0x8d, 0xc0, 0x7d, 0xc6, 0xb9, + 0x88, 0xc5, 0x71, 0x5e, 0x08, 0x8d, 0xf5, 0xc2, 0xc7, 0x08, 0xc9, 0x3d, 0xc0, 0xfa, 0xf7, 0x28, + 0xaf, 0x7a, 0xf0, 0x12, 0x4b, 0xcc, 0xc1, 0xa7, 0x03, 0xee, 0xb4, 0x00, 0xc9, 0x13, 0x80, 0xc9, + 0x44, 0x1e, 0x1b, 0x45, 0xbc, 0x6f, 0xb5, 0x0d, 0x92, 0xbd, 0x65, 0xbf, 0xbd, 0x83, 0x94, 0xa3, + 0x44, 0xff, 0x90, 0x09, 0x34, 0xf2, 0x4e, 0x6e, 0xa6, 0x48, 0x7b, 0xf7, 0x14, 0xa5, 0x20, 0xfa, + 0x97, 0xfb, 0xe0, 0xf5, 0xb4, 0x08, 0x5a, 0x1b, 0x21, 0x22, 0xdd, 0x03, 0x29, 0x29, 0xa5, 0xd1, + 0xbf, 0xfa, 0x95, 0xb7, 0xf2, 0x88, 0xfe, 0x9a, 0x47, 0x32, 0xfc, 0x0a, 0x00, 0x00, 0xff, 0xff, + 0x59, 0xcf, 0xab, 0xb5, 0x7c, 0x03, 0x00, 0x00, +} diff --git a/network/proto/network.proto b/network/proto/network.proto new file mode 100644 index 00000000..6025d90b --- /dev/null +++ b/network/proto/network.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package go.micro.network; + +import "github.com/micro/go-micro/router/proto/router.proto"; + +// Network service is usesd to gain visibility into networks +service Network { + rpc ListRoutes(go.micro.router.Request) returns (go.micro.router.ListResponse) {}; + rpc ListNodes(ListRequest) returns (ListResponse) {}; + rpc Neighbourhood(NeighbourhoodRequest) returns (NeighbourhoodResponse) {}; +} + +// Empty request +message ListRequest {} + +// ListResponse is returned by ListNodes and ListNeighbours +message ListResponse { + repeated Node nodes = 1; +} + +// NeighbourhoodRequest is sent to query node neighbourhood +message NeighbourhoodRequest { + string id = 1; +} + +// NeighbourhoodResponse contains node neighbourhood hierarchy +message NeighbourhoodResponse { + Neighbour neighbourhood = 1; +} + +// Node is network node +message Node { + // node ide + string id = 1; + // node address + string address = 2; +} + +// Connect is sent when the node connects to the network +message Connect { + // network mode + Node node = 1; +} + +// Close is sent when the node disconnects from the network +message Close { + // network node + Node node = 1; +} + +// Solicit is sent when requesting route advertisement from the network nodes +message Solicit { + // network node + Node node = 1; +} + +// Neighbour is used to nnounce node neighbourhood +message Neighbour { + // network node + Node node = 1; + // neighbours + repeated Node neighbours = 3; +} diff --git a/network/resolver/dns/dns.go b/network/resolver/dns/dns.go index ba109656..3cfa2746 100644 --- a/network/resolver/dns/dns.go +++ b/network/resolver/dns/dns.go @@ -8,6 +8,7 @@ import ( "github.com/micro/go-micro/network/resolver" ) +// Resolver is a DNS network resolve type Resolver struct{} // Resolve assumes ID is a domain name e.g micro.mu diff --git a/network/resolver/http/http.go b/network/resolver/http/http.go index b6025a59..055662a5 100644 --- a/network/resolver/http/http.go +++ b/network/resolver/http/http.go @@ -10,6 +10,7 @@ import ( "github.com/micro/go-micro/network/resolver" ) +// Resolver is a HTTP network resolver type Resolver struct { // If not set, defaults to http Proto string diff --git a/network/resolver/registry/registry.go b/network/resolver/registry/registry.go index 9fe80bc5..20fffc18 100644 --- a/network/resolver/registry/registry.go +++ b/network/resolver/registry/registry.go @@ -6,6 +6,7 @@ import ( "github.com/micro/go-micro/registry" ) +// Resolver is a registry network resolver type Resolver struct { // Registry is the registry to use otherwise we use the defaul Registry registry.Registry diff --git a/network/resolver/resolver.go b/network/resolver/resolver.go index 2eb0b2a2..369269df 100644 --- a/network/resolver/resolver.go +++ b/network/resolver/resolver.go @@ -5,7 +5,7 @@ package resolver // via the name to connect to. This is done based on Network.Name(). // Before we can be part of any network, we have to connect to it. type Resolver interface { - // Resolve returns a list of addresses for an name + // Resolve returns a list of addresses for a name Resolve(name string) ([]*Record, error) } diff --git a/plugin/default.go b/plugin/default.go new file mode 100644 index 00000000..bcafbc3b --- /dev/null +++ b/plugin/default.go @@ -0,0 +1,125 @@ +// Package plugin provides the ability to load plugins +package plugin + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + pg "plugin" + "strings" + "text/template" + + "github.com/micro/go-micro/broker" + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/client/selector" + "github.com/micro/go-micro/config/cmd" + "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/server" + "github.com/micro/go-micro/transport" +) + +type plugin struct{} + +// Init sets up the plugin +func (p *plugin) Init(c *Config) error { + switch c.Type { + case "broker": + pg, ok := c.NewFunc.(func(...broker.Option) broker.Broker) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultBrokers[c.Name] = pg + case "client": + pg, ok := c.NewFunc.(func(...client.Option) client.Client) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultClients[c.Name] = pg + case "registry": + pg, ok := c.NewFunc.(func(...registry.Option) registry.Registry) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultRegistries[c.Name] = pg + + case "selector": + pg, ok := c.NewFunc.(func(...selector.Option) selector.Selector) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultSelectors[c.Name] = pg + case "server": + pg, ok := c.NewFunc.(func(...server.Option) server.Server) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultServers[c.Name] = pg + case "transport": + pg, ok := c.NewFunc.(func(...transport.Option) transport.Transport) + if !ok { + return fmt.Errorf("Invalid plugin %s", c.Name) + } + cmd.DefaultTransports[c.Name] = pg + default: + return fmt.Errorf("Unknown plugin type: %s for %s", c.Type, c.Name) + } + + return nil +} + +// Load loads a plugin created with `go build -buildmode=plugin` +func (p *plugin) Load(path string) (*Config, error) { + plugin, err := pg.Open(path) + if err != nil { + return nil, err + } + s, err := plugin.Lookup("Plugin") + if err != nil { + return nil, err + } + pl, ok := s.(*Config) + if !ok { + return nil, errors.New("could not cast Plugin object") + } + return pl, nil +} + +// Generate creates a go file at the specified path. +// You must use `go build -buildmode=plugin`to build it. +func (p *plugin) Generate(path string, c *Config) error { + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + t, err := template.New(c.Name).Parse(tmpl) + if err != nil { + return err + } + return t.Execute(f, c) +} + +// Build generates a dso plugin using the go command `go build -buildmode=plugin` +func (p *plugin) Build(path string, c *Config) error { + path = strings.TrimSuffix(path, ".so") + + // create go file in tmp path + temp := os.TempDir() + base := filepath.Base(path) + goFile := filepath.Join(temp, base+".go") + + // generate .go file + if err := p.Generate(goFile, c); err != nil { + return err + } + // remove .go file + defer os.Remove(goFile) + + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil && !os.IsExist(err) { + return fmt.Errorf("Failed to create dir %s: %v", filepath.Dir(path), err) + } + cmd := exec.Command("go", "build", "-buildmode=plugin", "-o", path+".so", goFile) + return cmd.Run() +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 00000000..c1aecc6e --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,46 @@ +// Package plugin provides the ability to load plugins +package plugin + +// Plugin is a plugin loaded from a file +type Plugin interface { + // Initialise a plugin with the config + Init(c *Config) error + // Load loads a .so plugin at the given path + Load(path string) (*Config, error) + // Build a .so plugin with config at the path specified + Build(path string, c *Config) error +} + +// Config is the plugin config +type Config struct { + // Name of the plugin e.g rabbitmq + Name string + // Type of the plugin e.g broker + Type string + // Path specifies the import path + Path string + // NewFunc creates an instance of the plugin + NewFunc interface{} +} + +var ( + // Default plugin loader + DefaultPlugin = NewPlugin() +) + +// NewPlugin creates a new plugin interface +func NewPlugin() Plugin { + return &plugin{} +} + +func Build(path string, c *Config) error { + return DefaultPlugin.Build(path, c) +} + +func Load(path string) (*Config, error) { + return DefaultPlugin.Load(path) +} + +func Init(c *Config) error { + return DefaultPlugin.Init(c) +} diff --git a/plugin/template.go b/plugin/template.go new file mode 100644 index 00000000..dd559bfa --- /dev/null +++ b/plugin/template.go @@ -0,0 +1,20 @@ +package plugin + +var ( + tmpl = ` +package main + +import ( + "github.com/micro/go-micro/plugin" + + "{{.Path}}" +) + +var Plugin = plugin.Config{ + Name: "{{.Name}}", + Type: "{{.Type}}", + Path: "{{.Path}}", + NewFunc: {{.Name}}.{{.NewFunc}}, +} +` +) diff --git a/proxy/mucp/mucp.go b/proxy/mucp/mucp.go index ec95f407..9fccc6cc 100644 --- a/proxy/mucp/mucp.go +++ b/proxy/mucp/mucp.go @@ -161,9 +161,6 @@ func (p *Proxy) manageRouteCache(route router.Route, action string) error { } p.Routes[route.Service][route.Hash()] = route case "delete": - if _, ok := p.Routes[route.Service]; !ok { - return fmt.Errorf("route not found") - } delete(p.Routes[route.Service], route.Hash()) default: return fmt.Errorf("unknown action: %s", action) @@ -219,6 +216,10 @@ func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server // endpoint to call endpoint := req.Endpoint() + if len(service) == 0 { + return errors.BadRequest("go.micro.proxy", "service name is blank") + } + // are we network routing or local routing if len(p.Links) == 0 { local = true diff --git a/registry/handler/handler.go b/registry/handler/handler.go new file mode 100644 index 00000000..f86148cd --- /dev/null +++ b/registry/handler/handler.go @@ -0,0 +1,76 @@ +package handler + +import ( + "context" + + "github.com/micro/go-micro/errors" + "github.com/micro/go-micro/registry" + pb "github.com/micro/go-micro/registry/proto" + "github.com/micro/go-micro/registry/service" +) + +type Registry struct { + // internal registry + Registry registry.Registry +} + +func (r *Registry) GetService(ctx context.Context, req *pb.GetRequest, rsp *pb.GetResponse) error { + services, err := r.Registry.GetService(req.Service) + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + for _, srv := range services { + rsp.Services = append(rsp.Services, service.ToProto(srv)) + } + return nil +} + +func (r *Registry) Register(ctx context.Context, req *pb.Service, rsp *pb.EmptyResponse) error { + err := r.Registry.Register(service.ToService(req)) + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + return nil +} + +func (r *Registry) Deregister(ctx context.Context, req *pb.Service, rsp *pb.EmptyResponse) error { + err := r.Registry.Deregister(service.ToService(req)) + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + return nil +} + +func (r *Registry) ListServices(ctx context.Context, req *pb.ListRequest, rsp *pb.ListResponse) error { + services, err := r.Registry.ListServices() + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + for _, srv := range services { + rsp.Services = append(rsp.Services, service.ToProto(srv)) + } + return nil +} + +func (r *Registry) Watch(ctx context.Context, req *pb.WatchRequest, rsp pb.Registry_WatchStream) error { + watcher, err := r.Registry.Watch(registry.WatchService(req.Service)) + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + + for { + next, err := watcher.Next() + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + err = rsp.Send(&pb.Result{ + Action: next.Action, + Service: service.ToProto(next.Service), + }) + if err != nil { + return errors.InternalServerError("go.micro.registry", err.Error()) + } + } + + return nil +} diff --git a/registry/mdns/mdns.go b/registry/mdns/mdns.go index 6739c694..f84e8961 100644 --- a/registry/mdns/mdns.go +++ b/registry/mdns/mdns.go @@ -2,6 +2,8 @@ package mdns import ( + "context" + "github.com/micro/go-micro/registry" ) @@ -9,3 +11,13 @@ import ( func NewRegistry(opts ...registry.Option) registry.Registry { return registry.NewRegistry(opts...) } + +// 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) + } +} diff --git a/registry/mdns_registry.go b/registry/mdns_registry.go index 8e68948a..039710a6 100644 --- a/registry/mdns_registry.go +++ b/registry/mdns_registry.go @@ -14,6 +14,11 @@ import ( hash "github.com/mitchellh/hashstructure" ) +var ( + // use a .micro domain rather than .local + mdnsDomain = "micro" +) + type mdnsTxt struct { Service string Version string @@ -29,6 +34,8 @@ type mdnsEntry struct { type mdnsRegistry struct { opts Options + // the mdns domain + domain string sync.Mutex services map[string][]*mdnsEntry @@ -36,11 +43,25 @@ type mdnsRegistry struct { func newRegistry(opts ...Option) Registry { options := Options{ + Context: context.Background(), Timeout: time.Millisecond * 100, } + for _, o := range opts { + o(&options) + } + + // set the domain + domain := mdnsDomain + + d, ok := options.Context.Value("mdns.domain").(string) + if ok { + domain = d + } + return &mdnsRegistry{ opts: options, + domain: domain, services: make(map[string][]*mdnsEntry), } } @@ -66,7 +87,7 @@ func (m *mdnsRegistry) Register(service *Service, opts ...RegisterOption) error s, err := mdns.NewMDNSService( service.Name, "_services", - "", + m.domain+".", "", 9999, []net.IP{net.ParseIP("0.0.0.0")}, @@ -141,7 +162,7 @@ func (m *mdnsRegistry) Register(service *Service, opts ...RegisterOption) error s, err := mdns.NewMDNSService( node.Id, service.Name, - "", + m.domain+".", "", port, []net.IP{net.ParseIP(host)}, @@ -214,6 +235,8 @@ func (m *mdnsRegistry) GetService(service string) ([]*Service, error) { p.Context, _ = context.WithTimeout(context.Background(), m.opts.Timeout) // set entries channel p.Entries = entries + // set the domain + p.Domain = m.domain go func() { for { @@ -223,7 +246,9 @@ func (m *mdnsRegistry) GetService(service string) ([]*Service, error) { if p.Service == "_services" { continue } - + if p.Domain != m.domain { + continue + } if e.TTL == 0 { continue } @@ -288,6 +313,8 @@ func (m *mdnsRegistry) ListServices() ([]*Service, error) { p.Context, _ = context.WithTimeout(context.Background(), m.opts.Timeout) // set entries channel p.Entries = entries + // set domain + p.Domain = m.domain var services []*Service @@ -298,7 +325,9 @@ func (m *mdnsRegistry) ListServices() ([]*Service, error) { 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 @@ -329,9 +358,10 @@ func (m *mdnsRegistry) Watch(opts ...WatchOption) (Watcher, error) { } md := &mdnsWatcher{ - wo: wo, - ch: make(chan *mdns.ServiceEntry, 32), - exit: make(chan struct{}), + wo: wo, + ch: make(chan *mdns.ServiceEntry, 32), + exit: make(chan struct{}), + domain: m.domain, } go func() { diff --git a/registry/mdns_watcher.go b/registry/mdns_watcher.go index e1c326ff..ce13866f 100644 --- a/registry/mdns_watcher.go +++ b/registry/mdns_watcher.go @@ -11,6 +11,8 @@ type mdnsWatcher struct { wo WatchOptions ch chan *mdns.ServiceEntry exit chan struct{} + // the mdns domain + domain string } func (m *mdnsWatcher) Next() (*Result, error) { @@ -46,13 +48,14 @@ func (m *mdnsWatcher) Next() (*Result, error) { Endpoints: txt.Endpoints, } - // TODO: don't hardcode .local. - if !strings.HasSuffix(e.Name, "."+service.Name+".local.") { + // skip anything without the domain we care about + suffix := fmt.Sprintf(".%s.%s.", service.Name, m.domain) + if !strings.HasSuffix(e.Name, suffix) { continue } service.Nodes = append(service.Nodes, &Node{ - Id: strings.TrimSuffix(e.Name, "."+service.Name+".local."), + Id: strings.TrimSuffix(e.Name, suffix), Address: fmt.Sprintf("%s:%d", e.AddrV4.String(), e.Port), Metadata: txt.Metadata, }) diff --git a/registry/proto/registry.micro.go b/registry/proto/registry.micro.go new file mode 100644 index 00000000..3b78fdbf --- /dev/null +++ b/registry/proto/registry.micro.go @@ -0,0 +1,224 @@ +// Code generated by protoc-gen-micro. DO NOT EDIT. +// source: micro/go-micro/registry/proto/registry.proto + +package go_micro_registry + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + math "math" +) + +import ( + context "context" + client "github.com/micro/go-micro/client" + server "github.com/micro/go-micro/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 _ context.Context +var _ client.Option +var _ server.Option + +// 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 { + if c == nil { + c = client.NewClient() + } + if len(name) == 0 { + name = "go.micro.registry" + } + 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 { + 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) 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 { + 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) 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) +} diff --git a/registry/proto/registry.pb.go b/registry/proto/registry.pb.go new file mode 100644 index 00000000..b05602ed --- /dev/null +++ b/registry/proto/registry.pb.go @@ -0,0 +1,848 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: micro/go-micro/registry/proto/registry.proto + +package go_micro_registry + +import ( + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + math "math" +) + +// 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 + +// Service represents a go-micro service +type Service struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Endpoints []*Endpoint `protobuf:"bytes,4,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + Nodes []*Node `protobuf:"bytes,5,rep,name=nodes,proto3" json:"nodes,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Service) Reset() { *m = Service{} } +func (m *Service) String() string { return proto.CompactTextString(m) } +func (*Service) ProtoMessage() {} +func (*Service) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{0} +} + +func (m *Service) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Service.Unmarshal(m, b) +} +func (m *Service) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Service.Marshal(b, m, deterministic) +} +func (m *Service) XXX_Merge(src proto.Message) { + xxx_messageInfo_Service.Merge(m, src) +} +func (m *Service) XXX_Size() int { + return xxx_messageInfo_Service.Size(m) +} +func (m *Service) XXX_DiscardUnknown() { + xxx_messageInfo_Service.DiscardUnknown(m) +} + +var xxx_messageInfo_Service proto.InternalMessageInfo + +func (m *Service) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Service) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *Service) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Service) GetEndpoints() []*Endpoint { + if m != nil { + return m.Endpoints + } + return nil +} + +func (m *Service) GetNodes() []*Node { + if m != nil { + return m.Nodes + } + return nil +} + +// Node represents the node the service is on +type Node struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Port int64 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Node) Reset() { *m = Node{} } +func (m *Node) String() string { return proto.CompactTextString(m) } +func (*Node) ProtoMessage() {} +func (*Node) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{1} +} + +func (m *Node) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Node.Unmarshal(m, b) +} +func (m *Node) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Node.Marshal(b, m, deterministic) +} +func (m *Node) XXX_Merge(src proto.Message) { + xxx_messageInfo_Node.Merge(m, src) +} +func (m *Node) XXX_Size() int { + return xxx_messageInfo_Node.Size(m) +} +func (m *Node) XXX_DiscardUnknown() { + xxx_messageInfo_Node.DiscardUnknown(m) +} + +var xxx_messageInfo_Node proto.InternalMessageInfo + +func (m *Node) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Node) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *Node) GetPort() int64 { + if m != nil { + return m.Port + } + return 0 +} + +func (m *Node) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +// Endpoint is a endpoint provided by a service +type Endpoint struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Request *Value `protobuf:"bytes,2,opt,name=request,proto3" json:"request,omitempty"` + Response *Value `protobuf:"bytes,3,opt,name=response,proto3" json:"response,omitempty"` + Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Endpoint) Reset() { *m = Endpoint{} } +func (m *Endpoint) String() string { return proto.CompactTextString(m) } +func (*Endpoint) ProtoMessage() {} +func (*Endpoint) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{2} +} + +func (m *Endpoint) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Endpoint.Unmarshal(m, b) +} +func (m *Endpoint) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Endpoint.Marshal(b, m, deterministic) +} +func (m *Endpoint) XXX_Merge(src proto.Message) { + xxx_messageInfo_Endpoint.Merge(m, src) +} +func (m *Endpoint) XXX_Size() int { + return xxx_messageInfo_Endpoint.Size(m) +} +func (m *Endpoint) XXX_DiscardUnknown() { + xxx_messageInfo_Endpoint.DiscardUnknown(m) +} + +var xxx_messageInfo_Endpoint proto.InternalMessageInfo + +func (m *Endpoint) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Endpoint) GetRequest() *Value { + if m != nil { + return m.Request + } + return nil +} + +func (m *Endpoint) GetResponse() *Value { + if m != nil { + return m.Response + } + return nil +} + +func (m *Endpoint) GetMetadata() map[string]string { + if m != nil { + return m.Metadata + } + return nil +} + +// Value is an opaque value for a request or response +type Value struct { + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Values []*Value `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Value) Reset() { *m = Value{} } +func (m *Value) String() string { return proto.CompactTextString(m) } +func (*Value) ProtoMessage() {} +func (*Value) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{3} +} + +func (m *Value) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Value.Unmarshal(m, b) +} +func (m *Value) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Value.Marshal(b, m, deterministic) +} +func (m *Value) XXX_Merge(src proto.Message) { + xxx_messageInfo_Value.Merge(m, src) +} +func (m *Value) XXX_Size() int { + return xxx_messageInfo_Value.Size(m) +} +func (m *Value) XXX_DiscardUnknown() { + xxx_messageInfo_Value.DiscardUnknown(m) +} + +var xxx_messageInfo_Value proto.InternalMessageInfo + +func (m *Value) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Value) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *Value) GetValues() []*Value { + if m != nil { + return m.Values + } + return nil +} + +// Result is returns by the watcher +type Result struct { + Action string `protobuf:"bytes,1,opt,name=action,proto3" json:"action,omitempty"` + Service *Service `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Result) Reset() { *m = Result{} } +func (m *Result) String() string { return proto.CompactTextString(m) } +func (*Result) ProtoMessage() {} +func (*Result) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{4} +} + +func (m *Result) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Result.Unmarshal(m, b) +} +func (m *Result) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Result.Marshal(b, m, deterministic) +} +func (m *Result) XXX_Merge(src proto.Message) { + xxx_messageInfo_Result.Merge(m, src) +} +func (m *Result) XXX_Size() int { + return xxx_messageInfo_Result.Size(m) +} +func (m *Result) XXX_DiscardUnknown() { + xxx_messageInfo_Result.DiscardUnknown(m) +} + +var xxx_messageInfo_Result proto.InternalMessageInfo + +func (m *Result) GetAction() string { + if m != nil { + return m.Action + } + return "" +} + +func (m *Result) GetService() *Service { + if m != nil { + return m.Service + } + return nil +} + +func (m *Result) GetTimestamp() int64 { + if m != nil { + return m.Timestamp + } + return 0 +} + +type EmptyResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EmptyResponse) Reset() { *m = EmptyResponse{} } +func (m *EmptyResponse) String() string { return proto.CompactTextString(m) } +func (*EmptyResponse) ProtoMessage() {} +func (*EmptyResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{5} +} + +func (m *EmptyResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EmptyResponse.Unmarshal(m, b) +} +func (m *EmptyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EmptyResponse.Marshal(b, m, deterministic) +} +func (m *EmptyResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_EmptyResponse.Merge(m, src) +} +func (m *EmptyResponse) XXX_Size() int { + return xxx_messageInfo_EmptyResponse.Size(m) +} +func (m *EmptyResponse) XXX_DiscardUnknown() { + xxx_messageInfo_EmptyResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_EmptyResponse proto.InternalMessageInfo + +type GetRequest struct { + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetRequest) Reset() { *m = GetRequest{} } +func (m *GetRequest) String() string { return proto.CompactTextString(m) } +func (*GetRequest) ProtoMessage() {} +func (*GetRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{6} +} + +func (m *GetRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetRequest.Unmarshal(m, b) +} +func (m *GetRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetRequest.Marshal(b, m, deterministic) +} +func (m *GetRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetRequest.Merge(m, src) +} +func (m *GetRequest) XXX_Size() int { + return xxx_messageInfo_GetRequest.Size(m) +} +func (m *GetRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetRequest proto.InternalMessageInfo + +func (m *GetRequest) GetService() string { + if m != nil { + return m.Service + } + return "" +} + +type GetResponse struct { + Services []*Service `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetResponse) Reset() { *m = GetResponse{} } +func (m *GetResponse) String() string { return proto.CompactTextString(m) } +func (*GetResponse) ProtoMessage() {} +func (*GetResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{7} +} + +func (m *GetResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetResponse.Unmarshal(m, b) +} +func (m *GetResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetResponse.Marshal(b, m, deterministic) +} +func (m *GetResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetResponse.Merge(m, src) +} +func (m *GetResponse) XXX_Size() int { + return xxx_messageInfo_GetResponse.Size(m) +} +func (m *GetResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetResponse proto.InternalMessageInfo + +func (m *GetResponse) GetServices() []*Service { + if m != nil { + return m.Services + } + return nil +} + +type ListRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListRequest) Reset() { *m = ListRequest{} } +func (m *ListRequest) String() string { return proto.CompactTextString(m) } +func (*ListRequest) ProtoMessage() {} +func (*ListRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{8} +} + +func (m *ListRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListRequest.Unmarshal(m, b) +} +func (m *ListRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListRequest.Marshal(b, m, deterministic) +} +func (m *ListRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListRequest.Merge(m, src) +} +func (m *ListRequest) XXX_Size() int { + return xxx_messageInfo_ListRequest.Size(m) +} +func (m *ListRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ListRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ListRequest proto.InternalMessageInfo + +type ListResponse struct { + Services []*Service `protobuf:"bytes,1,rep,name=services,proto3" json:"services,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *ListResponse) Reset() { *m = ListResponse{} } +func (m *ListResponse) String() string { return proto.CompactTextString(m) } +func (*ListResponse) ProtoMessage() {} +func (*ListResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{9} +} + +func (m *ListResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ListResponse.Unmarshal(m, b) +} +func (m *ListResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ListResponse.Marshal(b, m, deterministic) +} +func (m *ListResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ListResponse.Merge(m, src) +} +func (m *ListResponse) XXX_Size() int { + return xxx_messageInfo_ListResponse.Size(m) +} +func (m *ListResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ListResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ListResponse proto.InternalMessageInfo + +func (m *ListResponse) GetServices() []*Service { + if m != nil { + return m.Services + } + return nil +} + +type WatchRequest struct { + // service is optional + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WatchRequest) Reset() { *m = WatchRequest{} } +func (m *WatchRequest) String() string { return proto.CompactTextString(m) } +func (*WatchRequest) ProtoMessage() {} +func (*WatchRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f287a6b809166ad2, []int{10} +} + +func (m *WatchRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WatchRequest.Unmarshal(m, b) +} +func (m *WatchRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WatchRequest.Marshal(b, m, deterministic) +} +func (m *WatchRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WatchRequest.Merge(m, src) +} +func (m *WatchRequest) XXX_Size() int { + return xxx_messageInfo_WatchRequest.Size(m) +} +func (m *WatchRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WatchRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WatchRequest proto.InternalMessageInfo + +func (m *WatchRequest) GetService() string { + if m != nil { + return m.Service + } + return "" +} + +func init() { + proto.RegisterType((*Service)(nil), "go.micro.registry.Service") + proto.RegisterMapType((map[string]string)(nil), "go.micro.registry.Service.MetadataEntry") + proto.RegisterType((*Node)(nil), "go.micro.registry.Node") + proto.RegisterMapType((map[string]string)(nil), "go.micro.registry.Node.MetadataEntry") + proto.RegisterType((*Endpoint)(nil), "go.micro.registry.Endpoint") + proto.RegisterMapType((map[string]string)(nil), "go.micro.registry.Endpoint.MetadataEntry") + proto.RegisterType((*Value)(nil), "go.micro.registry.Value") + proto.RegisterType((*Result)(nil), "go.micro.registry.Result") + proto.RegisterType((*EmptyResponse)(nil), "go.micro.registry.EmptyResponse") + proto.RegisterType((*GetRequest)(nil), "go.micro.registry.GetRequest") + proto.RegisterType((*GetResponse)(nil), "go.micro.registry.GetResponse") + proto.RegisterType((*ListRequest)(nil), "go.micro.registry.ListRequest") + proto.RegisterType((*ListResponse)(nil), "go.micro.registry.ListResponse") + proto.RegisterType((*WatchRequest)(nil), "go.micro.registry.WatchRequest") +} + +func init() { + proto.RegisterFile("micro/go-micro/registry/proto/registry.proto", fileDescriptor_f287a6b809166ad2) +} + +var fileDescriptor_f287a6b809166ad2 = []byte{ + // 577 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0x6d, 0x8b, 0xd3, 0x4c, + 0x14, 0x6d, 0x92, 0xbe, 0xde, 0x6e, 0x9f, 0x47, 0x2f, 0xa2, 0x31, 0xbe, 0x95, 0x80, 0x52, 0xc1, + 0xcd, 0x2e, 0x75, 0x11, 0x5f, 0x3e, 0x09, 0x5b, 0x17, 0x64, 0x57, 0x70, 0x04, 0xfd, 0x1c, 0x9b, + 0x4b, 0x0d, 0x6e, 0x5e, 0x9c, 0x99, 0x16, 0xfa, 0x1f, 0x04, 0xff, 0x84, 0x3f, 0xc5, 0x1f, 0x26, + 0x99, 0xcc, 0x34, 0x5d, 0x36, 0xa9, 0x1f, 0x56, 0xbf, 0xcd, 0xcd, 0x9c, 0x73, 0xe6, 0x9e, 0x73, + 0x67, 0x5a, 0x78, 0x92, 0xc4, 0x73, 0x9e, 0x1d, 0x2c, 0xb2, 0xfd, 0x72, 0xc1, 0x69, 0x11, 0x0b, + 0xc9, 0xd7, 0x07, 0x39, 0xcf, 0x64, 0x55, 0x06, 0xaa, 0xc4, 0xeb, 0x8b, 0x2c, 0x50, 0xb8, 0xc0, + 0x6c, 0xf8, 0x3f, 0x6d, 0xe8, 0x7d, 0x20, 0xbe, 0x8a, 0xe7, 0x84, 0x08, 0xed, 0x34, 0x4c, 0xc8, + 0xb5, 0xc6, 0xd6, 0x64, 0xc0, 0xd4, 0x1a, 0x5d, 0xe8, 0xad, 0x88, 0x8b, 0x38, 0x4b, 0x5d, 0x5b, + 0x7d, 0x36, 0x25, 0x1e, 0x43, 0x3f, 0x21, 0x19, 0x46, 0xa1, 0x0c, 0x5d, 0x67, 0xec, 0x4c, 0x86, + 0xd3, 0x49, 0x70, 0x49, 0x3f, 0xd0, 0xda, 0xc1, 0x99, 0x86, 0xce, 0x52, 0xc9, 0xd7, 0x6c, 0xc3, + 0xc4, 0x17, 0x30, 0xa0, 0x34, 0xca, 0xb3, 0x38, 0x95, 0xc2, 0x6d, 0x2b, 0x99, 0x3b, 0x35, 0x32, + 0x33, 0x8d, 0x61, 0x15, 0x1a, 0xf7, 0xa1, 0x93, 0x66, 0x11, 0x09, 0xb7, 0xa3, 0x68, 0xb7, 0x6a, + 0x68, 0xef, 0xb2, 0x88, 0x58, 0x89, 0xf2, 0x5e, 0xc1, 0xe8, 0x42, 0x13, 0x78, 0x0d, 0x9c, 0xaf, + 0xb4, 0xd6, 0x6e, 0x8b, 0x25, 0xde, 0x80, 0xce, 0x2a, 0x3c, 0x5f, 0x92, 0xb6, 0x5a, 0x16, 0x2f, + 0xed, 0xe7, 0x96, 0xff, 0xcb, 0x82, 0x76, 0x21, 0x86, 0xff, 0x81, 0x1d, 0x47, 0x9a, 0x63, 0xc7, + 0x51, 0x91, 0x4f, 0x18, 0x45, 0x9c, 0x84, 0x30, 0xf9, 0xe8, 0xb2, 0x48, 0x33, 0xcf, 0xb8, 0x74, + 0x9d, 0xb1, 0x35, 0x71, 0x98, 0x5a, 0xe3, 0xeb, 0xad, 0xcc, 0x4a, 0xb3, 0x0f, 0x1b, 0xba, 0x6e, + 0x0a, 0xec, 0x6a, 0x36, 0xbe, 0xdb, 0xd0, 0x37, 0x51, 0xd6, 0x8e, 0x7b, 0x0a, 0x3d, 0x4e, 0xdf, + 0x96, 0x24, 0xa4, 0x22, 0x0f, 0xa7, 0x6e, 0x4d, 0x7f, 0x1f, 0x0b, 0x3d, 0x66, 0x80, 0x78, 0x04, + 0x7d, 0x4e, 0x22, 0xcf, 0x52, 0x41, 0xca, 0xec, 0x2e, 0xd2, 0x06, 0x89, 0xb3, 0x4b, 0x51, 0x3c, + 0xde, 0x31, 0xf7, 0x7f, 0x13, 0x47, 0x08, 0x1d, 0xd5, 0x56, 0x6d, 0x14, 0x08, 0x6d, 0xb9, 0xce, + 0x0d, 0x4b, 0xad, 0xf1, 0x10, 0xba, 0x8a, 0x2d, 0xf4, 0x8d, 0x6f, 0x36, 0xaa, 0x71, 0xbe, 0x84, + 0x2e, 0x23, 0xb1, 0x3c, 0x97, 0x78, 0x13, 0xba, 0xe1, 0x5c, 0x16, 0x0f, 0xa9, 0x3c, 0x45, 0x57, + 0x78, 0x04, 0x3d, 0x51, 0x3e, 0x12, 0x1d, 0xb9, 0xd7, 0xfc, 0x8c, 0x98, 0x81, 0xe2, 0x5d, 0x18, + 0xc8, 0x38, 0x21, 0x21, 0xc3, 0x24, 0xd7, 0x57, 0xac, 0xfa, 0xe0, 0xff, 0x0f, 0xa3, 0x59, 0x92, + 0xcb, 0x35, 0xd3, 0x69, 0xfb, 0x8f, 0x00, 0x4e, 0x48, 0x32, 0x3d, 0x31, 0xb7, 0x3a, 0xb2, 0xec, + 0xc5, 0x94, 0xfe, 0x0c, 0x86, 0x0a, 0xa7, 0x87, 0xf4, 0x0c, 0xfa, 0x7a, 0x47, 0xb8, 0x96, 0x72, + 0xbc, 0xab, 0xb9, 0x0d, 0xd6, 0x1f, 0xc1, 0xf0, 0x34, 0x16, 0xe6, 0x3c, 0xff, 0x0d, 0xec, 0x95, + 0xe5, 0x15, 0x65, 0x27, 0xb0, 0xf7, 0x29, 0x94, 0xf3, 0x2f, 0x7f, 0xf4, 0x31, 0xfd, 0xe1, 0x40, + 0x9f, 0x69, 0x21, 0x3c, 0x53, 0xe6, 0xcd, 0xaf, 0xdc, 0xbd, 0x9a, 0xa3, 0xaa, 0x6c, 0xbc, 0xfb, + 0x4d, 0xdb, 0x3a, 0xc9, 0x16, 0xbe, 0x35, 0xd2, 0xc4, 0x71, 0x47, 0xdf, 0xde, 0xb8, 0xee, 0x3e, + 0x5f, 0x98, 0x4a, 0x0b, 0x4f, 0x01, 0x8e, 0x89, 0xff, 0x2d, 0xb5, 0xf7, 0x65, 0xce, 0x9a, 0x22, + 0xb0, 0xce, 0xcb, 0xd6, 0x5c, 0xbc, 0x07, 0x8d, 0xfb, 0x1b, 0xc9, 0x13, 0xe8, 0xa8, 0xc8, 0xb1, + 0x0e, 0xbb, 0x3d, 0x0c, 0xef, 0x76, 0x0d, 0xa0, 0xbc, 0xfa, 0x7e, 0xeb, 0xd0, 0xfa, 0xdc, 0x55, + 0x7f, 0x41, 0x4f, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x0b, 0x12, 0xd6, 0xb2, 0x06, 0x00, + 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// RegistryClient is the client API for Registry service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type RegistryClient interface { + GetService(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) + Register(ctx context.Context, in *Service, opts ...grpc.CallOption) (*EmptyResponse, error) + Deregister(ctx context.Context, in *Service, opts ...grpc.CallOption) (*EmptyResponse, error) + ListServices(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) + Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (Registry_WatchClient, error) +} + +type registryClient struct { + cc *grpc.ClientConn +} + +func NewRegistryClient(cc *grpc.ClientConn) RegistryClient { + return ®istryClient{cc} +} + +func (c *registryClient) GetService(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) { + out := new(GetResponse) + err := c.cc.Invoke(ctx, "/go.micro.registry.Registry/GetService", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) Register(ctx context.Context, in *Service, opts ...grpc.CallOption) (*EmptyResponse, error) { + out := new(EmptyResponse) + err := c.cc.Invoke(ctx, "/go.micro.registry.Registry/Register", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) Deregister(ctx context.Context, in *Service, opts ...grpc.CallOption) (*EmptyResponse, error) { + out := new(EmptyResponse) + err := c.cc.Invoke(ctx, "/go.micro.registry.Registry/Deregister", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) ListServices(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { + out := new(ListResponse) + err := c.cc.Invoke(ctx, "/go.micro.registry.Registry/ListServices", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *registryClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (Registry_WatchClient, error) { + stream, err := c.cc.NewStream(ctx, &_Registry_serviceDesc.Streams[0], "/go.micro.registry.Registry/Watch", opts...) + if err != nil { + return nil, err + } + x := ®istryWatchClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Registry_WatchClient interface { + Recv() (*Result, error) + grpc.ClientStream +} + +type registryWatchClient struct { + grpc.ClientStream +} + +func (x *registryWatchClient) Recv() (*Result, error) { + m := new(Result) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// RegistryServer is the server API for Registry service. +type RegistryServer 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(*WatchRequest, Registry_WatchServer) error +} + +func RegisterRegistryServer(s *grpc.Server, srv RegistryServer) { + s.RegisterService(&_Registry_serviceDesc, srv) +} + +func _Registry_GetService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).GetService(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.registry.Registry/GetService", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).GetService(ctx, req.(*GetRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Service) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.registry.Registry/Register", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).Register(ctx, req.(*Service)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_Deregister_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Service) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).Deregister(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.registry.Registry/Deregister", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).Deregister(ctx, req.(*Service)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_ListServices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServer).ListServices(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/go.micro.registry.Registry/ListServices", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServer).ListServices(ctx, req.(*ListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Registry_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RegistryServer).Watch(m, ®istryWatchServer{stream}) +} + +type Registry_WatchServer interface { + Send(*Result) error + grpc.ServerStream +} + +type registryWatchServer struct { + grpc.ServerStream +} + +func (x *registryWatchServer) Send(m *Result) error { + return x.ServerStream.SendMsg(m) +} + +var _Registry_serviceDesc = grpc.ServiceDesc{ + ServiceName: "go.micro.registry.Registry", + HandlerType: (*RegistryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetService", + Handler: _Registry_GetService_Handler, + }, + { + MethodName: "Register", + Handler: _Registry_Register_Handler, + }, + { + MethodName: "Deregister", + Handler: _Registry_Deregister_Handler, + }, + { + MethodName: "ListServices", + Handler: _Registry_ListServices_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Watch", + Handler: _Registry_Watch_Handler, + ServerStreams: true, + }, + }, + Metadata: "micro/go-micro/registry/proto/registry.proto", +} diff --git a/registry/proto/registry.proto b/registry/proto/registry.proto new file mode 100644 index 00000000..8d7cc854 --- /dev/null +++ b/registry/proto/registry.proto @@ -0,0 +1,73 @@ +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; +} + +// 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; +} + +// 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; +} + +message GetResponse { + repeated Service services = 1; +} + +message ListRequest { + // TODO: filtering +} + +message ListResponse { + repeated Service services = 1; +} + +message WatchRequest { + // service is optional + string service = 1; +} diff --git a/registry/service/service.go b/registry/service/service.go new file mode 100644 index 00000000..2473c32f --- /dev/null +++ b/registry/service/service.go @@ -0,0 +1,155 @@ +// Package service uses the registry service +package service + +import ( + "context" + "time" + + "github.com/micro/go-micro/client" + "github.com/micro/go-micro/registry" + pb "github.com/micro/go-micro/registry/proto" +) + +var ( + // The default service name + DefaultService = "go.micro.service" +) + +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) + } + 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) + } + + // register the service + _, err := s.client.Register(context.TODO(), ToProto(srv), s.callOpts()...) + if err != nil { + return err + } + + return nil +} + +func (s *serviceRegistry) Deregister(srv *registry.Service) error { + // deregister the service + _, err := s.client.Deregister(context.TODO(), ToProto(srv), s.callOpts()...) + if err != nil { + return err + } + return nil +} + +func (s *serviceRegistry) GetService(name string) ([]*registry.Service, error) { + rsp, err := s.client.GetService(context.TODO(), &pb.GetRequest{ + Service: name, + }, s.callOpts()...) + + if err != nil { + return nil, err + } + + var services []*registry.Service + for _, service := range rsp.Services { + services = append(services, ToService(service)) + } + return services, nil +} + +func (s *serviceRegistry) ListServices() ([]*registry.Service, error) { + rsp, err := s.client.ListServices(context.TODO(), &pb.ListRequest{}, s.callOpts()...) + if err != nil { + return nil, err + } + + var services []*registry.Service + 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) + } + + stream, err := s.client.Watch(context.TODO(), &pb.WatchRequest{ + Service: options.Service, + }, s.callOpts()...) + + if err != nil { + return nil, err + } + + return newWatcher(stream), nil +} + +func (s *serviceRegistry) String() string { + return s.name +} + +// NewRegistry returns a new registry service client +func NewRegistry(opts ...registry.Option) registry.Registry { + var options registry.Options + for _, o := range opts { + o(&options) + } + + // use mdns to find the service registry + mReg := registry.NewRegistry() + + // create new client with mdns + cli := client.NewClient( + client.Registry(mReg), + ) + + // service name + // TODO: accept option + name := DefaultService + + return &serviceRegistry{ + opts: options, + name: name, + address: options.Addrs, + client: pb.NewRegistryService(name, cli), + } +} diff --git a/registry/service/util.go b/registry/service/util.go new file mode 100644 index 00000000..bbde4095 --- /dev/null +++ b/registry/service/util.go @@ -0,0 +1,133 @@ +package service + +import ( + "github.com/micro/go-micro/registry" + pb "github.com/micro/go-micro/registry/proto" +) + +func values(v []*registry.Value) []*pb.Value { + if len(v) == 0 { + return []*pb.Value{} + } + + var vs []*pb.Value + 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{} + } + + var vs []*registry.Value + 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 { + var endpoints []*pb.Endpoint + 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, + }) + } + + var nodes []*pb.Node + + 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, + } +} + +func ToService(s *pb.Service) *registry.Service { + var endpoints []*registry.Endpoint + 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, + }) + } + + var nodes []*registry.Node + 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, + } +} diff --git a/registry/service/watcher.go b/registry/service/watcher.go new file mode 100644 index 00000000..08f2144d --- /dev/null +++ b/registry/service/watcher.go @@ -0,0 +1,49 @@ +package service + +import ( + "github.com/micro/go-micro/registry" + pb "github.com/micro/go-micro/registry/proto" +) + +type serviceWatcher struct { + stream pb.Registry_WatchService + closed chan bool +} + +func (s *serviceWatcher) Next() (*registry.Result, error) { + for { + // 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), + } +} diff --git a/router/default.go b/router/default.go index bf3f0e60..cfc30548 100644 --- a/router/default.go +++ b/router/default.go @@ -10,6 +10,7 @@ import ( "github.com/google/uuid" "github.com/micro/go-micro/registry" + "github.com/micro/go-micro/util/log" ) const ( @@ -43,7 +44,7 @@ var ( // router implements default router type router struct { sync.RWMutex - opts Options + options Options status Status table *table exit chan struct{} @@ -70,7 +71,7 @@ func newRouter(opts ...Option) Router { status := Status{Code: Stopped, Error: nil} return &router{ - opts: options, + options: options, status: status, table: newTable(), advertWg: &sync.WaitGroup{}, @@ -85,7 +86,7 @@ func (r *router) Init(opts ...Option) error { defer r.Unlock() for _, o := range opts { - o(&r.opts) + o(&r.options) } return nil @@ -94,10 +95,10 @@ func (r *router) Init(opts ...Option) error { // Options returns router options func (r *router) Options() Options { r.Lock() - opts := r.opts + options := r.options r.Unlock() - return opts + return options } // Table returns routing table @@ -120,6 +121,9 @@ func (r *router) manageRoute(route Route, action string) error { if err := r.table.Delete(route); err != nil && err != ErrRouteNotFound { return fmt.Errorf("failed deleting route for service %s: %s", route.Service, err) } + case "solicit": + // nothing to do here + return nil default: return fmt.Errorf("failed to manage route for service %s. Unknown action: %s", route.Service, action) } @@ -139,7 +143,8 @@ func (r *router) manageServiceRoutes(service *registry.Service, action string) e Service: service.Name, Address: node.Address, Gateway: "", - Network: r.opts.Network, + Network: r.options.Network, + Router: r.options.Id, Link: DefaultLink, Metric: DefaultLocalMetric, } @@ -278,13 +283,14 @@ func (r *router) publishAdvert(advType AdvertType, events []*Event) { defer r.advertWg.Done() a := &Advert{ - Id: r.opts.Id, + Id: r.options.Id, Type: advType, TTL: DefaultAdvertTTL, Timestamp: time.Now(), Events: events, } + log.Debugf("Router publishing advert; %+v", a) r.RLock() for _, sub := range r.subscribers { // check the exit chan first @@ -529,20 +535,22 @@ func (r *router) Start() error { } // add all local service routes into the routing table - if err := r.manageRegistryRoutes(r.opts.Registry, "create"); err != nil { + if err := r.manageRegistryRoutes(r.options.Registry, "create"); err != nil { e := fmt.Errorf("failed adding registry routes: %s", err) r.status = Status{Code: Error, Error: e} return e } // add default gateway into routing table - if r.opts.Gateway != "" { + if r.options.Gateway != "" { // note, the only non-default value is the gateway route := Route{ Service: "*", Address: "*", - Gateway: r.opts.Gateway, + Gateway: r.options.Gateway, Network: "*", + Router: r.options.Id, + Link: DefaultLink, Metric: DefaultLocalMetric, } if err := r.table.Create(route); err != nil { @@ -557,7 +565,7 @@ func (r *router) Start() error { r.exit = make(chan struct{}) // registry watcher - regWatcher, err := r.opts.Registry.Watch() + regWatcher, err := r.options.Registry.Watch() if err != nil { e := fmt.Errorf("failed creating registry watcher: %v", err) r.status = Status{Code: Error, Error: e} @@ -595,25 +603,14 @@ func (r *router) Advertise() (<-chan *Advert, error) { switch r.status.Code { case Advertising: - advertChan := make(chan *Advert) + advertChan := make(chan *Advert, 128) r.subscribers[uuid.New().String()] = advertChan return advertChan, nil case Running: - // list routing table routes to announce - routes, err := r.table.List() + // list all the routes and pack them into even slice to advertise + events, err := r.flushRouteEvents(Create) if err != nil { - return nil, fmt.Errorf("failed listing routes: %s", err) - } - - // collect all the added routes before we attempt to add default gateway - events := make([]*Event, len(routes)) - for i, route := range routes { - event := &Event{ - Type: Create, - Timestamp: time.Now(), - Route: route, - } - events[i] = event + return nil, fmt.Errorf("failed to flush routes: %s", err) } // create event channels @@ -646,7 +643,7 @@ func (r *router) Advertise() (<-chan *Advert, error) { r.status = Status{Code: Advertising, Error: nil} // create advert channel - advertChan := make(chan *Advert) + advertChan := make(chan *Advert, 128) r.subscribers[uuid.New().String()] = advertChan return advertChan, nil @@ -669,6 +666,10 @@ func (r *router) Process(a *Advert) error { }) for _, event := range events { + // skip if the router is the origin of this route + if event.Route.Router == r.options.Id { + continue + } // create a copy of the route route := event.Route action := event.Type @@ -680,6 +681,43 @@ func (r *router) Process(a *Advert) error { return nil } +// flushRouteEvents returns a slice of events, one per each route in the routing table +func (r *router) flushRouteEvents(evType EventType) ([]*Event, error) { + // list all routes + routes, err := r.table.List() + if err != nil { + return nil, fmt.Errorf("failed listing routes: %s", err) + } + + // build a list of events to advertise + events := make([]*Event, len(routes)) + for i, route := range routes { + event := &Event{ + Type: evType, + Timestamp: time.Now(), + Route: route, + } + events[i] = event + } + + return events, nil +} + +// Solicit advertises all of its routes to the network +// It returns error if the router fails to list the routes +func (r *router) Solicit() error { + events, err := r.flushRouteEvents(Update) + if err != nil { + return fmt.Errorf("failed solicit routes: %s", err) + } + + // advertise the routes + r.advertWg.Add(1) + go r.publishAdvert(RouteUpdate, events) + + return nil +} + // Lookup routes in the routing table func (r *router) Lookup(q Query) ([]Route, error) { return r.table.Query(q) diff --git a/router/handler/router.go b/router/handler/router.go index a7571024..ac05f012 100644 --- a/router/handler/router.go +++ b/router/handler/router.go @@ -33,6 +33,7 @@ func (r *Router) Lookup(ctx context.Context, req *pb.LookupRequest, resp *pb.Loo Address: route.Address, Gateway: route.Gateway, Network: route.Network, + Router: route.Router, Link: route.Link, Metric: int64(route.Metric), } @@ -58,6 +59,7 @@ func (r *Router) Advertise(ctx context.Context, req *pb.Request, stream pb.Route Address: event.Route.Address, Gateway: event.Route.Gateway, Network: event.Route.Network, + Router: event.Route.Router, Link: event.Route.Link, Metric: int64(event.Route.Metric), } @@ -97,6 +99,7 @@ func (r *Router) Process(ctx context.Context, req *pb.Advert, rsp *pb.ProcessRes Address: event.Route.Address, Gateway: event.Route.Gateway, Network: event.Route.Network, + Router: event.Route.Router, Link: event.Route.Link, Metric: int(event.Route.Metric), } @@ -161,6 +164,7 @@ func (r *Router) Watch(ctx context.Context, req *pb.WatchRequest, stream pb.Rout Address: event.Route.Address, Gateway: event.Route.Gateway, Network: event.Route.Network, + Router: event.Route.Router, Link: event.Route.Link, Metric: int64(event.Route.Metric), } diff --git a/router/handler/table.go b/router/handler/table.go index ad17fa51..63e3c96c 100644 --- a/router/handler/table.go +++ b/router/handler/table.go @@ -18,6 +18,7 @@ func (t *Table) Create(ctx context.Context, route *pb.Route, resp *pb.CreateResp Address: route.Address, Gateway: route.Gateway, Network: route.Network, + Router: route.Router, Link: route.Link, Metric: int(route.Metric), }) @@ -34,6 +35,7 @@ func (t *Table) Update(ctx context.Context, route *pb.Route, resp *pb.UpdateResp Address: route.Address, Gateway: route.Gateway, Network: route.Network, + Router: route.Router, Link: route.Link, Metric: int(route.Metric), }) @@ -50,6 +52,7 @@ func (t *Table) Delete(ctx context.Context, route *pb.Route, resp *pb.DeleteResp Address: route.Address, Gateway: route.Gateway, Network: route.Network, + Router: route.Router, Link: route.Link, Metric: int(route.Metric), }) @@ -74,6 +77,7 @@ func (t *Table) List(ctx context.Context, req *pb.Request, resp *pb.ListResponse Address: route.Address, Gateway: route.Gateway, Network: route.Network, + Router: route.Router, Link: route.Link, Metric: int64(route.Metric), } @@ -102,6 +106,7 @@ func (t *Table) Query(ctx context.Context, req *pb.QueryRequest, resp *pb.QueryR Address: route.Address, Gateway: route.Gateway, Network: route.Network, + Router: route.Router, Link: route.Link, Metric: int64(route.Metric), } diff --git a/router/proto/router.pb.go b/router/proto/router.pb.go index c448e484..6dedf13d 100644 --- a/router/proto/router.pb.go +++ b/router/proto/router.pb.go @@ -672,10 +672,12 @@ type Route struct { Gateway string `protobuf:"bytes,3,opt,name=gateway,proto3" json:"gateway,omitempty"` // the network for this destination Network string `protobuf:"bytes,4,opt,name=network,proto3" json:"network,omitempty"` + // router if the router id + Router string `protobuf:"bytes,5,opt,name=router,proto3" json:"router,omitempty"` // the network link - Link string `protobuf:"bytes,5,opt,name=link,proto3" json:"link,omitempty"` + Link string `protobuf:"bytes,6,opt,name=link,proto3" json:"link,omitempty"` // the metric / score of this route - Metric int64 `protobuf:"varint,6,opt,name=metric,proto3" json:"metric,omitempty"` + Metric int64 `protobuf:"varint,7,opt,name=metric,proto3" json:"metric,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -734,6 +736,13 @@ func (m *Route) GetNetwork() string { return "" } +func (m *Route) GetRouter() string { + if m != nil { + return m.Router + } + return "" +} + func (m *Route) GetLink() string { if m != nil { return m.Link @@ -861,51 +870,51 @@ func init() { } var fileDescriptor_6a36eee0b1adf739 = []byte{ - // 689 bytes of a gzipped FileDescriptorProto + // 699 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x4e, 0xdb, 0x40, - 0x10, 0xb6, 0x93, 0xd8, 0x28, 0xd3, 0x10, 0xdc, 0x51, 0x05, 0x56, 0x5a, 0x20, 0xf2, 0x29, 0x42, - 0xd4, 0xa9, 0xd2, 0x6b, 0xff, 0x02, 0xa5, 0xaa, 0x54, 0x0e, 0xad, 0x0b, 0xea, 0xd9, 0xd8, 0x23, - 0x6a, 0x91, 0xd8, 0x66, 0x77, 0x03, 0xca, 0xb9, 0x8f, 0xd1, 0x27, 0xe8, 0x73, 0xf5, 0xda, 0x87, - 0xa8, 0xbc, 0xbb, 0x0e, 0x21, 0xc6, 0x48, 0x70, 0xf2, 0xce, 0xdf, 0x37, 0xff, 0x63, 0x18, 0x4c, - 0x93, 0x88, 0x65, 0xc3, 0xf3, 0xec, 0xa5, 0x7a, 0xb0, 0x6c, 0x26, 0x88, 0x0d, 0x73, 0x96, 0x89, - 0x92, 0xf0, 0x25, 0x81, 0x1b, 0xe7, 0x99, 0x2f, 0x75, 0x7c, 0xc5, 0xf6, 0xda, 0xb0, 0x16, 0xd0, - 0xe5, 0x8c, 0xb8, 0xf0, 0xde, 0x41, 0xe7, 0x38, 0xe1, 0x22, 0x20, 0x9e, 0x67, 0x29, 0x27, 0xf4, - 0xc1, 0x96, 0x4a, 0xdc, 0x35, 0xfb, 0xcd, 0xc1, 0x93, 0xd1, 0xa6, 0xbf, 0x62, 0xec, 0x07, 0xc5, - 0x27, 0xd0, 0x5a, 0xde, 0x5b, 0x58, 0x3f, 0xce, 0xb2, 0x8b, 0x59, 0xae, 0x01, 0x71, 0x1f, 0xac, - 0xcb, 0x19, 0xb1, 0xb9, 0x6b, 0xf6, 0xcd, 0x3b, 0xed, 0xbf, 0x15, 0xd2, 0x40, 0x29, 0x79, 0x1f, - 0xa0, 0x5b, 0x9a, 0x3f, 0x32, 0x80, 0x37, 0xd0, 0x51, 0x88, 0x8f, 0xf2, 0xff, 0x1e, 0xd6, 0xb5, - 0xf5, 0x23, 0xdd, 0x77, 0xa1, 0xf3, 0x23, 0x14, 0xd1, 0xcf, 0xb2, 0x9e, 0x7f, 0x4c, 0xb0, 0xc7, - 0xf1, 0x15, 0x31, 0x81, 0x5d, 0x68, 0x24, 0xb1, 0x0c, 0xa3, 0x1d, 0x34, 0x92, 0x18, 0x87, 0xd0, - 0x12, 0xf3, 0x9c, 0xdc, 0x46, 0xdf, 0x1c, 0x74, 0x47, 0xcf, 0x2b, 0xc0, 0xca, 0xec, 0x64, 0x9e, - 0x53, 0x20, 0x15, 0xf1, 0x05, 0xb4, 0x45, 0x32, 0x25, 0x2e, 0xc2, 0x69, 0xee, 0x36, 0xfb, 0xe6, - 0xa0, 0x19, 0xdc, 0x30, 0xd0, 0x81, 0xa6, 0x10, 0x13, 0xb7, 0x25, 0xf9, 0xc5, 0xb3, 0x88, 0x9d, - 0xae, 0x28, 0x15, 0xdc, 0xb5, 0x6a, 0x62, 0x3f, 0x2a, 0xc4, 0x81, 0xd6, 0xf2, 0x9e, 0xc2, 0xc6, - 0x57, 0x96, 0x45, 0xc4, 0x79, 0x99, 0xbe, 0xe7, 0x40, 0xf7, 0x90, 0x51, 0x28, 0x68, 0x99, 0xf3, - 0x91, 0x26, 0x74, 0x9b, 0x73, 0x9a, 0xc7, 0xcb, 0x3a, 0xbf, 0x4c, 0xb0, 0x24, 0x34, 0xfa, 0x3a, - 0x47, 0x53, 0xe6, 0xd8, 0xbb, 0x3b, 0x80, 0xba, 0x14, 0x1b, 0xab, 0x29, 0xee, 0x83, 0x25, 0xed, - 0x64, 0xf2, 0xf5, 0xbd, 0x50, 0x4a, 0xde, 0x29, 0x58, 0xb2, 0x97, 0xe8, 0xc2, 0x1a, 0x27, 0x76, - 0x95, 0x44, 0xa4, 0xab, 0x5f, 0x92, 0x85, 0xe4, 0x3c, 0x14, 0x74, 0x1d, 0xce, 0xa5, 0xb3, 0x76, - 0x50, 0x92, 0x85, 0x24, 0x25, 0x71, 0x9d, 0xb1, 0x0b, 0xe9, 0xac, 0x1d, 0x94, 0xa4, 0xf7, 0xdb, - 0x04, 0x4b, 0xfa, 0xb9, 0x1f, 0x37, 0x8c, 0x63, 0x46, 0x9c, 0x97, 0xb8, 0x9a, 0x5c, 0xf6, 0xd8, - 0xac, 0xf5, 0xd8, 0xba, 0xe5, 0x11, 0x11, 0x5a, 0x93, 0x24, 0xbd, 0x70, 0x2d, 0xc9, 0x96, 0x6f, - 0xdc, 0x04, 0x7b, 0x4a, 0x82, 0x25, 0x91, 0x6b, 0xcb, 0x2a, 0x69, 0xca, 0x1b, 0x81, 0xfd, 0x5d, - 0x84, 0x62, 0xc6, 0x0b, 0xab, 0x28, 0x8b, 0xcb, 0xd0, 0xe4, 0x1b, 0x9f, 0x81, 0x45, 0x8c, 0x65, - 0x4c, 0x47, 0xa5, 0x08, 0x6f, 0x0c, 0x5d, 0x65, 0xb3, 0x98, 0xfa, 0x21, 0xd8, 0x5c, 0x72, 0xf4, - 0xd6, 0x6c, 0x55, 0x2a, 0xad, 0x0d, 0xb4, 0xda, 0xde, 0x08, 0xe0, 0x66, 0x5c, 0x11, 0xa1, 0xab, - 0xa8, 0x71, 0x9a, 0x66, 0xb3, 0x34, 0x22, 0xc7, 0x40, 0x07, 0x3a, 0x8a, 0xa7, 0x66, 0xc5, 0x31, - 0xf7, 0x86, 0xd0, 0x5e, 0xb4, 0x1f, 0x01, 0x6c, 0x35, 0x68, 0x8e, 0x51, 0xbc, 0xd5, 0x88, 0x39, - 0x66, 0xf1, 0xd6, 0x06, 0x8d, 0xd1, 0xbf, 0x06, 0xd8, 0xb2, 0xf2, 0x0c, 0xbf, 0x80, 0xad, 0xee, - 0x04, 0xee, 0x54, 0x42, 0xbb, 0x75, 0x7f, 0x7a, 0xbb, 0xb5, 0x72, 0x3d, 0xac, 0x06, 0x1e, 0x80, - 0x25, 0x77, 0x16, 0xb7, 0x2b, 0xba, 0xcb, 0xbb, 0xdc, 0xab, 0xd9, 0x1f, 0xcf, 0x78, 0x65, 0xe2, - 0x01, 0xb4, 0x55, 0x7a, 0x09, 0x27, 0x74, 0xab, 0x83, 0xa9, 0x21, 0xb6, 0x6a, 0xb6, 0x5c, 0x62, - 0x7c, 0x82, 0x35, 0xbd, 0x7f, 0x58, 0xa7, 0xd7, 0xeb, 0x57, 0x04, 0xab, 0x2b, 0x6b, 0xe0, 0xd1, - 0x62, 0x06, 0xea, 0x03, 0xd9, 0xad, 0xeb, 0xe8, 0x02, 0x66, 0xf4, 0xb7, 0x01, 0xd6, 0x49, 0x78, - 0x36, 0x21, 0x3c, 0x2c, 0x9b, 0x83, 0x35, 0x2b, 0x77, 0x07, 0xdc, 0xca, 0xd9, 0x30, 0x0a, 0x10, - 0xd5, 0xd5, 0x07, 0x80, 0xac, 0x5c, 0x1a, 0x09, 0xa2, 0xc6, 0xe1, 0x01, 0x20, 0x2b, 0xc7, 0xc9, - 0xc0, 0x31, 0xb4, 0x8a, 0x7f, 0xdc, 0x3d, 0xd5, 0xa9, 0x0e, 0xc2, 0xf2, 0x4f, 0xd1, 0x33, 0xf0, - 0x73, 0x79, 0x5b, 0xb6, 0x6b, 0xfe, 0x27, 0x1a, 0x68, 0xa7, 0x4e, 0x5c, 0x22, 0x9d, 0xd9, 0xf2, - 0x9f, 0xfc, 0xfa, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8c, 0xd0, 0xc0, 0x27, 0xbf, 0x07, 0x00, - 0x00, + 0x10, 0xb6, 0x9d, 0xd8, 0x91, 0xa7, 0xc1, 0xb8, 0xa3, 0x0a, 0xac, 0xb4, 0x40, 0xe4, 0x53, 0x84, + 0xa8, 0x53, 0xa5, 0xd7, 0xfe, 0x05, 0x4a, 0x55, 0xa9, 0x1c, 0x5a, 0x17, 0xd4, 0xb3, 0xb1, 0x57, + 0xd4, 0x22, 0xb1, 0xcd, 0xee, 0x06, 0x94, 0x73, 0x9f, 0xa6, 0xe7, 0x3e, 0x52, 0xaf, 0x7d, 0x88, + 0xca, 0xbb, 0xeb, 0x10, 0x62, 0x8c, 0x44, 0x4e, 0xde, 0x99, 0xf9, 0xe6, 0x9b, 0x99, 0xdd, 0x99, + 0x31, 0x0c, 0xa6, 0x69, 0x4c, 0xf3, 0xe1, 0x45, 0xfe, 0x52, 0x1e, 0x68, 0x3e, 0xe3, 0x84, 0x0e, + 0x0b, 0x9a, 0xf3, 0x4a, 0x08, 0x84, 0x80, 0x9b, 0x17, 0x79, 0x20, 0x30, 0x81, 0x54, 0xfb, 0x36, + 0x74, 0x42, 0x72, 0x35, 0x23, 0x8c, 0xfb, 0xef, 0xa0, 0x7b, 0x92, 0x32, 0x1e, 0x12, 0x56, 0xe4, + 0x19, 0x23, 0x18, 0x80, 0x25, 0x40, 0xcc, 0xd3, 0xfb, 0xad, 0xc1, 0x93, 0xd1, 0x56, 0xb0, 0xe2, + 0x1c, 0x84, 0xe5, 0x27, 0x54, 0x28, 0xff, 0x2d, 0x6c, 0x9c, 0xe4, 0xf9, 0xe5, 0xac, 0x50, 0x84, + 0x78, 0x00, 0xe6, 0xd5, 0x8c, 0xd0, 0xb9, 0xa7, 0xf7, 0xf5, 0x7b, 0xfd, 0xbf, 0x95, 0xd6, 0x50, + 0x82, 0xfc, 0x0f, 0xe0, 0x54, 0xee, 0x6b, 0x26, 0xf0, 0x06, 0xba, 0x92, 0x71, 0xad, 0xf8, 0xef, + 0x61, 0x43, 0x79, 0xaf, 0x19, 0xde, 0x81, 0xee, 0x8f, 0x88, 0xc7, 0x3f, 0xab, 0xfb, 0xfc, 0xad, + 0x83, 0x35, 0x4e, 0xae, 0x09, 0xe5, 0xe8, 0x80, 0x91, 0x26, 0x22, 0x0d, 0x3b, 0x34, 0xd2, 0x04, + 0x87, 0xd0, 0xe6, 0xf3, 0x82, 0x78, 0x46, 0x5f, 0x1f, 0x38, 0xa3, 0xe7, 0x35, 0x62, 0xe9, 0x76, + 0x3a, 0x2f, 0x48, 0x28, 0x80, 0xf8, 0x02, 0x6c, 0x9e, 0x4e, 0x09, 0xe3, 0xd1, 0xb4, 0xf0, 0x5a, + 0x7d, 0x7d, 0xd0, 0x0a, 0x6f, 0x15, 0xe8, 0x42, 0x8b, 0xf3, 0x89, 0xd7, 0x16, 0xfa, 0xf2, 0x58, + 0xe6, 0x4e, 0xae, 0x49, 0xc6, 0x99, 0x67, 0x36, 0xe4, 0x7e, 0x5c, 0x9a, 0x43, 0x85, 0xf2, 0x9f, + 0xc2, 0xe6, 0x57, 0x9a, 0xc7, 0x84, 0xb1, 0xaa, 0x7c, 0xdf, 0x05, 0xe7, 0x88, 0x92, 0x88, 0x93, + 0x65, 0xcd, 0x47, 0x32, 0x21, 0x77, 0x35, 0x67, 0x45, 0xb2, 0x8c, 0xf9, 0xa5, 0x83, 0x29, 0xa8, + 0x31, 0x50, 0x35, 0xea, 0xa2, 0xc6, 0xde, 0xfd, 0x09, 0x34, 0x95, 0x68, 0xac, 0x96, 0x78, 0x00, + 0xa6, 0xf0, 0x13, 0xc5, 0x37, 0xbf, 0x85, 0x04, 0xf9, 0x67, 0x60, 0x8a, 0xb7, 0x44, 0x0f, 0x3a, + 0x8c, 0xd0, 0xeb, 0x34, 0x26, 0xea, 0xf6, 0x2b, 0xb1, 0xb4, 0x5c, 0x44, 0x9c, 0xdc, 0x44, 0x73, + 0x11, 0xcc, 0x0e, 0x2b, 0xb1, 0xb4, 0x64, 0x84, 0xdf, 0xe4, 0xf4, 0x52, 0x04, 0xb3, 0xc3, 0x4a, + 0xf4, 0xff, 0xe8, 0x60, 0x8a, 0x38, 0x0f, 0xf3, 0x46, 0x49, 0x42, 0x09, 0x63, 0x15, 0xaf, 0x12, + 0x97, 0x23, 0xb6, 0x1a, 0x23, 0xb6, 0xef, 0x44, 0xc4, 0x2d, 0xd5, 0x83, 0xd4, 0x33, 0x85, 0x41, + 0x49, 0x88, 0xd0, 0x9e, 0xa4, 0xd9, 0xa5, 0x67, 0x09, 0xad, 0x38, 0x97, 0xd8, 0x29, 0xe1, 0x34, + 0x8d, 0xbd, 0x8e, 0xb8, 0x3d, 0x25, 0xf9, 0x23, 0xb0, 0xbe, 0xf3, 0x88, 0xcf, 0x58, 0xe9, 0x15, + 0xe7, 0x49, 0x95, 0xb2, 0x38, 0xe3, 0x33, 0x30, 0x09, 0xa5, 0x39, 0x55, 0xd9, 0x4a, 0xc1, 0x1f, + 0x83, 0x23, 0x7d, 0x16, 0xd3, 0x30, 0x04, 0x8b, 0x09, 0x8d, 0x9a, 0xa6, 0xed, 0xda, 0x0b, 0x28, + 0x07, 0x05, 0xdb, 0x1f, 0x01, 0xdc, 0xb6, 0x31, 0x22, 0x38, 0x52, 0x1a, 0x67, 0x59, 0x3e, 0xcb, + 0x62, 0xe2, 0x6a, 0xe8, 0x42, 0x57, 0xea, 0x64, 0x0f, 0xb9, 0xfa, 0xfe, 0x10, 0xec, 0x45, 0x5b, + 0x20, 0x80, 0x25, 0x1b, 0xd0, 0xd5, 0xca, 0xb3, 0x6c, 0x3d, 0x57, 0x2f, 0xcf, 0xca, 0xc1, 0x18, + 0xfd, 0x33, 0xc0, 0x0a, 0xe5, 0x95, 0x7c, 0x01, 0x4b, 0xee, 0x0f, 0xdc, 0xad, 0xa5, 0x76, 0x67, + 0x2f, 0xf5, 0xf6, 0x1a, 0xed, 0xaa, 0x89, 0x35, 0x3c, 0x04, 0x53, 0xcc, 0x32, 0xee, 0xd4, 0xb0, + 0xcb, 0x33, 0xde, 0x6b, 0x98, 0x2b, 0x5f, 0x7b, 0xa5, 0xe3, 0x21, 0xd8, 0xb2, 0xbc, 0x94, 0x11, + 0xf4, 0xea, 0x0d, 0xab, 0x28, 0xb6, 0x1b, 0xa6, 0x5f, 0x70, 0x7c, 0x82, 0x8e, 0x9a, 0x4b, 0x6c, + 0xc2, 0xf5, 0xfa, 0x35, 0xc3, 0xea, 0x28, 0x6b, 0x78, 0xbc, 0xe8, 0x81, 0xe6, 0x44, 0xf6, 0x9a, + 0x5e, 0x74, 0x41, 0x33, 0xfa, 0x6b, 0x80, 0x79, 0x1a, 0x9d, 0x4f, 0x08, 0x1e, 0x55, 0x8f, 0x83, + 0x0d, 0xa3, 0x78, 0x0f, 0xdd, 0xca, 0x3a, 0xd1, 0x4a, 0x12, 0xf9, 0xaa, 0x8f, 0x20, 0x59, 0xd9, + 0x40, 0x82, 0x44, 0xb6, 0xc3, 0x23, 0x48, 0x56, 0x96, 0x96, 0x86, 0x63, 0x68, 0x97, 0xff, 0xbe, + 0x07, 0x6e, 0xa7, 0xde, 0x08, 0xcb, 0x3f, 0x4b, 0x5f, 0xc3, 0xcf, 0xd5, 0xce, 0xd9, 0x69, 0xf8, + 0xcf, 0x28, 0xa2, 0xdd, 0x26, 0x73, 0xc5, 0x74, 0x6e, 0x89, 0x7f, 0xf5, 0xeb, 0xff, 0x01, 0x00, + 0x00, 0xff, 0xff, 0xe2, 0xe9, 0xe2, 0x3b, 0xd7, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/router/proto/router.proto b/router/proto/router.proto index 3e18d456..9c2ebed4 100644 --- a/router/proto/router.proto +++ b/router/proto/router.proto @@ -7,6 +7,7 @@ service Router { rpc Lookup(LookupRequest) returns (LookupResponse) {}; rpc Watch(WatchRequest) returns (stream Event) {}; rpc Advertise(Request) returns (stream Advert) {}; + rpc Solicit(Request) returns (Response) {}; rpc Process(Advert) returns (ProcessResponse) {}; rpc Status(Request) returns (StatusResponse) {}; } @@ -22,6 +23,9 @@ service Table { // Empty request message Request {} +// Empty response +message Response {} + // ListResponse is returned by List message ListResponse { repeated Route routes = 1; @@ -37,10 +41,12 @@ message LookupResponse { repeated Route routes = 1; } +// QueryRequest queries Table for Routes message QueryRequest{ Query query = 1; } +// QueryResponse is returned by Query message QueryResponse { repeated Route routes = 1; } @@ -117,10 +123,12 @@ message Route { string gateway = 3; // the network for this destination string network = 4; + // router if the router id + string router = 5; // the network link - string link = 5; + string link = 6; // the metric / score of this route - int64 metric = 6; + int64 metric = 7; } message Status { diff --git a/router/query.go b/router/query.go index 68fb3588..3e2c3d38 100644 --- a/router/query.go +++ b/router/query.go @@ -11,29 +11,38 @@ type QueryOptions struct { Gateway string // Network is network address Network string + // Router is router id + Router string } -// QueryService sets destination address +// QueryService sets service to query func QueryService(s string) QueryOption { return func(o *QueryOptions) { o.Service = s } } -// QueryGateway sets route gateway +// QueryGateway sets gateway address to query func QueryGateway(g string) QueryOption { return func(o *QueryOptions) { o.Gateway = g } } -// QueryNetwork sets route network address +// QueryNetwork sets network name to query func QueryNetwork(n string) QueryOption { return func(o *QueryOptions) { o.Network = n } } +// QueryRouter sets router id to query +func QueryRouter(r string) QueryOption { + return func(o *QueryOptions) { + o.Router = r + } +} + // Query is routing table query type Query interface { // Options returns query options @@ -52,6 +61,7 @@ func NewQuery(opts ...QueryOption) Query { Service: "*", Gateway: "*", Network: "*", + Router: "*", } for _, o := range opts { @@ -67,8 +77,3 @@ func NewQuery(opts ...QueryOption) Query { func (q *query) Options() QueryOptions { return q.opts } - -// String prints routing table query in human readable form -func (q query) String() string { - return "query" -} diff --git a/router/route.go b/router/route.go index fa9dbc72..f2768403 100644 --- a/router/route.go +++ b/router/route.go @@ -7,9 +7,9 @@ import ( var ( // DefaultLink is default network link DefaultLink = "local" - // DefaultLocalMetric is default route cost metric for the local network + // DefaultLocalMetric is default route cost for a local route DefaultLocalMetric = 1 - // DefaultNetworkMetric is default route cost metric for the micro network + // DefaultNetworkMetric is default route cost for a network route DefaultNetworkMetric = 10 ) @@ -23,6 +23,8 @@ type Route struct { Gateway string // Network is network address Network string + // Router is router id + Router string // Link is network link Link string // Metric is the route cost metric @@ -33,6 +35,6 @@ type Route struct { func (r *Route) Hash() uint64 { h := fnv.New64() h.Reset() - h.Write([]byte(r.Service + r.Address + r.Gateway + r.Network + r.Link)) + h.Write([]byte(r.Service + r.Address + r.Gateway + r.Network + r.Router + r.Link)) return h.Sum64() } diff --git a/router/router.go b/router/router.go index 7d7ab0de..3e8f00cd 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,17 @@ import ( "time" ) +var ( + // DefaultAddress is default router address + DefaultAddress = ":9093" + // DefaultName is default router service name + DefaultName = "go.micro.router" + // DefaultNetwork is default micro network + DefaultNetwork = "go.micro" + // DefaultRouter is default network router + DefaultRouter = NewRouter() +) + // Router is an interface for a routing control plane type Router interface { // Init initializes the router with options @@ -17,6 +28,8 @@ type Router interface { Advertise() (<-chan *Advert, error) // Process processes incoming adverts Process(*Advert) error + // Solicit advertises the whole routing table to the network + Solicit() error // Lookup queries routes in the routing table Lookup(Query) ([]Route, error) // Watch returns a watcher which tracks updates to the routing table @@ -31,16 +44,17 @@ type Router interface { String() string } +// Table is an interface for routing table type Table interface { // Create new route in the routing table Create(Route) error - // Delete deletes existing route from the routing table + // Delete existing route from the routing table Delete(Route) error - // Update updates route in the routing table + // Update route in the routing table Update(Route) error - // List returns the list of all routes in the table + // List all routes in the table List() ([]Route, error) - // Query queries routes in the routing table + // Query routes in the routing table Query(Query) ([]Route, error) } @@ -125,17 +139,6 @@ type Advert struct { Events []*Event } -var ( - // DefaultAddress is default router address - DefaultAddress = ":9093" - // DefaultName is default router service name - DefaultName = "go.micro.router" - // DefaultNetwork is default micro network - DefaultNetwork = "go.micro" - // DefaultRouter is default network router - DefaultRouter = NewRouter() -) - // NewRouter creates new Router and returns it func NewRouter(opts ...Option) Router { return newRouter(opts...) diff --git a/router/service/service.go b/router/service/service.go index 77619762..f3063546 100644 --- a/router/service/service.go +++ b/router/service/service.go @@ -220,6 +220,42 @@ func (s *svc) Process(advert *router.Advert) error { return nil } +// Solicit advertise all routes +func (s *svc) Solicit() error { + // list all the routes + routes, err := s.table.List() + if err != nil { + return err + } + + // build events to advertise + events := make([]*router.Event, len(routes)) + for i, _ := range events { + events[i] = &router.Event{ + Type: router.Update, + Timestamp: time.Now(), + Route: routes[i], + } + } + + advert := &router.Advert{ + Id: s.opts.Id, + Type: router.RouteUpdate, + Timestamp: time.Now(), + TTL: time.Duration(router.DefaultAdvertTTL), + Events: events, + } + + select { + case s.advertChan <- advert: + case <-s.exit: + close(s.advertChan) + return nil + } + + return nil +} + // Status returns router status func (s *svc) Status() router.Status { s.Lock() diff --git a/router/table.go b/router/table.go index 6b34bb00..c61b560c 100644 --- a/router/table.go +++ b/router/table.go @@ -8,7 +8,14 @@ import ( "github.com/google/uuid" ) -// table is an in memory routing table +var ( + // ErrRouteNotFound is returned when no route was found in the routing table + ErrRouteNotFound = errors.New("route not found") + // ErrDuplicateRoute is returned when the route already exists + ErrDuplicateRoute = errors.New("duplicate route") +) + +// table is an in-memory routing table type table struct { sync.RWMutex // routes stores service routes @@ -25,6 +32,19 @@ func newTable(opts ...Option) *table { } } +// sendEvent sends events to all subscribed watchers +func (t *table) sendEvent(e *Event) { + t.RLock() + defer t.RUnlock() + + for _, w := range t.watchers { + select { + case w.resChan <- e: + case <-w.done: + } + } +} + // Create creates new route in the routing table func (t *table) Create(r Route) error { service := r.Service @@ -63,8 +83,10 @@ func (t *table) Delete(r Route) error { return ErrRouteNotFound } - delete(t.routes[service], sum) - go t.sendEvent(&Event{Type: Delete, Timestamp: time.Now(), Route: r}) + if _, ok := t.routes[service][sum]; ok { + delete(t.routes[service], sum) + go t.sendEvent(&Event{Type: Delete, Timestamp: time.Now(), Route: r}) + } return nil } @@ -85,8 +107,10 @@ func (t *table) Update(r Route) error { return nil } - t.routes[service][sum] = r - go t.sendEvent(&Event{Type: Update, Timestamp: time.Now(), Route: r}) + if _, ok := t.routes[service][sum]; !ok { + t.routes[service][sum] = r + go t.sendEvent(&Event{Type: Update, Timestamp: time.Now(), Route: r}) + } return nil } @@ -106,21 +130,23 @@ func (t *table) List() ([]Route, error) { return routes, nil } -// isMatch checks if the route matches given network and router -func isMatch(route Route, network, router string) bool { - if network == "*" || network == route.Network { - if router == "*" || router == route.Gateway { - return true +// isMatch checks if the route matches given query options +func isMatch(route Route, gateway, network, router string) bool { + if gateway == "*" || gateway == route.Gateway { + if network == "*" || network == route.Network { + if router == "*" || router == route.Router { + return true + } } } return false } // findRoutes finds all the routes for given network and router and returns them -func findRoutes(routes map[uint64]Route, network, router string) []Route { +func findRoutes(routes map[uint64]Route, gateway, network, router string) []Route { var results []Route for _, route := range routes { - if isMatch(route, network, router) { + if isMatch(route, gateway, network, router) { results = append(results, route) } } @@ -136,13 +162,13 @@ func (t *table) Query(q Query) ([]Route, error) { if _, ok := t.routes[q.Options().Service]; !ok { return nil, ErrRouteNotFound } - return findRoutes(t.routes[q.Options().Service], q.Options().Network, q.Options().Gateway), nil + return findRoutes(t.routes[q.Options().Service], q.Options().Gateway, q.Options().Network, q.Options().Router), nil } var results []Route // search through all destinations for _, routes := range t.routes { - results = append(results, findRoutes(routes, q.Options().Network, q.Options().Gateway)...) + results = append(results, findRoutes(routes, q.Options().Gateway, q.Options().Network, q.Options().Router)...) } return results, nil @@ -181,23 +207,3 @@ func (t *table) Watch(opts ...WatchOption) (Watcher, error) { return w, nil } - -// sendEvent sends events to all subscribed watchers -func (t *table) sendEvent(e *Event) { - t.RLock() - defer t.RUnlock() - - for _, w := range t.watchers { - select { - case w.resChan <- e: - case <-w.done: - } - } -} - -var ( - // ErrRouteNotFound is returned when no route was found in the routing table - ErrRouteNotFound = errors.New("route not found") - // ErrDuplicateRoute is returned when the route already exists - ErrDuplicateRoute = errors.New("duplicate route") -) diff --git a/router/table_test.go b/router/table_test.go index 16c73f2f..d989ee3b 100644 --- a/router/table_test.go +++ b/router/table_test.go @@ -9,6 +9,7 @@ func testSetup() (*table, Route) { Service: "dest.svc", Gateway: "dest.gw", Network: "dest.network", + Router: "src.router", Link: "det.link", Metric: 10, } @@ -109,11 +110,13 @@ func TestQuery(t *testing.T) { svc := []string{"svc1", "svc2", "svc3"} net := []string{"net1", "net2", "net1"} gw := []string{"gw1", "gw2", "gw3"} + rtr := []string{"rtr1", "rt2", "rt3"} for i := 0; i < len(svc); i++ { route.Service = svc[i] route.Network = net[i] route.Gateway = gw[i] + route.Router = rtr[i] if err := table.Create(route); err != nil { t.Errorf("error adding route: %s", err) } @@ -127,8 +130,9 @@ func TestQuery(t *testing.T) { t.Errorf("error looking up routes: %s", err) } - // query particular net - query = NewQuery(QueryNetwork("net1")) + // query routes particular network + network := "net1" + query = NewQuery(QueryNetwork(network)) routes, err = table.Query(query) if err != nil { @@ -139,7 +143,13 @@ func TestQuery(t *testing.T) { t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 2, len(routes)) } - // query particular gateway + for _, route := range routes { + if route.Network != network { + t.Errorf("incorrect route returned. Expected network: %s, found: %s", network, route.Network) + } + } + + // query routes for particular gateway gateway := "gw1" query = NewQuery(QueryGateway(gateway)) @@ -156,11 +166,28 @@ func TestQuery(t *testing.T) { t.Errorf("incorrect route returned. Expected gateway: %s, found: %s", gateway, routes[0].Gateway) } - // query particular route - network := "net1" + // query routes for particular router + router := "rtr1" + query = NewQuery(QueryRouter(router)) + + routes, err = table.Query(query) + if err != nil { + t.Errorf("error looking up routes: %s", err) + } + + if len(routes) != 1 { + t.Errorf("incorrect number of routes returned. Expected: %d, found: %d", 1, len(routes)) + } + + if routes[0].Router != router { + t.Errorf("incorrect route returned. Expected router: %s, found: %s", router, routes[0].Router) + } + + // query particular gateway and network query = NewQuery( QueryGateway(gateway), QueryNetwork(network), + QueryRouter(router), ) routes, err = table.Query(query) @@ -180,7 +207,11 @@ func TestQuery(t *testing.T) { t.Errorf("incorrect network returned. Expected network: %s, found: %s", network, routes[0].Network) } - // bullshit route query + if routes[0].Router != router { + t.Errorf("incorrect route returned. Expected router: %s, found: %s", router, routes[0].Router) + } + + // non-existen route query query = NewQuery(QueryService("foobar")) routes, err = table.Query(query) diff --git a/router/watcher.go b/router/watcher.go index f2c8beac..148b88d4 100644 --- a/router/watcher.go +++ b/router/watcher.go @@ -6,6 +6,11 @@ import ( "time" ) +var ( + // ErrWatcherStopped is returned when routing table watcher has been stopped + ErrWatcherStopped = errors.New("watcher stopped") +) + // EventType defines routing table event type EventType int @@ -42,9 +47,6 @@ type Event struct { Route Route } -// WatchOption is used to define what routes to watch in the table -type WatchOption func(*WatchOptions) - // Watcher defines routing table watcher interface // Watcher returns updates to the routing table type Watcher interface { @@ -56,7 +58,11 @@ type Watcher interface { Stop() } +// WatchOption is used to define what routes to watch in the table +type WatchOption func(*WatchOptions) + // WatchOptions are table watcher options +// TODO: expand the options to watch based on other criteria type WatchOptions struct { // Service allows to watch specific service routes Service string @@ -70,6 +76,7 @@ func WatchService(s string) WatchOption { } } +// tableWatcher implements routing table Watcher type tableWatcher struct { sync.RWMutex id string @@ -113,8 +120,3 @@ func (w *tableWatcher) Stop() { close(w.done) } } - -var ( - // ErrWatcherStopped is returned when routing table watcher has been stopped - ErrWatcherStopped = errors.New("watcher stopped") -) diff --git a/service.go b/service.go index e612a4b3..eed7f8ac 100644 --- a/service.go +++ b/service.go @@ -3,6 +3,7 @@ package micro import ( "os" "os/signal" + "strings" "sync" "syscall" @@ -10,7 +11,9 @@ import ( "github.com/micro/go-micro/config/cmd" "github.com/micro/go-micro/debug/handler" "github.com/micro/go-micro/metadata" + "github.com/micro/go-micro/plugin" "github.com/micro/go-micro/server" + "github.com/micro/go-micro/util/log" ) type service struct { @@ -44,6 +47,24 @@ func (s *service) Init(opts ...Option) { } s.once.Do(func() { + // setup the plugins + for _, p := range strings.Split(os.Getenv("MICRO_PLUGIN"), ",") { + if len(p) == 0 { + continue + } + + // load the plugin + c, err := plugin.Load(p) + if err != nil { + log.Fatal(err) + } + + // initialise the plugin + if err := plugin.Init(c); err != nil { + log.Fatal(err) + } + } + // Initialise the command flags, overriding new service _ = s.opts.Cmd.Init( cmd.Broker(&s.opts.Broker), diff --git a/tunnel/broker/broker.go b/tunnel/broker/broker.go index 0a33c610..6778dfaa 100644 --- a/tunnel/broker/broker.go +++ b/tunnel/broker/broker.go @@ -58,7 +58,7 @@ func (t *tunBroker) Disconnect() error { func (t *tunBroker) Publish(topic string, m *broker.Message, opts ...broker.PublishOption) error { // TODO: this is probably inefficient, we might want to just maintain an open connection // it may be easier to add broadcast to the tunnel - c, err := t.tunnel.Dial(topic) + c, err := t.tunnel.Dial(topic, tunnel.DialMulticast()) if err != nil { return err } diff --git a/tunnel/default.go b/tunnel/default.go index 69a5d878..7d265544 100644 --- a/tunnel/default.go +++ b/tunnel/default.go @@ -1,9 +1,9 @@ package tunnel import ( - "crypto/sha256" "errors" - "fmt" + "math/rand" + "strings" "sync" "time" @@ -13,6 +13,8 @@ import ( ) var ( + // DiscoverTime sets the time at which we fire discover messages + DiscoverTime = 60 * time.Second // KeepAliveTime defines time interval we send keepalive messages to outbound links KeepAliveTime = 30 * time.Second // ReconnectTime defines time interval we periodically attempt to reconnect dead links @@ -25,7 +27,10 @@ type tun struct { sync.RWMutex - // tunnel token + // the unique id for this tunnel + id string + + // tunnel token for authentication token string // to indicate if we're connected or not @@ -37,8 +42,8 @@ type tun struct { // close channel closed chan bool - // a map of sockets based on Micro-Tunnel-Id - sockets map[string]*socket + // a map of sessions based on Micro-Tunnel-Channel + sessions map[string]*session // outbound links links map[string]*link @@ -47,13 +52,6 @@ type tun struct { listener transport.Listener } -type link struct { - transport.Socket - id string - loopback bool - lastKeepAlive time.Time -} - // create new tunnel on top of a link func newTunnel(opts ...Option) *tun { options := DefaultOptions() @@ -62,12 +60,13 @@ func newTunnel(opts ...Option) *tun { } return &tun{ - options: options, - token: uuid.New().String(), - send: make(chan *message, 128), - closed: make(chan bool), - sockets: make(map[string]*socket), - links: make(map[string]*link), + options: options, + id: options.Id, + token: options.Token, + send: make(chan *message, 128), + closed: make(chan bool), + sessions: make(map[string]*session), + links: make(map[string]*link), } } @@ -81,54 +80,118 @@ func (t *tun) Init(opts ...Option) error { return nil } -// getSocket returns a socket from the internal socket map. -// It does this based on the Micro-Tunnel-Id and Micro-Tunnel-Session -func (t *tun) getSocket(id, session string) (*socket, bool) { - // get the socket +// getSession returns a session from the internal session map. +// It does this based on the Micro-Tunnel-Channel and Micro-Tunnel-Session +func (t *tun) getSession(channel, session string) (*session, bool) { + // get the session t.RLock() - s, ok := t.sockets[id+session] + s, ok := t.sessions[channel+session] t.RUnlock() return s, ok } -// newSocket creates a new socket and saves it -func (t *tun) newSocket(id, session string) (*socket, bool) { - // hash the id - h := sha256.New() - h.Write([]byte(id)) - id = fmt.Sprintf("%x", h.Sum(nil)) +func (t *tun) delSession(channel, session string) { + t.Lock() + delete(t.sessions, channel+session) + t.Unlock() +} - // new socket - s := &socket{ - id: id, - session: session, +// listChannels returns a list of listening channels +func (t *tun) listChannels() []string { + t.RLock() + defer t.RUnlock() + + var channels []string + for _, session := range t.sessions { + if session.session != "listener" { + continue + } + channels = append(channels, session.channel) + } + return channels +} + +// newSession creates a new session and saves it +func (t *tun) newSession(channel, sessionId string) (*session, bool) { + // new session + s := &session{ + tunnel: t.id, + channel: channel, + session: sessionId, closed: make(chan bool), recv: make(chan *message, 128), send: t.send, wait: make(chan bool), + errChan: make(chan error, 1), } - // save socket + // save session t.Lock() - _, ok := t.sockets[id+session] + _, ok := t.sessions[channel+sessionId] if ok { - // socket already exists + // session already exists t.Unlock() return nil, false } - t.sockets[id+session] = s + t.sessions[channel+sessionId] = s t.Unlock() - // return socket + // return session return s, true } // TODO: use tunnel id as part of the session -func (t *tun) newSession() string { +func (t *tun) newSessionId() string { return uuid.New().String() } +func (t *tun) announce(channel, session string, link *link) { + // create the "announce" response message for a discover request + msg := &transport.Message{ + Header: map[string]string{ + "Micro-Tunnel": "announce", + "Micro-Tunnel-Id": t.id, + "Micro-Tunnel-Channel": channel, + "Micro-Tunnel-Session": session, + "Micro-Tunnel-Link": link.id, + "Micro-Tunnel-Token": t.token, + }, + } + + // if no channel is present we've been asked to discover all channels + if len(channel) == 0 { + // get the list of channels + t.RLock() + channels := t.listChannels() + t.RUnlock() + + // if there are no channels continue + if len(channels) == 0 { + return + } + + // create a list of channels as comma separated list + channel = strings.Join(channels, ",") + // set channels as header + msg.Header["Micro-Tunnel-Channel"] = channel + } else { + // otherwise look for a single channel mapping + // looking for existing mapping as a listener + _, exists := t.getSession(channel, "listener") + if !exists { + return + } + } + + log.Debugf("Tunnel sending announce for discovery of channel(s) %s", channel) + + // send back the announcement + if err := link.Send(msg); err != nil { + log.Debugf("Tunnel failed to send announcement for channel(s) %s message: %v", channel, err) + } +} + // monitor monitors outbound links and attempts to reconnect to the failed ones func (t *tun) monitor() { reconnect := time.NewTicker(ReconnectTime) @@ -139,44 +202,63 @@ func (t *tun) monitor() { case <-t.closed: return case <-reconnect.C: + var connect []string + + // build list of unknown nodes to connect to + t.RLock() for _, node := range t.options.Nodes { - t.Lock() if _, ok := t.links[node]; !ok { - link, err := t.setupLink(node) - if err != nil { - log.Debugf("Tunnel failed to setup node link to %s: %v", node, err) - t.Unlock() - continue - } - t.links[node] = link + connect = append(connect, node) } + } + t.RUnlock() + + for _, node := range connect { + // create new link + link, err := t.setupLink(node) + if err != nil { + log.Debugf("Tunnel failed to setup node link to %s: %v", node, err) + continue + } + // set the link id to the node + // TODO: hash it + link.id = node + // save the link + t.Lock() + t.links[node] = link t.Unlock() } } } } -// process outgoing messages sent by all local sockets +// process outgoing messages sent by all local sessions func (t *tun) process() { // manage the send buffer - // all pseudo sockets throw everything down this + // all pseudo sessions throw everything down this for { select { case msg := <-t.send: newMsg := &transport.Message{ Header: make(map[string]string), - Body: msg.data.Body, } - for k, v := range msg.data.Header { - newMsg.Header[k] = v + // set the data + if msg.data != nil { + for k, v := range msg.data.Header { + newMsg.Header[k] = v + } + newMsg.Body = msg.data.Body } // set message head - newMsg.Header["Micro-Tunnel"] = "message" + newMsg.Header["Micro-Tunnel"] = msg.typ // set the tunnel id on the outgoing message - newMsg.Header["Micro-Tunnel-Id"] = msg.id + newMsg.Header["Micro-Tunnel-Id"] = msg.tunnel + + // set the tunnel channel on the outgoing message + newMsg.Header["Micro-Tunnel-Channel"] = msg.channel // set the session id newMsg.Header["Micro-Tunnel-Session"] = msg.session @@ -186,145 +268,355 @@ func (t *tun) process() { // send the message via the interface t.Lock() + if len(t.links) == 0 { - log.Debugf("No links to send to") + log.Debugf("No links to send message type: %s channel: %s", msg.typ, msg.channel) } + + var sent bool + var err error + for node, link := range t.links { - if link.loopback && msg.outbound { + // if the link is not connected skip it + if !link.connected { + log.Debugf("Link for node %s not connected", node) + err = errors.New("link not connected") continue } + + // if the link was a loopback accepted connection + // and the message is being sent outbound via + // a dialled connection don't use this link + if link.loopback && msg.outbound { + err = errors.New("link is loopback") + continue + } + + // if the message was being returned by the loopback listener + // send it back up the loopback link only + if msg.loopback && !link.loopback { + err = errors.New("link is not loopback") + continue + } + + // check the multicast mappings + if msg.multicast { + link.RLock() + _, ok := link.channels[msg.channel] + link.RUnlock() + // channel mapping not found in link + if !ok { + continue + } + } else { + // if we're picking the link check the id + // this is where we explicitly set the link + // in a message received via the listen method + if len(msg.link) > 0 && link.id != msg.link { + err = errors.New("link not found") + continue + } + } + + // send the message via the current link log.Debugf("Sending %+v to %s", newMsg, node) - if err := link.Send(newMsg); err != nil { - log.Debugf("Tunnel error sending %+v to %s: %v", newMsg, node, err) + + if errr := link.Send(newMsg); errr != nil { + log.Debugf("Tunnel error sending %+v to %s: %v", newMsg, node, errr) + err = errors.New(errr.Error()) + // kill the link + link.Close() + // delete the link delete(t.links, node) continue } + + // is sent + sent = true + + // keep sending broadcast messages + if msg.broadcast || msg.multicast { + continue + } + + // break on unicast + break } + t.Unlock() + + // set the error if not sent + var gerr error + if !sent { + gerr = err + } + + // skip if its not been set + if msg.errChan == nil { + continue + } + + // return error non blocking + select { + case msg.errChan <- gerr: + default: + } case <-t.closed: return } } } +func (t *tun) delLink(id string) { + t.Lock() + defer t.Unlock() + // get the link + link, ok := t.links[id] + if !ok { + return + } + // close and delete + link.Close() + delete(t.links, id) +} + // process incoming messages func (t *tun) listen(link *link) { + // remove the link on exit + defer func() { + log.Debugf("Tunnel deleting connection from %s", link.Remote()) + t.delLink(link.Remote()) + }() + + // let us know if its a loopback + var loopback bool + for { // process anything via the net interface msg := new(transport.Message) - err := link.Recv(msg) - if err != nil { + if err := link.Recv(msg); err != nil { log.Debugf("Tunnel link %s receive error: %#v", link.Remote(), err) - t.Lock() - delete(t.links, link.Remote()) - t.Unlock() return } - switch msg.Header["Micro-Tunnel"] { + // always ensure we have the correct auth token + // TODO: segment the tunnel based on token + // e.g use it as the basis + token := msg.Header["Micro-Tunnel-Token"] + if token != t.token { + log.Debugf("Tunnel link %s received invalid token %s", token) + return + } + + // message type + mtype := msg.Header["Micro-Tunnel"] + // the tunnel id + id := msg.Header["Micro-Tunnel-Id"] + // the tunnel channel + channel := msg.Header["Micro-Tunnel-Channel"] + // the session id + sessionId := msg.Header["Micro-Tunnel-Session"] + + // if its not connected throw away the link + // the first message we process needs to be connect + if !link.connected && mtype != "connect" { + log.Debugf("Tunnel link %s not connected", link.id) + return + } + + switch mtype { case "connect": log.Debugf("Tunnel link %s received connect message", link.Remote()) - // check the Micro-Tunnel-Token - token, ok := msg.Header["Micro-Tunnel-Token"] - if !ok { - continue - } + link.Lock() // are we connecting to ourselves? - if token == t.token { - t.Lock() + if id == t.id { link.loopback = true - t.Unlock() + loopback = true } + // set to remote node + link.id = id + // set as connected + link.connected = true + link.Unlock() + + // save the link once connected + t.Lock() + t.links[link.Remote()] = link + t.Unlock() + + // send back a discovery + go t.announce("", "", link) // nothing more to do continue case "close": - log.Debugf("Tunnel link %s closing connection", link.Remote()) // TODO: handle the close message // maybe report io.EOF or kill the link - continue + + // close the link entirely + if len(channel) == 0 { + log.Debugf("Tunnel link %s received close message", link.Remote()) + return + } + + // the entire listener was closed so remove it from the mapping + if sessionId == "listener" { + link.Lock() + delete(link.channels, channel) + link.Unlock() + continue + } + + // try get the dialing socket + s, exists := t.getSession(channel, sessionId) + if exists { + // close and continue + s.Close() + continue + } + // otherwise its a session mapping of sorts case "keepalive": log.Debugf("Tunnel link %s received keepalive", link.Remote()) t.Lock() + // save the keepalive link.lastKeepAlive = time.Now() t.Unlock() continue - case "message": + // a new connection dialled outbound + case "open": + log.Debugf("Tunnel link %s received open %s %s", link.id, channel, sessionId) + // we just let it pass through to be processed + // an accept returned by the listener + case "accept": + s, exists := t.getSession(channel, sessionId) + // we don't need this + if exists && s.multicast { + s.accepted = true + continue + } + if exists && s.accepted { + continue + } + // a continued session + case "session": // process message log.Debugf("Received %+v from %s", msg, link.Remote()) + // an announcement of a channel listener + case "announce": + // process the announcement + channels := strings.Split(channel, ",") + + // update mapping in the link + link.Lock() + for _, channel := range channels { + link.channels[channel] = time.Now() + } + link.Unlock() + + // this was an announcement not intended for anything + if sessionId == "listener" || sessionId == "" { + continue + } + + // get the session that asked for the discovery + s, exists := t.getSession(channel, sessionId) + if exists { + // don't bother it's already discovered + if s.discovered { + continue + } + + // send the announce back to the caller + s.recv <- &message{ + typ: "announce", + tunnel: id, + channel: channel, + session: sessionId, + link: link.id, + } + } + continue + case "discover": + // send back an announcement + go t.announce(channel, sessionId, link) + continue default: // blackhole it continue } - // strip message header - delete(msg.Header, "Micro-Tunnel") - - // the tunnel id - id := msg.Header["Micro-Tunnel-Id"] - delete(msg.Header, "Micro-Tunnel-Id") - - // the session id - session := msg.Header["Micro-Tunnel-Session"] - delete(msg.Header, "Micro-Tunnel-Session") - - // strip token header - delete(msg.Header, "Micro-Tunnel-Token") + // strip tunnel message header + for k, _ := range msg.Header { + if strings.HasPrefix(k, "Micro-Tunnel") { + delete(msg.Header, k) + } + } // if the session id is blank there's nothing we can do // TODO: check this is the case, is there any reason // why we'd have a blank session? Is the tunnel // used for some other purpose? - if len(id) == 0 || len(session) == 0 { + if len(channel) == 0 || len(sessionId) == 0 { continue } - var s *socket + var s *session var exists bool + // If its a loopback connection then we've enabled link direction + // listening side is used for listening, the dialling side for dialling switch { - case link.loopback: - s, exists = t.getSocket(id, "listener") + case loopback, mtype == "open": + s, exists = t.getSession(channel, "listener") + // only return accept to the session + case mtype == "accept": + log.Debugf("Received accept message for %s %s", channel, sessionId) + s, exists = t.getSession(channel, sessionId) + if exists && s.accepted { + continue + } default: - // get the socket based on the tunnel id and session + // get the session based on the tunnel id and session // this could be something we dialed in which case // we have a session for it otherwise its a listener - s, exists = t.getSocket(id, session) + s, exists = t.getSession(channel, sessionId) if !exists { // try get it based on just the tunnel id // the assumption here is that a listener // has no session but its set a listener session - s, exists = t.getSocket(id, "listener") + s, exists = t.getSession(channel, "listener") } } - // bail if no socket has been found + + // bail if no session or listener has been found if !exists { - log.Debugf("Tunnel skipping no socket exists") + log.Debugf("Tunnel skipping no session %s %s exists", channel, sessionId) // drop it, we don't care about // messages we don't know about continue } - log.Debugf("Tunnel using socket %s %s", s.id, s.session) - // is the socket closed? + // is the session closed? select { case <-s.closed: // closed - delete(t.sockets, id) + delete(t.sessions, channel) continue default: // process } - // is the socket new? + log.Debugf("Tunnel using channel %s session %s", s.channel, s.session) + + // is the session new? select { - // if its new the socket is actually blocked waiting + // if its new the session is actually blocked waiting // for a connection. so we check if its waiting. case <-s.wait: // if its waiting e.g its new then we close it default: - // set remote address of the socket + // set remote address of the session s.remote = msg.Header["Remote"] close(s.wait) } @@ -337,9 +629,14 @@ func (t *tun) listen(link *link) { // construct the internal message imsg := &message{ - id: id, - session: session, - data: tmsg, + tunnel: id, + typ: mtype, + channel: channel, + session: sessionId, + data: tmsg, + link: link.id, + loopback: loopback, + errChan: make(chan error, 1), } // append to recv backlog @@ -351,6 +648,30 @@ func (t *tun) listen(link *link) { } } +// discover sends channel discover requests periodically +func (t *tun) discover(link *link) { + tick := time.NewTicker(DiscoverTime) + defer tick.Stop() + + for { + select { + case <-tick.C: + // send a discovery message to all links + if err := link.Send(&transport.Message{ + Header: map[string]string{ + "Micro-Tunnel": "discover", + "Micro-Tunnel-Id": t.id, + "Micro-Tunnel-Token": t.token, + }, + }); err != nil { + log.Debugf("Tunnel failed to send discover to link %s: %v", link.id, err) + } + case <-t.closed: + return + } + } +} + // keepalive periodically sends keepalive messages to link func (t *tun) keepalive(link *link) { keepalive := time.NewTicker(KeepAliveTime) @@ -366,13 +687,12 @@ func (t *tun) keepalive(link *link) { if err := link.Send(&transport.Message{ Header: map[string]string{ "Micro-Tunnel": "keepalive", + "Micro-Tunnel-Id": t.id, "Micro-Tunnel-Token": t.token, }, }); err != nil { log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err) - t.Lock() - delete(t.links, link.Remote()) - t.Unlock() + t.delLink(link.Remote()) return } } @@ -382,7 +702,7 @@ func (t *tun) keepalive(link *link) { // setupLink connects to node and returns link if successful // It returns error if the link failed to be established func (t *tun) setupLink(node string) (*link, error) { - log.Debugf("Tunnel dialing %s", node) + log.Debugf("Tunnel setting up link: %s", node) c, err := t.options.Transport.Dial(node) if err != nil { log.Debugf("Tunnel failed to connect to %s: %v", node, err) @@ -390,22 +710,24 @@ func (t *tun) setupLink(node string) (*link, error) { } log.Debugf("Tunnel connected to %s", node) + // send the first connect message if err := c.Send(&transport.Message{ Header: map[string]string{ "Micro-Tunnel": "connect", + "Micro-Tunnel-Id": t.id, "Micro-Tunnel-Token": t.token, }, }); err != nil { return nil, err } - // save the link - id := uuid.New().String() - link := &link{ - Socket: c, - id: id, - } - t.links[node] = link + // create a new link + link := newLink(c) + // set link id to remote side + link.id = c.Remote() + // we made the outbound connection + // and sent the connect message + link.connected = true // process incoming messages go t.listen(link) @@ -413,6 +735,9 @@ func (t *tun) setupLink(node string) (*link, error) { // start keepalive monitor go t.keepalive(link) + // discover things on the remote side + go t.discover(link) + return link, nil } @@ -430,30 +755,18 @@ func (t *tun) connect() error { // accept inbound connections err := l.Accept(func(sock transport.Socket) { log.Debugf("Tunnel accepted connection from %s", sock.Remote()) - // save the link - id := uuid.New().String() - t.Lock() - link := &link{ - Socket: sock, - id: id, - } - t.links[sock.Remote()] = link - t.Unlock() - // delete the link - defer func() { - log.Debugf("Tunnel deleting connection from %s", sock.Remote()) - t.Lock() - delete(t.links, sock.Remote()) - t.Unlock() - }() + // create a new link + link := newLink(sock) - // listen for inbound messages + // listen for inbound messages. + // only save the link once connected. + // we do this inside liste t.listen(link) }) - t.Lock() - defer t.Unlock() + t.RLock() + defer t.RUnlock() // still connected but the tunnel died if err != nil && t.connected { @@ -473,8 +786,9 @@ func (t *tun) connect() error { log.Debugf("Tunnel failed to establish node link to %s: %v", node, err) continue } + // save the link - t.links[node] = link + t.links[link.Remote()] = link } // process outbound messages to be sent @@ -511,11 +825,18 @@ func (t *tun) Connect() error { } func (t *tun) close() error { + // close all the sessions + for id, s := range t.sessions { + s.Close() + delete(t.sessions, id) + } + // close all the links for node, link := range t.links { link.Send(&transport.Message{ Header: map[string]string{ "Micro-Tunnel": "close", + "Micro-Tunnel-Id": t.id, "Micro-Tunnel-Token": t.token, }, }) @@ -524,6 +845,7 @@ func (t *tun) close() error { } // close the listener + // this appears to be blocking return t.listener.Close() } @@ -547,16 +869,12 @@ func (t *tun) Close() error { return nil } + log.Debug("Tunnel closing") + select { case <-t.closed: return nil default: - // close all the sockets - for id, s := range t.sockets { - s.Close() - delete(t.sockets, id) - } - // close the connection close(t.closed) t.connected = false @@ -570,60 +888,200 @@ func (t *tun) Close() error { } // Dial an address -func (t *tun) Dial(addr string) (Conn, error) { - log.Debugf("Tunnel dialing %s", addr) - c, ok := t.newSocket(addr, t.newSession()) +func (t *tun) Dial(channel string, opts ...DialOption) (Session, error) { + log.Debugf("Tunnel dialing %s", channel) + c, ok := t.newSession(channel, t.newSessionId()) if !ok { - return nil, errors.New("error dialing " + addr) + return nil, errors.New("error dialing " + channel) } // set remote - c.remote = addr + c.remote = channel // set local c.local = "local" - // outbound socket + // outbound session c.outbound = true + // get opts + options := DialOptions{ + Timeout: DefaultDialTimeout, + } + + for _, o := range opts { + o(&options) + } + + // set the multicast option + c.multicast = options.Multicast + // set the dial timeout + c.timeout = options.Timeout + + now := time.Now() + + after := func() time.Duration { + d := time.Since(now) + // dial timeout minus time since + wait := options.Timeout - d + if wait < time.Duration(0) { + return time.Duration(0) + } + return wait + } + + var links []string + + // non multicast so we need to find the link + t.RLock() + for _, link := range t.links { + link.RLock() + _, ok := link.channels[channel] + link.RUnlock() + + // we have at least one channel mapping + if ok { + c.discovered = true + links = append(links, link.id) + } + } + t.RUnlock() + + // discovered so set the link if not multicast + // TODO: pick the link efficiently based + // on link status and saturation. + if c.discovered && !c.multicast { + // set the link + i := rand.Intn(len(links)) + c.link = links[i] + } + + // shit fuck + if !c.discovered { + // create a new discovery message for this channel + msg := c.newMessage("discover") + msg.broadcast = true + msg.outbound = true + msg.link = "" + + // send the discovery message + t.send <- msg + + select { + case <-time.After(after()): + return nil, ErrDialTimeout + case err := <-c.errChan: + if err != nil { + return nil, err + } + } + + var err error + + // set a dialTimeout + dialTimeout := after() + + // set a shorter delay for multicast + if c.multicast { + // shorten this + dialTimeout = time.Millisecond * 500 + } + + // wait for announce + select { + case msg := <-c.recv: + if msg.typ != "announce" { + err = ErrDiscoverChan + } + case <-time.After(dialTimeout): + err = ErrDialTimeout + } + + // if its multicast just go ahead because this is best effort + if c.multicast { + c.discovered = true + c.accepted = true + return c, nil + } + + // otherwise return an error + if err != nil { + return nil, err + } + + // set discovered to true + c.discovered = true + } + + // a unicast session so we call "open" and wait for an "accept" + + // try to open the session + err := c.Open() + if err != nil { + // delete the session + t.delSession(c.channel, c.session) + return nil, err + } + return c, nil } // Accept a connection on the address -func (t *tun) Listen(addr string) (Listener, error) { - log.Debugf("Tunnel listening on %s", addr) - // create a new socket by hashing the address - c, ok := t.newSocket(addr, "listener") +func (t *tun) Listen(channel string) (Listener, error) { + log.Debugf("Tunnel listening on %s", channel) + + // create a new session by hashing the address + c, ok := t.newSession(channel, "listener") if !ok { - return nil, errors.New("already listening on " + addr) + return nil, errors.New("already listening on " + channel) + } + + delFunc := func() { + t.delSession(channel, "listener") } // set remote. it will be replaced by the first message received c.remote = "remote" // set local - c.local = addr + c.local = channel tl := &tunListener{ - addr: addr, + channel: channel, // the accept channel - accept: make(chan *socket, 128), + accept: make(chan *session, 128), // the channel to close closed: make(chan bool), // tunnel closed channel tunClosed: t.closed, - // the connection - conn: c, - // the listener socket - socket: c, + // the listener session + session: c, + // delete session + delFunc: delFunc, } // this kicks off the internal message processor - // for the listener so it can create pseudo sockets + // for the listener so it can create pseudo sessions // per session if they do not exist or pass messages // to the existign sessions go tl.process() + // announces the listener channel to others + go tl.announce() + // return the listener return tl, nil } +func (t *tun) Links() []Link { + t.RLock() + defer t.RUnlock() + + var links []Link + + for _, link := range t.links { + links = append(links, link) + } + + return links +} + func (t *tun) String() string { return "mucp" } diff --git a/tunnel/link.go b/tunnel/link.go new file mode 100644 index 00000000..b360e826 --- /dev/null +++ b/tunnel/link.go @@ -0,0 +1,101 @@ +package tunnel + +import ( + "sync" + "time" + + "github.com/google/uuid" + "github.com/micro/go-micro/transport" +) + +type link struct { + transport.Socket + + sync.RWMutex + + // unique id of this link e.g uuid + // which we define for ourselves + id string + // whether its a loopback connection + // this flag is used by the transport listener + // which accepts inbound quic connections + loopback bool + // whether its actually connected + // dialled side sets it to connected + // after sending the message. the + // listener waits for the connect + connected bool + // the last time we received a keepalive + // on this link from the remote side + lastKeepAlive time.Time + // channels keeps a mapping of channels and last seen + channels map[string]time.Time + // stop the link + closed chan bool +} + +func newLink(s transport.Socket) *link { + l := &link{ + Socket: s, + id: uuid.New().String(), + channels: make(map[string]time.Time), + closed: make(chan bool), + } + go l.run() + return l +} + +func (l *link) run() { + t := time.NewTicker(time.Minute) + defer t.Stop() + + for { + select { + case <-l.closed: + return + case <-t.C: + // drop any channel mappings older than 2 minutes + var kill []string + killTime := time.Minute * 2 + + l.RLock() + for ch, t := range l.channels { + if d := time.Since(t); d > killTime { + kill = append(kill, ch) + } + } + l.RUnlock() + + // if nothing to kill don't bother with a wasted lock + if len(kill) == 0 { + continue + } + + // kill the channels! + l.Lock() + for _, ch := range kill { + delete(l.channels, ch) + } + l.Unlock() + } + } +} + +func (l *link) Id() string { + l.RLock() + defer l.RUnlock() + + return l.id +} + +func (l *link) Close() error { + select { + case <-l.closed: + return nil + default: + close(l.closed) + return nil + } + + return nil +} diff --git a/network/link/default.go b/tunnel/link/default.go similarity index 100% rename from network/link/default.go rename to tunnel/link/default.go diff --git a/network/link/link.go b/tunnel/link/link.go similarity index 92% rename from network/link/link.go rename to tunnel/link/link.go index 3acfd772..1c42831a 100644 --- a/network/link/link.go +++ b/tunnel/link/link.go @@ -8,8 +8,13 @@ import ( "github.com/micro/go-micro/transport" ) +var ( + // ErrLinkClosed is returned when attempting i/o operation on the closed link + ErrLinkClosed = errors.New("link closed") +) + // Link is a layer on top of a transport socket with the -// buffering send and recv queue's with the ability to +// buffering send and recv queues with the ability to // measure the actual transport link and reconnect if // an address is specified. type Link interface { @@ -28,10 +33,6 @@ type Link interface { Length() int } -var ( - ErrLinkClosed = errors.New("link closed") -) - // NewLink creates a new link on top of a socket func NewLink(opts ...options.Option) Link { return newLink(options.NewOptions(opts...)) diff --git a/tunnel/listener.go b/tunnel/listener.go index 3002e7b6..ee394519 100644 --- a/tunnel/listener.go +++ b/tunnel/listener.go @@ -2,79 +2,148 @@ package tunnel import ( "io" + "time" "github.com/micro/go-micro/util/log" ) type tunListener struct { // address of the listener - addr string + channel string // the accept channel - accept chan *socket + accept chan *session // the channel to close closed chan bool // the tunnel closed channel tunClosed chan bool - // the connection - conn Conn - // the listener socket - socket *socket + // the listener session + session *session + // del func to kill listener + delFunc func() +} + +// periodically announce self +func (t *tunListener) announce() { + tick := time.NewTicker(time.Second * 30) + defer tick.Stop() + + // first announcement + t.session.Announce() + + for { + select { + case <-tick.C: + t.session.Announce() + case <-t.closed: + return + } + } } func (t *tunListener) process() { // our connection map for session - conns := make(map[string]*socket) + conns := make(map[string]*session) + + defer func() { + // close the sessions + for _, conn := range conns { + conn.Close() + } + }() for { select { case <-t.closed: return + case <-t.tunClosed: + t.Close() + return // receive a new message - case m := <-t.socket.recv: - // get a socket - sock, ok := conns[m.session] - log.Debugf("Tunnel listener received id %s session %s exists: %t", m.id, m.session, ok) + case m := <-t.session.recv: + // get a session + sess, ok := conns[m.session] + log.Debugf("Tunnel listener received channel %s session %s exists: %t", m.channel, m.session, ok) if !ok { - // create a new socket session - sock = &socket{ - // our tunnel id - id: m.id, + switch m.typ { + case "open", "session": + default: + continue + } + + // create a new session session + sess = &session{ + // the id of the remote side + tunnel: m.tunnel, + // the channel + channel: m.channel, // the session id session: m.session, + // is loopback conn + loopback: m.loopback, + // the link the message was received on + link: m.link, + // set multicast + multicast: m.multicast, // close chan closed: make(chan bool), // recv called by the acceptor recv: make(chan *message, 128), // use the internal send buffer - send: t.socket.send, + send: t.session.send, // wait wait: make(chan bool), + // error channel + errChan: make(chan error, 1), } - // save the socket - conns[m.session] = sock + // save the session + conns[m.session] = sess - // send to accept chan select { case <-t.closed: return - case t.accept <- sock: + // send to accept chan + case t.accept <- sess: } } + // an existing session was found + + // received a close message + switch m.typ { + case "close": + select { + case <-sess.closed: + // no op + delete(conns, m.session) + default: + // close and delete session + close(sess.closed) + delete(conns, m.session) + } + + // continue + continue + case "session": + // operate on this + default: + // non operational type + continue + } + // send this to the accept chan select { - case <-sock.closed: + case <-sess.closed: delete(conns, m.session) - case sock.recv <- m: - log.Debugf("Tunnel listener sent to recv chan id %s session %s", m.id, m.session) + case sess.recv <- m: + log.Debugf("Tunnel listener sent to recv chan channel %s session %s", m.channel, m.session) } } } } -func (t *tunListener) Addr() string { - return t.addr +func (t *tunListener) Channel() string { + return t.channel } // Close closes tunnel listener @@ -83,26 +152,33 @@ func (t *tunListener) Close() error { case <-t.closed: return nil default: + // close and delete + t.delFunc() + t.session.Close() close(t.closed) } return nil } // Everytime accept is called we essentially block till we get a new connection -func (t *tunListener) Accept() (Conn, error) { +func (t *tunListener) Accept() (Session, error) { select { - // if the socket is closed return + // if the session is closed return case <-t.closed: return nil, io.EOF case <-t.tunClosed: // close the listener when the tunnel closes - t.Close() return nil, io.EOF // wait for a new connection case c, ok := <-t.accept: + // check if the accept chan is closed if !ok { return nil, io.EOF } + // send back the accept + if err := c.Accept(); err != nil { + return nil, err + } return c, nil } return nil, nil diff --git a/tunnel/options.go b/tunnel/options.go index 99406b05..39795671 100644 --- a/tunnel/options.go +++ b/tunnel/options.go @@ -1,6 +1,8 @@ package tunnel import ( + "time" + "github.com/google/uuid" "github.com/micro/go-micro/transport" "github.com/micro/go-micro/transport/quic" @@ -9,6 +11,8 @@ import ( var ( // DefaultAddress is default tunnel bind address DefaultAddress = ":0" + // The shared default token + DefaultToken = "micro" ) type Option func(*Options) @@ -21,10 +25,21 @@ type Options struct { Address string // Nodes are remote nodes Nodes []string + // The shared auth token + Token string // Transport listens to incoming connections Transport transport.Transport } +type DialOption func(*DialOptions) + +type DialOptions struct { + // specify a multicast connection + Multicast bool + // the dial timeout + Timeout time.Duration +} + // The tunnel id func Id(id string) Option { return func(o *Options) { @@ -46,6 +61,13 @@ func Nodes(n ...string) Option { } } +// Token sets the shared token for auth +func Token(t string) Option { + return func(o *Options) { + o.Token = t + } +} + // Transport listens for incoming connections func Transport(t transport.Transport) Option { return func(o *Options) { @@ -58,6 +80,22 @@ func DefaultOptions() Options { return Options{ Id: uuid.New().String(), Address: DefaultAddress, + Token: DefaultToken, Transport: quic.NewTransport(), } } + +// Dial options + +// Dial multicast sets the multicast option to send only to those mapped +func DialMulticast() DialOption { + return func(o *DialOptions) { + o.Multicast = true + } +} + +func DialTimeout(t time.Duration) DialOption { + return func(o *DialOptions) { + o.Timeout = t + } +} diff --git a/tunnel/session.go b/tunnel/session.go new file mode 100644 index 00000000..a4c7a2fe --- /dev/null +++ b/tunnel/session.go @@ -0,0 +1,286 @@ +package tunnel + +import ( + "errors" + "io" + "time" + + "github.com/micro/go-micro/transport" + "github.com/micro/go-micro/util/log" +) + +// session is our pseudo session for transport.Socket +type session struct { + // the tunnel id + tunnel string + // the channel name + channel string + // the session id based on Micro.Tunnel-Session + session string + // closed + closed chan bool + // remote addr + remote string + // local addr + local string + // send chan + send chan *message + // recv chan + recv chan *message + // wait until we have a connection + wait chan bool + // if the discovery worked + discovered bool + // if the session was accepted + accepted bool + // outbound marks the session as outbound dialled connection + outbound bool + // lookback marks the session as a loopback on the inbound + loopback bool + // if the session is multicast + multicast bool + // if the session is broadcast + broadcast bool + // the timeout + timeout time.Duration + // the link on which this message was received + link string + // the error response + errChan chan error +} + +// message is sent over the send channel +type message struct { + // type of message + typ string + // tunnel id + tunnel string + // channel name + channel string + // the session id + session string + // outbound marks the message as outbound + outbound bool + // loopback marks the message intended for loopback + loopback bool + // whether to send as multicast + multicast bool + // broadcast sets the broadcast type + broadcast bool + // the link to send the message on + link string + // transport data + data *transport.Message + // the error channel + errChan chan error +} + +func (s *session) Remote() string { + return s.remote +} + +func (s *session) Local() string { + return s.local +} + +func (s *session) Id() string { + return s.session +} + +func (s *session) Channel() string { + return s.channel +} + +// newMessage creates a new message based on the session +func (s *session) newMessage(typ string) *message { + return &message{ + typ: typ, + tunnel: s.tunnel, + channel: s.channel, + session: s.session, + outbound: s.outbound, + loopback: s.loopback, + multicast: s.multicast, + link: s.link, + errChan: s.errChan, + } +} + +// Open will fire the open message for the session. This is called by the dialler. +func (s *session) Open() error { + // create a new message + msg := s.newMessage("open") + + // send open message + s.send <- msg + + // wait for an error response for send + select { + case err := <-msg.errChan: + if err != nil { + return err + } + case <-s.closed: + return io.EOF + } + + // we don't wait on multicast + if s.multicast { + s.accepted = true + return nil + } + + // now wait for the accept + select { + case msg = <-s.recv: + if msg.typ != "accept" { + log.Debugf("Received non accept message in Open %s", msg.typ) + return errors.New("failed to connect") + } + // set to accepted + s.accepted = true + // set link + s.link = msg.link + case <-time.After(s.timeout): + return ErrDialTimeout + case <-s.closed: + return io.EOF + } + + return nil +} + +// Accept sends the accept response to an open message from a dialled connection +func (s *session) Accept() error { + msg := s.newMessage("accept") + + // send the accept message + select { + case <-s.closed: + return io.EOF + case s.send <- msg: + return nil + } + + // wait for send response + select { + case err := <-s.errChan: + if err != nil { + return err + } + case <-s.closed: + return io.EOF + } + + return nil +} + +// Announce sends an announcement to notify that this session exists. This is primarily used by the listener. +func (s *session) Announce() error { + msg := s.newMessage("announce") + // we don't need an error back + msg.errChan = nil + // announce to all + msg.broadcast = true + // we don't need the link + msg.link = "" + + select { + case s.send <- msg: + return nil + case <-s.closed: + return io.EOF + } +} + +// Send is used to send a message +func (s *session) Send(m *transport.Message) error { + select { + case <-s.closed: + return io.EOF + default: + // no op + } + + // make copy + data := &transport.Message{ + Header: make(map[string]string), + Body: m.Body, + } + + for k, v := range m.Header { + data.Header[k] = v + } + + // create a new message + msg := s.newMessage("session") + // set the data + msg.data = data + + // if multicast don't set the link + if s.multicast { + msg.link = "" + } + + log.Debugf("Appending %+v to send backlog", msg) + // send the actual message + s.send <- msg + + // wait for an error response + select { + case err := <-msg.errChan: + return err + case <-s.closed: + return io.EOF + } + + return nil +} + +// Recv is used to receive a message +func (s *session) Recv(m *transport.Message) error { + select { + case <-s.closed: + return errors.New("session is closed") + default: + // no op + } + // recv from backlog + msg := <-s.recv + + // check the error if one exists + select { + case err := <-msg.errChan: + return err + default: + } + + log.Debugf("Received %+v from recv backlog", msg) + // set message + *m = *msg.data + // return nil + return nil +} + +// Close closes the session by sending a close message +func (s *session) Close() error { + select { + case <-s.closed: + // no op + default: + close(s.closed) + + // append to backlog + msg := s.newMessage("close") + // no error response on close + msg.errChan = nil + + // send the close message + select { + case s.send <- msg: + default: + } + } + + return nil +} diff --git a/tunnel/socket.go b/tunnel/socket.go deleted file mode 100644 index 2590a48e..00000000 --- a/tunnel/socket.go +++ /dev/null @@ -1,115 +0,0 @@ -package tunnel - -import ( - "errors" - - "github.com/micro/go-micro/transport" - "github.com/micro/go-micro/util/log" -) - -// socket is our pseudo socket for transport.Socket -type socket struct { - // socket id based on Micro-Tunnel - id string - // the session id based on Micro.Tunnel-Session - session string - // closed - closed chan bool - // remote addr - remote string - // local addr - local string - // send chan - send chan *message - // recv chan - recv chan *message - // wait until we have a connection - wait chan bool - // outbound marks the socket as outbound - outbound bool -} - -// message is sent over the send channel -type message struct { - // tunnel id - id string - // the session id - session string - // outbound marks the message as outbound - outbound bool - // transport data - data *transport.Message -} - -func (s *socket) Remote() string { - return s.remote -} - -func (s *socket) Local() string { - return s.local -} - -func (s *socket) Id() string { - return s.id -} - -func (s *socket) Session() string { - return s.session -} - -func (s *socket) Send(m *transport.Message) error { - select { - case <-s.closed: - return errors.New("socket is closed") - default: - // no op - } - - // make copy - data := &transport.Message{ - Header: make(map[string]string), - Body: m.Body, - } - - for k, v := range m.Header { - data.Header[k] = v - } - - // append to backlog - msg := &message{ - id: s.id, - session: s.session, - outbound: s.outbound, - data: data, - } - log.Debugf("Appending %+v to send backlog", msg) - s.send <- msg - return nil -} - -func (s *socket) Recv(m *transport.Message) error { - select { - case <-s.closed: - return errors.New("socket is closed") - default: - // no op - } - // recv from backlog - msg := <-s.recv - log.Debugf("Received %+v from recv backlog", msg) - // set message - *m = *msg.data - // return nil - return nil -} - -// Close closes the socket -func (s *socket) Close() error { - select { - case <-s.closed: - // no op - default: - close(s.closed) - } - return nil -} diff --git a/tunnel/transport/listener.go b/tunnel/transport/listener.go index b7a7280c..075f12cf 100644 --- a/tunnel/transport/listener.go +++ b/tunnel/transport/listener.go @@ -10,7 +10,7 @@ type tunListener struct { } func (t *tunListener) Addr() string { - return t.l.Addr() + return t.l.Channel() } func (t *tunListener) Close() error { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index f7bc91cb..14604215 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -2,11 +2,23 @@ package tunnel import ( + "errors" + "time" + "github.com/micro/go-micro/transport" ) -// Tunnel creates a gre network tunnel on top of a link. -// It establishes multiple streams using the Micro-Tunnel-Id header +var ( + // DefaultDialTimeout is the dial timeout if none is specified + DefaultDialTimeout = time.Second * 5 + // ErrDialTimeout is returned by a call to Dial where the timeout occurs + ErrDialTimeout = errors.New("dial timeout") + // ErrDiscoverChan is returned when we failed to receive the "announce" back from a discovery + ErrDiscoverChan = errors.New("failed to discover channel") +) + +// Tunnel creates a gre tunnel on top of the go-micro/transport. +// It establishes multiple streams using the Micro-Tunnel-Channel header // and Micro-Tunnel-Session header. The tunnel id is a hash of // the address being requested. type Tunnel interface { @@ -17,27 +29,37 @@ type Tunnel interface { Connect() error // Close closes the tunnel Close() error - // Dial an endpoint - Dial(addr string) (Conn, error) - // Accept connections - Listen(addr string) (Listener, error) + // Connect to a channel + Dial(channel string, opts ...DialOption) (Session, error) + // Accept connections on a channel + Listen(channel string) (Listener, error) + // All the links the tunnel is connected to + Links() []Link // Name of the tunnel implementation String() string } -// The listener provides similar constructs to the transport.Listener -type Listener interface { - Addr() string - Close() error - Accept() (Conn, error) +// Link represents internal links to the tunnel +type Link interface { + // The id of the link + Id() string + // honours transport socket + transport.Socket } -// Conn is a connection dialed or accepted which includes the tunnel id and session -type Conn interface { - // Specifies the tunnel id +// The listener provides similar constructs to the transport.Listener +type Listener interface { + Accept() (Session, error) + Channel() string + Close() error +} + +// Session is a unique session created when dialling or accepting connections on the tunnel +type Session interface { + // The unique session id Id() string - // The session - Session() string + // The channel name + Channel() string // a transport socket transport.Socket } diff --git a/tunnel/tunnel_test.go b/tunnel/tunnel_test.go index 3fc84b6d..fc76421b 100644 --- a/tunnel/tunnel_test.go +++ b/tunnel/tunnel_test.go @@ -187,30 +187,15 @@ func testBrokenTunAccept(t *testing.T, tun Tunnel, wait chan bool, wg *sync.Wait if err := c.Recv(m); err != nil { t.Fatal(err) } - tun.Close() - // re-start tunnel - err = tun.Connect() - if err != nil { - t.Fatal(err) - } - defer tun.Close() - - // listen on some virtual address - tl, err = tun.Listen("test-tunnel") - if err != nil { - t.Fatal(err) + // close all the links + for _, link := range tun.Links() { + link.Close() } // receiver ready; notify sender wait <- true - // accept a connection - c, err = tl.Accept() - if err != nil { - t.Fatal(err) - } - // accept the message m = new(transport.Message) if err := c.Recv(m); err != nil { @@ -279,6 +264,7 @@ func TestReconnectTunnel(t *testing.T) { if err != nil { t.Fatal(err) } + defer tunB.Close() // we manually override the tunnel.ReconnectTime value here // this is so that we make the reconnects faster than the default 5s @@ -289,6 +275,7 @@ func TestReconnectTunnel(t *testing.T) { if err != nil { t.Fatal(err) } + defer tunA.Close() wait := make(chan bool) diff --git a/util/addr/addr.go b/util/addr/addr.go index b2874533..911dacb0 100644 --- a/util/addr/addr.go +++ b/util/addr/addr.go @@ -40,14 +40,20 @@ func Extract(addr string) (string, error) { } var addrs []net.Addr + var loAddrs []net.Addr for _, iface := range ifaces { ifaceAddrs, err := iface.Addrs() if err != nil { // ignore error, interface can dissapear from system continue } + if iface.Flags&net.FlagLoopback != 0 { + loAddrs = append(loAddrs, ifaceAddrs...) + continue + } addrs = append(addrs, ifaceAddrs...) } + addrs = append(addrs, loAddrs...) var ipAddr []byte var publicIP []byte diff --git a/util/log/log.go b/util/log/log.go index 0b911aae..c8ae4acc 100644 --- a/util/log/log.go +++ b/util/log/log.go @@ -14,7 +14,7 @@ type Level int const ( LevelFatal Level = iota LevelInfo - LevelWarn + LevelError LevelDebug LevelTrace ) @@ -29,16 +29,16 @@ var ( func init() { switch os.Getenv("MICRO_LOG_LEVEL") { + case "trace": + level = LevelTrace case "debug": level = LevelDebug case "info": level = LevelInfo - case "trace": - level = LevelTrace + case "error": + level = LevelError case "fatal": level = LevelFatal - case "warn": - level = LevelWarn } } @@ -98,14 +98,14 @@ func Infof(format string, v ...interface{}) { WithLevelf(LevelInfo, format, v...) } -// Warn provides warn level logging -func Warn(v ...interface{}) { - WithLevel(LevelWarn, v...) +// Error provides warn level logging +func Error(v ...interface{}) { + WithLevel(LevelError, v...) } -// Warnf provides warn level logging -func Warnf(format string, v ...interface{}) { - WithLevelf(LevelWarn, format, v...) +// Errorf provides warn level logging +func Errorf(format string, v ...interface{}) { + WithLevelf(LevelError, format, v...) } // Fatal logs with Log and then exits with os.Exit(1)