Compare commits

...

283 Commits

Author SHA1 Message Date
Asim Aslam
0d85e35da0 Merge pull request #649 from milosgajdos83/tun-types
Rough outline of tunnel types
2019-08-06 11:54:54 +01:00
Milos Gajdos
4074cce397 Rough outline of tunnel types 2019-08-06 11:46:47 +01:00
Asim Aslam
000431f489 Nats addr fix https://github.com/micro/go-micro/pull/648 2019-08-06 09:15:54 +01:00
Asim Aslam
e16420fdbd Consul config fix https://github.com/micro/go-micro/pull/641 2019-08-06 09:15:38 +01:00
Milos Gajdos
52d8d26018 Transport() will return tunnel (pseudo) Transport 2019-08-05 21:09:46 +01:00
Asim Aslam
6649012af3 Merge pull request #645 from milosgajdos83/transport-out
Tunnel no longer embeds transport
2019-08-05 19:57:22 +01:00
Milos Gajdos
6b5dcbf814 Tunnel no longer embeds transport 2019-08-05 19:41:48 +01:00
Asim Aslam
34381213e7 Package comment 2019-08-05 18:04:47 +01:00
Asim Aslam
767292906a Merge pull request #644 from milosgajdos83/tunnel
Adds outline of go-micro Tunnel interface
2019-08-05 17:54:40 +01:00
Milos Gajdos
e1ecd728c5 Adds outline of go-micro Tunnel interface 2019-08-05 17:52:57 +01:00
Asim Aslam
f1b6709722 Fix breaking api changes 2019-08-05 17:47:50 +01:00
Asim Aslam
4030ccc27b Move proxy/router 2019-08-05 17:44:33 +01:00
Asim Aslam
2e67e23a23 Update .travis.yml 2019-08-03 15:16:30 +01:00
Asim Aslam
be229438bc Merge pull request #640 from unistack-org/deadline
transport memory: add Send/Recv Timeout
2019-08-03 13:51:01 +01:00
e1709026e4 transport memory: add Send/Recv Timeout
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-08-03 15:39:44 +03:00
Asim Aslam
d250ac736f In the event watchRegistry or watchTable exit then close the go routines 2019-08-02 15:17:48 +01:00
Asim Aslam
6719f8d655 Remove the table watcher when stopped 2019-08-02 14:59:08 +01:00
Asim Aslam
d7929ef8f3 Stop the ticker when exiting 2019-08-02 14:44:11 +01:00
Asim Aslam
04404441a4 Merge pull request #634 from micro/rcache-stop
Stop a goroutine leak in registy
2019-08-01 23:17:39 +01:00
Asim Aslam
b806e7bdf5 Stop a goroutine leak in registy 2019-08-01 23:03:11 +01:00
Milos Gajdos
2720c6f28e Removed trailing white characters 2019-08-01 13:32:55 +01:00
Asim Aslam
cdf0f14d58 remove this code 2019-07-31 17:19:49 +01:00
Asim Aslam
679c5f0ccd Fix some connection bugs 2019-07-31 16:49:48 +01:00
Asim Aslam
873bfcc73c Process/Stop router 2019-07-31 16:46:55 +01:00
Asim Aslam
7884e889f4 Don't publish the process rpc call and embed the router handler in the network 2019-07-31 16:36:53 +01:00
Asim Aslam
b1c49a0ddc Add router handler 2019-07-31 16:10:04 +01:00
Asim Aslam
318367cd71 move NewNetwork 2019-07-31 15:37:12 +01:00
Asim Aslam
2d09e74b0e add address/advertise 2019-07-31 15:35:51 +01:00
Asim Aslam
3e90d32f29 Add proxy/router 2019-07-31 15:30:51 +01:00
Asim Aslam
fca89e06ef Some network inspiration 2019-07-31 15:22:57 +01:00
Asim Aslam
89fc142e47 Update to use mdns 0.2.0 2019-07-31 13:16:57 +01:00
Asim Aslam
0b2c8ee523 Add top level data package comment 2019-07-30 16:47:18 +01:00
Asim Aslam
852abcaaed yolo commit functioning router code. all credit to the milos gajdos 2019-07-29 18:57:40 +01:00
Asim Aslam
11f80708ce move lock 2019-07-29 12:52:52 +01:00
Asim Aslam
104778e5e5 move lock 2019-07-29 12:52:32 +01:00
Asim Aslam
ae99b9a887 syntactic changes 2019-07-29 12:44:59 +01:00
Asim Aslam
8fdc050e2e syntactic changes 2019-07-29 12:44:28 +01:00
Asim Aslam
8855beb62d syntactic changes 2019-07-29 12:43:20 +01:00
Asim Aslam
47acdf6a4b move Table to table 2019-07-29 12:40:13 +01:00
Asim Aslam
4fc9b9821a Merge pull request #621 from milosgajdos83/no-table-package
[WIP] No table package. router/service package introduced
2019-07-29 12:36:40 +01:00
Asim Aslam
a5fb124b22 update the mdns version 2019-07-29 12:34:00 +01:00
Asim Aslam
5b327ce723 change id to name in resolver 2019-07-28 20:00:09 +01:00
Asim Aslam
2b5bf1154a rename config tests 2019-07-28 19:52:01 +01:00
Asim Aslam
b7b8f8bf11 remove agent readme 2019-07-28 19:47:25 +01:00
Asim Aslam
a63dcda003 Strip the verbosity of the debug handler 2019-07-28 19:43:50 +01:00
Asim Aslam
1db98ee0f0 move all the buffer references to util/buf 2019-07-28 19:33:24 +01:00
Asim Aslam
f2669e7b1e Move connection pool to own package 2019-07-28 18:56:18 +01:00
Asim Aslam
adb6760e21 readd the resolver 2019-07-28 12:14:40 +01:00
Asim Aslam
2b3a87a212 Merge pull request #617 from three-zhang/master
fix bug
2019-07-27 18:08:32 +01:00
Milos Gajdos
3d2ec5dbb1 Regenerated proto because proto reasons. 2019-07-27 16:12:44 +01:00
Milos Gajdos
96f9ce1bd3 Proper router stopping. Printable router status. 2019-07-27 16:11:06 +01:00
Milos Gajdos
cb3052ce04 Proper stopping of service router 2019-07-27 16:11:06 +01:00
Milos Gajdos
2f1658c213 Table package is no more, hence removed references to it 2019-07-27 16:11:06 +01:00
Milos Gajdos
d8b00e801d Stop watcher when router stops. Drain advert channel when stopping. 2019-07-27 16:11:06 +01:00
Milos Gajdos
002abca61f Finished Advertise(). Implemented Process() 2019-07-27 16:11:06 +01:00
Milos Gajdos
c5740ae031 Outline of Advertise, Watch and start of the router. 2019-07-27 16:11:05 +01:00
Milos Gajdos
ddad43bd77 Added service.Router Route CRUD. Outlined watcher and run() 2019-07-27 16:11:05 +01:00
Milos Gajdos
b6fb969ab9 Add List and Lookup implementation. Default error for not implement. 2019-07-27 16:11:05 +01:00
Milos Gajdos
22d0f1f08f Changed documentation. 2019-07-27 16:08:14 +01:00
Milos Gajdos
c3a8146d99 Added outline of router/service package. 2019-07-27 16:08:14 +01:00
Milos Gajdos
2338780a61 Full router RPC coverage 2019-07-27 16:08:14 +01:00
Milos Gajdos
e22c4b4c07 table package is no more. Cleaned up unnecessary code, too. 2019-07-27 16:04:08 +01:00
张三
100cb9db6b fix bug
https://github.com/micro/micro/issues/293
Send request failed using micro Content-Type application/grpc+json
2019-07-27 11:11:16 +08:00
Asim Aslam
4e27aac398 regen router proto 2019-07-26 18:07:36 -07:00
Asim Aslam
7ca06f0c1d set router proto package name to go.micro.router 2019-07-26 18:07:14 -07:00
Asim Aslam
7ca8f8f0ab Merge pull request #611 from milosgajdos83/rpc-router
Adds new RPC methods to router service interface
2019-07-24 13:30:29 -07:00
Milos Gajdos
9ad5ae6644 Adds new RPC methods to router service interface
We have added Advertise() and Process() RPCs in this commit.
2019-07-24 21:07:04 +01:00
Asim Aslam
220a8fafb1 Merge pull request #610 from milosgajdos83/proxy-watch
Adds route watcher to mucp.Proxy
2019-07-24 11:19:52 -07:00
Milos Gajdos
809de7a052 Mutex Unlock when we fail to store route in cache. 2019-07-24 19:13:05 +01:00
Milos Gajdos
23f0231a09 Adds route watcher to mucp.Proxy 2019-07-24 19:03:13 +01:00
Asim Aslam
74cbce72df Merge pull request #609 from milosgajdos83/proxy-router-interface
mucp Proxy no longer uses RPC interface of router.Router directly
2019-07-24 10:40:33 -07:00
Milos Gajdos
b55adc0c30 mucp Proxy no longer uses RPC interface of router.Router directly 2019-07-24 18:32:39 +01:00
Asim Aslam
388ac34b7c Merge pull request #608 from milosgajdos83/router-cleanup
Small router refactoring
2019-07-24 09:46:48 -07:00
Milos Gajdos
13a8cfe7f3 Small function documentation update 2019-07-24 17:22:27 +01:00
Milos Gajdos
1e94d9fe5a Router cleanup and refactoring for win and profit.
This commit adds the following changes to router package:
* it refactors Advertise() function which now does only what
it claims to do: advertising
* various router packages functions/methods have been renamed to make
their functionality more obvious and more in line with what they actually do
* function documentation changes related to the above bullet points
2019-07-24 17:16:52 +01:00
Asim Aslam
49dcc3d1bd Remove readme and examples from web repo 2019-07-22 09:57:34 -07:00
Milos Gajdos
481ebe9d4f Merge pull request #604 from BruceWangNo1/patch-1
Update client.go
2019-07-22 10:55:55 +01:00
Bruce Wang
502f6d3e9f Update client.go
fixed one typo
2019-07-22 15:41:14 +08:00
Asim Aslam
8f2585724c Merge pull request #598 from unistack-org/ipv6fix
bunch of other ipv6 fixes
2019-07-18 07:24:47 -07:00
1217ca94b1 bunch of other ipv6 fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-18 08:59:53 +03:00
Asim Aslam
96cf14ed53 Merge pull request #591 from milosgajdos83/advert-damp
[WIP] Fixes advert route event dampening behaviour
2019-07-17 08:12:35 -07:00
Asim Aslam
3a8edd705c Merge pull request #594 from unistack-org/ipv6
fix ipv6 addr parsing
2019-07-17 07:51:17 -07:00
Milos Gajdos
94b6455577 Increment WaitGroup before launching advertiseEvents goroutine 2019-07-17 13:02:47 +01:00
e688ab0a45 fix ipv6 addr parsing and using
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-17 12:20:29 +03:00
Milos Gajdos
2803146673 Renaming rampage
Addressing the comments in #591, router.String() now returns "default"

Furthermore, a tonne of other renaming has been included in this commit
as a result of running go vet ./... inside the router package.
2019-07-17 00:06:11 +01:00
Asim Aslam
d4fefc4b76 Merge pull request #592 from unistack-org/speedup
changes to minimize allocations and provide useful info
2019-07-16 14:41:11 -07:00
a3bddf5839 changes to minimize allocations and provide useful info
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-17 00:21:03 +03:00
Milos Gajdos
92495d22db Fixes advert dampening behaviour.
This commit adds the following changes:
* advert now stores a list of route events as opposed to just last one
* attempt to dedup route events before appending them to advert
* have max suppress threshold for long time suppressed adverts
* decaying events on every advert tick

Originally we werent decaying penalties on every advert tick.
That was incorrect behaviour. Furthermore some events would end up being
accumulated potentially causing memory leaks.

We were also overriding the last received router event which was causing
incorrect sequence of events to be applied when received by a receiver:
Create, Delete would be "squashed" into Delete only which would be
nonsensical since the Create event would never be delivered hence we
would be deleting nonexistent routes.

Not Decaying the events on every tick or not having the max suppression
threshold could lead to DoS by growing the router memory infinitely.
2019-07-16 19:00:25 +01:00
Asim Aslam
8c7e35c3c6 Merge pull request #587 from milosgajdos83/registry-copy-perf
Preallocate slices in registry.Copy() to avoid append() reallocations when copying data
2019-07-15 07:04:49 -07:00
Milos Gajdos
c108188d65 Preallocate nodes slice in addNodes before populating it 2019-07-15 14:47:33 +01:00
Milos Gajdos
609934ce99 Preallocate slices; avoide append() reallocations when copying data 2019-07-15 11:13:58 +01:00
Asim Aslam
aa79c41fc5 update tunnel comment 2019-07-14 18:34:32 -07:00
Asim Aslam
a549f92dec Merge pull request #585 from unistack-org/transport2
transport memory: fix races
2019-07-13 19:38:22 -07:00
81d2259fac transport memory: fix races
* fix race with rand.Intn for non default source
* increase random interval to avoid issues when many services
  running on the host

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-13 23:47:57 +03:00
Asim Aslam
2fecde1dbb Merge pull request #583 from unistack-org/broker
broker memory: fix issue with publish/subscribe
2019-07-13 00:16:28 +01:00
008749b2b0 broker memory: fix issue with publish/subscribe
mutex locking have errors, so when two service (one pub, other sub)
try to use this broker it waits for mutex release and nothing works

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-13 00:04:53 +03:00
Asim Aslam
3ccb900bca Merge pull request #582 from unistack-org/memory2
memory transport: use write mutex lock when close
2019-07-12 10:29:46 +01:00
a72e1185da memory transport: use write mutex lock when close
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-12 12:11:08 +03:00
Asim Aslam
5157241c88 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-11 21:46:33 +01:00
Asim Aslam
70d811c47a don't use quic in the test 2019-07-11 21:46:27 +01:00
Asim Aslam
b371704444 Merge pull request #581 from milosgajdos83/proto-update
Added proto.Advert, proto.TableEvent is now proto.Event
2019-07-11 21:44:10 +01:00
Asim Aslam
a5f21e69ad Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-11 21:41:00 +01:00
Asim Aslam
6b984136f7 update go mod 2019-07-11 21:40:52 +01:00
Milos Gajdos
9c851f297b Added proto.Advert type to protobuf definitions 2019-07-11 21:14:34 +01:00
Asim Aslam
dac8a13a77 Merge pull request #580 from milosgajdos83/advertise-table
Advertise full table every minute.
2019-07-11 12:49:02 +01:00
Asim Aslam
360e193a01 update go mod 2019-07-11 12:47:50 +01:00
Milos Gajdos
35a1de91a9 Advertise full table every minute. 2019-07-11 12:39:20 +01:00
Asim Aslam
7631463b94 fix compilation errors 2019-07-11 10:47:02 +01:00
Asim Aslam
6581586226 Make tunnel test use quic 2019-07-11 10:34:01 +01:00
Asim Aslam
06c29302d7 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-11 09:38:27 +01:00
Asim Aslam
dab0e9e9bc Set next protos in quic 2019-07-11 09:38:20 +01:00
Asim Aslam
47d91a1f64 Merge pull request #579 from magodo/magodo/store_get_reset_expiry
`memoryStore.Read()` returns honor `Record.Expiry`
2019-07-11 08:39:46 +01:00
magodo
bdeae91063 condense code 2019-07-11 14:13:58 +08:00
magodo
c8d57032bc update expiry only if it is non-zero 2019-07-11 12:58:20 +08:00
magodo
3abe3aa28b store.Read() returns honor Record.Expiry 2019-07-11 12:51:55 +08:00
Asim Aslam
9b1cb4ef0e functioning tunnel with test 2019-07-11 00:55:50 +01:00
Asim Aslam
b4796724d9 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-11 00:14:43 +01:00
Asim Aslam
ae5376cc0e functioning tunnel/link code 2019-07-11 00:14:36 +01:00
Asim Aslam
7bee0629c2 Merge pull request #578 from unistack-org/memory
memory transport: fix race cond on channel close
2019-07-10 23:30:46 +01:00
29fa8de98e memory transport: fix race cond on channel close
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-11 01:21:03 +03:00
Asim Aslam
382fbecd40 Merge pull request #577 from milosgajdos83/update-events
Added update action to handle update registry events. Table.Update inserts when no route found.
2019-07-10 21:54:48 +01:00
Milos Gajdos
a0ee7d2092 Added update action to manageServiceRoutes. Table is embedded; skip opts 2019-07-10 21:28:32 +01:00
Asim Aslam
1f744b31a4 Return the dead node when deleting the service 2019-07-10 21:03:53 +01:00
Asim Aslam
998a23c963 Functional code for link 2019-07-10 20:04:01 +01:00
Asim Aslam
e17ecf66b1 Fix breaking code 2019-07-10 20:03:55 +01:00
Asim Aslam
c5dd737568 Add back in broker address 2019-07-10 19:58:30 +01:00
Asim Aslam
7c29be288b Update a tunnel top level comment 2019-07-10 19:33:34 +01:00
Asim Aslam
217f540601 The listener has no session id 2019-07-10 19:17:36 +01:00
Asim Aslam
ffae0f0fab Add a comment for tunnel processor 2019-07-10 19:13:50 +01:00
Asim Aslam
4cca2b43a3 Add further link comments 2019-07-10 19:11:32 +01:00
Asim Aslam
8c157c1d5f update link comments 2019-07-10 19:09:22 +01:00
Asim Aslam
1f218f7b48 Allow the socket to be specified 2019-07-10 19:07:18 +01:00
Asim Aslam
7e0d4fe0cf Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-10 19:01:38 +01:00
Asim Aslam
0a39fe39c3 Update tunnel to use id+session for the key 2019-07-10 19:01:24 +01:00
Milos Gajdos
163b917ec7 proto.EventType Insert is now Create to mirror table.Event 2019-07-10 18:37:46 +01:00
Asim Aslam
0f16eb2858 add further comments to tunnel 2019-07-10 18:35:10 +01:00
Asim Aslam
89231f701b Add comments and session 2019-07-10 18:26:11 +01:00
Asim Aslam
196e76e350 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-10 18:24:12 +01:00
Asim Aslam
f3d9177233 Add sessions to tunnel 2019-07-10 18:24:03 +01:00
Asim Aslam
8b7ac8a3f9 Merge pull request #576 from milosgajdos83/router-rpc
Added List and Watch rpc calls.
2019-07-10 17:55:23 +01:00
Milos Gajdos
8f5aed707e Table.Add is now Table.Create. Insesrt event is now Create event. 2019-07-10 17:46:22 +01:00
Asim Aslam
c71576a538 Update link Id comment 2019-07-10 17:43:36 +01:00
Asim Aslam
27cfc06828 Cleanup and move around the link code 2019-07-10 17:42:41 +01:00
Asim Aslam
717ba4b3c0 Add tunnel comments 2019-07-10 17:41:17 +01:00
Asim Aslam
4e3a230356 top level package comment 2019-07-10 17:40:14 +01:00
Asim Aslam
66c2519696 Add Tunnel: an interface for stream duplexing over a link 2019-07-10 17:36:04 +01:00
Milos Gajdos
86dfa82dfa Added List and Watch rpc calls. 2019-07-10 17:21:55 +01:00
Asim Aslam
55f8045a70 Add link: a layer ontop of a transport socket 2019-07-10 17:12:51 +01:00
Asim Aslam
b23d955536 Use gateway if available 2019-07-10 08:26:33 +01:00
Asim Aslam
5b565f9f10 update comment 2019-07-10 07:56:52 +01:00
Asim Aslam
9955ed2034 move table 2019-07-10 07:56:18 +01:00
Asim Aslam
c36107e811 cleanup consts 2019-07-10 07:51:24 +01:00
Asim Aslam
a08b64c8ab remove the string methods 2019-07-10 07:50:33 +01:00
Asim Aslam
64ec0633a3 Fix breaks and go fmt 2019-07-10 07:47:17 +01:00
Asim Aslam
0a1b657221 visual cleanup of router code 2019-07-10 07:45:27 +01:00
Asim Aslam
34967e8e33 Merge pull request #573 from milosgajdos83/flap-detection
Router rework. Flap detection. Table package.
2019-07-10 07:12:18 +01:00
Asim Aslam
eda380284c remove network 2019-07-09 18:45:14 +01:00
Asim Aslam
0bf54c122f move transport back 2019-07-09 18:41:26 +01:00
Asim Aslam
97282a5377 remove resolver 2019-07-09 16:54:44 +01:00
Asim Aslam
b642d5e1c0 remove proto dir 2019-07-09 16:53:30 +01:00
Asim Aslam
c5a282ddd3 remove the tunnel 2019-07-09 16:52:44 +01:00
Milos Gajdos
6cf8bde612 Router selector and proxy modifications due to Route struct changes. 2019-07-09 16:45:31 +01:00
Asim Aslam
327029beff fix string method 2019-07-09 16:44:43 +01:00
Asim Aslam
c5214c931f reorder and reword 2019-07-09 16:38:44 +01:00
Asim Aslam
d725980444 add some initialisers 2019-07-09 16:37:59 +01:00
Milos Gajdos
23cb811f60 Removed fmt.Stringer artistry from all roouter and table structs 2019-07-09 16:17:18 +01:00
Milos Gajdos
c5fb409760 Removed debug logs 2019-07-09 15:55:39 +01:00
Milos Gajdos
70665e5a7d Route has changed to accomodate Link, Service and Address 2019-07-09 15:46:32 +01:00
Milos Gajdos
449aa0a339 Collect ANNOUNCE mesage events before adding default gateway. 2019-07-09 15:46:31 +01:00
Milos Gajdos
265271008e Simplified processEvents loop; Added router Announcement. 2019-07-09 15:46:31 +01:00
Milos Gajdos
b82245429e Simplified table logic. Lookup tests. mucp/cient update 2019-07-09 15:46:31 +01:00
Milos Gajdos
cc590f5f2c Table now has a dedicated package inside router package. 2019-07-09 15:46:31 +01:00
Milos Gajdos
0c1a28a9b6 Router routing table management. Table route hashes. Status codes changed.
We now manage routing table actions using dedicated functions run on
either registry or services in the registry.

Routing table now uses Route.Hash() instead of maintaining its own hash
struct filed which previously performed these operations.

Various names of variables have been changed to make them more concise.
2019-07-09 15:46:31 +01:00
Milos Gajdos
30d05e34a9 Read and remove routes based on registry event deltas 2019-07-09 15:46:31 +01:00
Milos Gajdos
b68f0e237f Removed event from eventMap once sent to be advertised 2019-07-09 15:46:31 +01:00
Milos Gajdos
72ef032162 First shot at flapping detection and event advertising.
This commit also adds Route hash function, lots of debug messages for
now and String() methods for various API objects.
2019-07-09 15:46:30 +01:00
Milos Gajdos
d6c07dfb16 Update is now Advert 2019-07-09 15:46:30 +01:00
Milos Gajdos
ea872f6900 Updated error statements; Update ships list of events. 2019-07-09 15:46:30 +01:00
Asim Aslam
6bdc23a3aa add comments 2019-07-08 16:32:12 +01:00
Asim Aslam
fa54db5ba5 rename network name to go.micro 2019-07-08 16:27:02 +01:00
Asim Aslam
8015a1daaf Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-08 16:25:41 +01:00
Asim Aslam
608a1f8add remove node code 2019-07-08 16:25:04 +01:00
Asim Aslam
4a02e1ff2f rewrite network interface 2019-07-08 16:24:57 +01:00
Asim Aslam
5cd1e81ba9 Merge pull request #570 from sunfuze/grpc-json-marshal
grpc: using jsonpb.Marshaler to do Marshal, map to jsonpb.Unmarsh
2019-07-08 08:44:51 +01:00
Asim Aslam
d3edad474e Merge pull request #571 from micro/remove-port
Remove Port from registry
2019-07-08 08:18:26 +01:00
Asim Aslam
e0bf1c2283 Remove Port from registry 2019-07-08 08:01:42 +01:00
Joe
b655f7f55a grpc: using jsonpb.Marshaler to do Marshal, map to jsonpb.Unmarsh 2019-07-08 10:32:10 +08:00
Asim Aslam
5b7454e5a8 update transport package comments 2019-07-07 15:04:07 +01:00
Asim Aslam
0b732b2c49 update transport package comments 2019-07-07 15:03:08 +01:00
Asim Aslam
be33d9204a Merge pull request #569 from micro/event
Change Publication to Event
2019-07-07 12:45:37 +01:00
Asim Aslam
4b4ad68eb9 Change Publication to Event 2019-07-07 12:44:09 +01:00
Asim Aslam
79b03a6825 add broker args 2019-07-07 12:36:14 +01:00
Asim Aslam
777a203f96 gofmt 2019-07-07 12:33:54 +01:00
Asim Aslam
c1097a4509 strip broker address 2019-07-07 12:33:47 +01:00
Asim Aslam
5f664faeba Add transport options comments 2019-07-07 12:23:03 +01:00
Asim Aslam
d2d6841f02 Move transport to network/transport 2019-07-07 10:37:34 +01:00
Asim Aslam
eafc930f84 Change network id to name 2019-07-07 10:10:38 +01:00
Asim Aslam
d1fc3c361e Merge pull request #566 from unistack-org/speed
codec grpc: fix extra allocations on message unmarshal
2019-07-04 17:29:32 +01:00
e40307c567 codec grpc: fix extra allocations on message unmarshal
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-04 14:06:29 +03:00
Asim Aslam
a412486c39 Update registry util semantics 2019-07-04 11:36:49 +01:00
Asim Aslam
59a0e727e4 Merge pull request #563 from unistack-org/race
export registry util function to safe copy registry data
2019-07-04 11:16:54 +01:00
Asim Aslam
b35f227f7a Merge pull request #565 from sunfuze/grpc-json-unmarshal
grpc: if unmarshal target is proto.Message, using jsonpb
2019-07-04 11:16:11 +01:00
Asim Aslam
00ba1655ca remove some readmes 2019-07-04 11:15:54 +01:00
Joe
e88041dc26 if unmarshal target is proto.Message, using jsonpb 2019-07-04 16:43:36 +08:00
0e34c572b4 export registry util function to safe copy registry data
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2019-07-04 11:39:43 +03:00
Asim Aslam
2644497ccb Fix some link connection logic 2019-07-03 19:51:40 +01:00
Asim Aslam
e54de56376 Functional loopback code 2019-07-03 19:26:24 +01:00
Asim Aslam
7008809eff Make the link use debug 2019-07-02 20:57:23 +01:00
Asim Aslam
f619e46def Some functioning network code 2019-07-02 20:54:21 +01:00
Asim Aslam
c3611aead2 go fmt 2019-07-02 20:53:42 +01:00
Asim Aslam
686aa3aa05 log levels 2019-07-02 19:21:43 +01:00
Asim Aslam
543dc0166c Restructure network things before moving 2019-07-02 08:45:00 +01:00
Asim Aslam
372ad949ff Rename to mucp transport 2019-07-02 00:48:15 +01:00
Asim Aslam
a0c2d18c40 Merge pull request #559 from milosgajdos83/table-tests
Default routing table tests
2019-07-02 00:28:52 +01:00
Asim Aslam
b4236f4430 Add network transport 2019-07-02 00:27:53 +01:00
Milos Gajdos
0e1fcc4f28 Stop hardcoding table sizes; increment as you move on. 2019-07-01 23:38:49 +01:00
Milos Gajdos
8f22e61a8b List test function properly named. 2019-07-01 23:38:48 +01:00
Milos Gajdos
956902f641 Added List tests. 2019-07-01 23:38:48 +01:00
Milos Gajdos
ffac0b9a18 First batch of Add/Del/Update tests. 2019-07-01 23:38:48 +01:00
Asim Aslam
c108b51d2a add network to Node proto 2019-07-01 23:12:05 +01:00
Asim Aslam
5fd798c9b6 add resolver comment 2019-07-01 23:11:55 +01:00
Asim Aslam
ebe3633082 move network initialiser code 2019-07-01 22:59:11 +01:00
Asim Aslam
032c3134c6 update comment 2019-07-01 22:54:26 +01:00
Asim Aslam
8ccf61ebaf Strip Link methods 2019-07-01 22:52:28 +01:00
Asim Aslam
fbbc33d0f9 Set Network() to string 2019-07-01 22:41:27 +01:00
Milos Gajdos
da299ea26b Simmplified RT Lookup. No more Metric in Query. 2019-07-01 20:33:08 +01:00
Asim Aslam
d3e200575c Merge branch 'master' of ssh://github.com/micro/go-micro 2019-07-01 18:37:45 +01:00
Asim Aslam
ddee8412ff Add tunnel interface 2019-07-01 18:37:39 +01:00
Asim Aslam
c84f101e17 Merge pull request #553 from milosgajdos83/router-network
Changed router interface. Added table watcher. Advertise routes
2019-07-01 16:17:02 +01:00
Milos Gajdos
f6e064cdbd Fixed router idempotency. Return registry.ErrWatchStopped from mdns reg 2019-07-01 15:46:26 +01:00
Milos Gajdos
cff46c3fd8 Added Init state. Recreate exit and advertise channels when recovering
In order to differentiate between intialized and other states we
introduced a new state: Init. The router is in this state only when it's
created.

We have cleaned up router status management which is now handled by
manageStatus function only.
2019-07-01 15:46:26 +01:00
Milos Gajdos
32300eadc1 Added Router Status which allows to track router status 2019-07-01 15:46:25 +01:00
Milos Gajdos
8ad2f73ad6 Advertisement is now Update; started bit is now running. 2019-07-01 15:46:25 +01:00
Milos Gajdos
9d7420658d Changed router interface. Added table watcher. Advertise routes
* Changed router interface to return Advertisement channel
* Added default gateway route to the routing table if supplied
* Watch table for updates and advertise to the network
* We hash the routes on 3-tuple (Destination, Gateway, Network)
2019-07-01 15:46:25 +01:00
Asim Aslam
0971deb9cc Merge pull request #558 from micro/network
Networking code
2019-07-01 12:12:23 +01:00
Asim Aslam
0899282277 Checkpoint networking code 2019-07-01 11:55:15 +01:00
Asim Aslam
d8e998ad85 add peer in context 2019-06-27 14:53:01 +01:00
Asim Aslam
b4b76d452a Call advertise 2019-06-27 14:38:12 +01:00
Asim Aslam
67e3d560fe Lookup every service. FML 2019-06-27 14:37:52 +01:00
Asim Aslam
9630e153a5 fix grpc proto wrapper 2019-06-27 13:08:06 +01:00
Asim Aslam
43297f731c Add default router 2019-06-27 12:57:23 +01:00
Asim Aslam
f6f6e1b561 Use the router to get routes 2019-06-27 12:56:52 +01:00
Asim Aslam
4bee5c1b2b Merge pull request #546 from lpxxn/master
pass parameter to anonymous function
2019-06-27 07:02:45 +01:00
lpxxn
3b0ef425b6 pass parameter to anonymous function 2019-06-27 13:06:53 +08:00
李鹏
5334203435 Merge pull request #4 from micro/master
pull
2019-06-27 13:01:14 +08:00
Asim Aslam
0da8256426 Accept a range of addresses 2019-06-26 20:51:13 +01:00
Asim Aslam
940ea94a96 Lookup router via registry 2019-06-26 19:56:40 +01:00
Asim Aslam
b904f383c1 go fmt 2019-06-26 19:28:30 +01:00
Asim Aslam
cedcef032d Add remote lookup via router selector 2019-06-26 19:27:38 +01:00
Milos Gajdos
76011b151d Bugfix: Set gateway to node.Address
gw has not been initialized; it was basically an empty string and only
got populated by Sprintf-ing the addr:port IF the port has been set.
This commit sets the gw to node.Address to it's never an empty string.
2019-06-26 16:28:33 +01:00
Asim Aslam
27b145f968 add router proto 2019-06-26 16:23:10 +01:00
Asim Aslam
ac098e4d78 add router selector and network defaults 2019-06-26 16:12:57 +01:00
Asim Aslam
1a62c11166 Merge pull request #544 from milosgajdos83/router-rework
Router rework
2019-06-26 16:08:12 +01:00
Milos Gajdos
fe84a2d726 Route per service node. No Network Registry for now. 2019-06-26 16:03:19 +01:00
李鹏
c282125f09 Merge pull request #3 from micro/master
update
2019-06-26 11:56:00 +08:00
Asim Aslam
4cad7697cc Merge pull request #542 from magodo/config_consul_source_opt
Add consul-specific option for config (as registry)
2019-06-25 16:14:03 +01:00
magodo
a8dbca756c rename stuff per feedback 2019-06-25 22:41:31 +08:00
magodo
8e4fd16aff Add consul-specific option for config (as registry) 2019-06-25 18:31:32 +08:00
Asim Aslam
68764ebafc Add registry resolver 2019-06-24 15:30:17 +01:00
Asim Aslam
4d08618517 fix typo 2019-06-24 15:22:12 +01:00
Asim Aslam
e5959f80d6 add http resolver 2019-06-24 15:21:24 +01:00
Asim Aslam
b89423bf37 add resolver 2019-06-24 15:11:11 +01:00
Asim Aslam
9a56c4e0b2 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-06-24 14:49:26 +01:00
Asim Aslam
4f982bb9cd Default to json content-type in api 2019-06-24 14:49:19 +01:00
Asim Aslam
1277f2478d Merge pull request #541 from milosgajdos83/gossip-del-service-revert
Reverts c0a628d
2019-06-22 20:05:10 +01:00
Asim Aslam
dffbe045e4 move node functions 2019-06-22 19:02:57 +01:00
Milos Gajdos
c3d2043caf Reverts c0a628d65b
Fixes #540
2019-06-22 19:01:03 +01:00
Asim Aslam
79cc8e34b0 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-06-22 16:51:28 +01:00
Asim Aslam
2d91ba411e update the network interface 2019-06-22 16:51:20 +01:00
Asim Aslam
5ee7140aa3 Merge pull request #536 from magodo/config_source_consul_support_array
Config source consul support 1st array
2019-06-22 08:39:17 +01:00
magodo
6ef838c9aa Merge branch 'master' of https://github.com/micro/go-micro into config_source_consul_support_array 2019-06-22 07:14:15 +08:00
Asim Aslam
1b4005e9a5 Go fmt everything 2019-06-21 17:20:41 +01:00
Asim Aslam
3f97743e34 Move router and proxy into network package 2019-06-21 17:20:31 +01:00
Asim Aslam
7936d74602 Update comments 2019-06-21 16:17:12 +01:00
Asim Aslam
6db720b197 Merge branch 'master' of ssh://github.com/micro/go-micro 2019-06-21 15:14:08 +01:00
Asim Aslam
ca5acba0c6 Move selector to client/selector 2019-06-21 15:13:54 +01:00
Asim Aslam
b4acb9bb58 Merge pull request #538 from magodo/consul_path_prefix_leading_slash
config consul source supports slash as prefix
2019-06-21 14:23:53 +01:00
Asim Aslam
c350e19552 Move cmd => config/cmd 2019-06-21 13:36:11 +01:00
magodo
3c82b2e9e8 Merge branch 'consul_path_prefix_leading_slash' into dev 2019-06-21 16:53:21 +08:00
magodo
9514bd7b2a Merge branch 'config_source_consul_support_array' into dev 2019-06-21 16:52:01 +08:00
magodo
7acd249147 config consul source supports slash as prefix
`config.NewConfig()` with consul source will both read from consul
and watch consul for changes. Hence, the `prefix` is used in these
2 cases:

- read case: it is used to strip path based on the `KVPair` returned
from consul `kv.List()` method
- watch case: it is used as the `key` of watch query (`keyprefix` type)

So for *watch case*, the `key` is leagal to be `/` for watching change
on root. While for *read case*, because `KVPair.Key` is always stripped
off the leading slash, so if user specified some `prefix` with leading
slash, we should strip it also.

An extream case would be: user want's to read & watch node in root dir.
One would specify `prefix` as `/`, and it should work then.
2019-06-21 16:35:48 +08:00
magodo
1983b4ae92 variable rename to abstract encoder 2019-06-21 15:30:45 +08:00
magodo
92b998c3ab consul config source support 1st-level array
Check whetehr the 1st level encoded json is array or not, to
support 1st level array in consul config.

During debug, i suspected the incapability of arrray is caused by
json reader, so i added test for array. I think it makes no harm
to also check that in.
2019-06-21 00:25:39 +08:00
156 changed files with 6815 additions and 2592 deletions

View File

@@ -7,3 +7,6 @@ env:
notifications:
slack:
secure: aEvhLbhujaGaKSrOokiG3//PaVHTIrc3fBpoRbCRqfZpyq6WREoapJJhF+tIpWWOwaC9GmChbD6aHo/jMUgwKXVyPSaNjiEL87YzUUpL8B2zslNp1rgfTg/LrzthOx3Q1TYwpaAl3to0fuHUVFX4yMeC2vuThq7WSXgMMxFCtbc=
cache:
directories:
- $GOPATH/pkg/mod

View File

@@ -1,197 +0,0 @@
# Agent
Agent is a library used to create commands, inputs and robot services
## Getting Started
- [Commands](#commands) - Commands are functions executed by the bot based on text based pattern matching.
- [Inputs](#inputs) - Inputs are plugins for communication e.g Slack, Telegram, IRC, etc.
- [Services](#services) - Write bots as micro services
## Commands
Commands are functions executed by the bot based on text based pattern matching.
### Write a Command
```go
import "github.com/micro/go-micro/agent/command"
func Ping() command.Command {
usage := "ping"
description := "Returns pong"
return command.NewCommand("ping", usage, desc, func(args ...string) ([]byte, error) {
return []byte("pong"), nil
})
}
```
### Register the command
Add the command to the Commands map with a pattern key that can be matched by golang/regexp.Match
```go
import "github.com/micro/go-micro/agent/command"
func init() {
command.Commands["^ping$"] = Ping()
}
```
### Rebuild Micro
Build binary
```shell
cd github.com/micro/micro
// For local use
go build -i -o micro ./main.go
// For docker image
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' -i -o micro ./main.go
```
## Inputs
Inputs are plugins for communication e.g Slack, HipChat, XMPP, IRC, SMTP, etc, etc.
New inputs can be added in the following way.
### Write an Input
Write an input that satisfies the Input interface.
```go
type Input interface {
// Provide cli flags
Flags() []cli.Flag
// Initialise input using cli context
Init(*cli.Context) error
// Stream events from the input
Stream() (Conn, error)
// Start the input
Start() error
// Stop the input
Stop() error
// name of the input
String() string
}
```
### Register the input
Add the input to the Inputs map.
```go
import "github.com/micro/micro/bot/input"
func init() {
input.Inputs["name"] = MyInput
}
```
### Rebuild Micro
Build binary
```shell
cd github.com/micro/micro
// For local use
go build -i -o micro ./main.go
// For docker image
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-w' -i -o micro ./main.go
```
## Services
The micro bot supports the ability to create commands as micro services.
### How does it work?
The bot watches the service registry for services with it's namespace. The default namespace is `go.micro.bot`.
Any service within this namespace will automatically be added to the list of available commands. When a command
is executed, the bot will call the service with method `Command.Exec`. It also expects the method `Command.Help`
to exist for usage info.
The service interface is as follows and can be found at [go-micro/agent/proto](https://github.com/micro/go-micro/agent/blob/master/proto/bot.proto)
```
syntax = "proto3";
package go.micro.bot;
service Command {
rpc Help(HelpRequest) returns (HelpResponse) {};
rpc Exec(ExecRequest) returns (ExecResponse) {};
}
message HelpRequest {
}
message HelpResponse {
string usage = 1;
string description = 2;
}
message ExecRequest {
repeated string args = 1;
}
message ExecResponse {
bytes result = 1;
string error = 2;
}
```
### Example
Here's an example echo command as a microservice
```go
package main
import (
"fmt"
"strings"
"github.com/micro/go-micro"
"golang.org/x/net/context"
proto "github.com/micro/go-micro/agent/proto"
)
type Command struct{}
// Help returns the command usage
func (c *Command) Help(ctx context.Context, req *proto.HelpRequest, rsp *proto.HelpResponse) error {
// Usage should include the name of the command
rsp.Usage = "echo"
rsp.Description = "This is an example bot command as a micro service which echos the message"
return nil
}
// Exec executes the command
func (c *Command) Exec(ctx context.Context, req *proto.ExecRequest, rsp *proto.ExecResponse) error {
rsp.Result = []byte(strings.Join(req.Args, " "))
// rsp.Error could be set to return an error instead
// the function error would only be used for service level issues
return nil
}
func main() {
service := micro.NewService(
micro.Name("go.micro.bot.echo"),
)
service.Init()
proto.RegisterCommandHandler(service.Server(), new(Command))
if err := service.Run(); err != nil {
fmt.Println(err)
}
}
```

View File

@@ -1,18 +0,0 @@
# Go API [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-micro/api?status.svg)](https://godoc.org/github.com/micro/go-micro/api) [![Travis CI](https://api.travis-ci.org/micro/go-micro/api.svg?branch=master)](https://travis-ci.org/micro/go-micro/api) [![Go Report Card](https://goreportcard.com/badge/micro/go-micro/api)](https://goreportcard.com/report/github.com/micro/go-micro/api)
Go API is a pluggable API framework driven by service discovery to help build powerful public API gateways.
## Overview
The Go API library provides api gateway routing capabilities. A microservice architecture decouples application logic into
separate service. An api gateway provides a single entry point to consolidate these services into a unified api. The
Go API uses routes defined in service discovery metadata to generate routing rules and serve http requests.
<img src="https://micro.mu/docs/images/go-api.png?v=1" alt="Go API" />
Go API is the basis for the [micro api](https://micro.mu/docs/api.html).
## Getting Started
See the [docs](https://micro.mu/docs/go-api.html) to learn more

View File

@@ -8,8 +8,8 @@ import (
"github.com/micro/go-micro/api/handler"
api "github.com/micro/go-micro/api/proto"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/errors"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/util/ctx"
)

View File

@@ -9,8 +9,8 @@ import (
"strings"
api "github.com/micro/go-micro/api/proto"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
)
func requestToProto(r *http.Request) (*api.Request, error) {

View File

@@ -120,7 +120,7 @@ func (c *conn) writeLoop() {
opts = append(opts, broker.Queue(c.queue))
}
subscriber, err := c.b.Subscribe(c.topic, func(p broker.Publication) error {
subscriber, err := c.b.Subscribe(c.topic, func(p broker.Event) error {
b, err := json.Marshal(p.Message())
if err != nil {
return nil

View File

@@ -10,7 +10,7 @@ import (
"github.com/micro/go-micro/api"
"github.com/micro/go-micro/api/handler"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/client/selector"
)
const (
@@ -73,7 +73,7 @@ func (h *httpHandler) getService(r *http.Request) (string, error) {
return "", nil
}
return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil
return fmt.Sprintf("http://%s", s.Address), nil
}
func (h *httpHandler) String() string {

View File

@@ -4,14 +4,12 @@ import (
"net"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"github.com/micro/go-micro/api/handler"
"github.com/micro/go-micro/api/router"
regRouter "github.com/micro/go-micro/api/router/registry"
"github.com/micro/go-micro/cmd"
"github.com/micro/go-micro/config/cmd"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/registry/memory"
)
@@ -26,21 +24,12 @@ func testHttp(t *testing.T, path, service, ns string) {
}
defer l.Close()
parts := strings.Split(l.Addr().String(), ":")
var host string
var port int
host = parts[0]
port, _ = strconv.Atoi(parts[1])
s := &registry.Service{
Name: service,
Nodes: []*registry.Node{
&registry.Node{
Id: service + "-1",
Address: host,
Port: port,
Address: l.Addr().String(),
},
},
}

View File

@@ -14,12 +14,12 @@ import (
"github.com/micro/go-micro/api/handler"
proto "github.com/micro/go-micro/api/internal/proto"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/codec/jsonrpc"
"github.com/micro/go-micro/codec/protorpc"
"github.com/micro/go-micro/errors"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/util/ctx"
)
@@ -120,32 +120,6 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var rsp []byte
switch {
// json codecs
case hasCodec(ct, jsonCodecs):
var request json.RawMessage
// if the extracted payload isn't empty lets use it
if len(br) > 0 {
request = json.RawMessage(br)
}
// create request/response
var response json.RawMessage
req := c.NewRequest(
service.Name,
service.Endpoint.Name,
&request,
client.WithContentType(ct),
)
// make the call
if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil {
writeError(w, r, err)
return
}
// marshall response
rsp, _ = response.MarshalJSON()
// proto codecs
case hasCodec(ct, protoCodecs):
request := &proto.Message{}
@@ -173,8 +147,36 @@ func (h *rpcHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// marshall response
rsp, _ = response.Marshal()
default:
http.Error(w, "Unsupported Content-Type", 400)
return
// if json codec is not present set to json
if !hasCodec(ct, jsonCodecs) {
ct = "application/json"
}
// default to trying json
var request json.RawMessage
// if the extracted payload isn't empty lets use it
if len(br) > 0 {
request = json.RawMessage(br)
}
// create request/response
var response json.RawMessage
req := c.NewRequest(
service.Name,
service.Endpoint.Name,
&request,
client.WithContentType(ct),
)
// make the call
if err := c.Call(cx, req, &response, client.WithSelectOption(so)); err != nil {
writeError(w, r, err)
return
}
// marshall response
rsp, _ = response.MarshalJSON()
}
// write the response

View File

@@ -13,7 +13,7 @@ import (
"github.com/micro/go-micro/api"
"github.com/micro/go-micro/api/handler"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/client/selector"
)
const (
@@ -79,7 +79,7 @@ func (wh *webHandler) getService(r *http.Request) (string, error) {
return "", nil
}
return fmt.Sprintf("http://%s:%d", s.Address, s.Port), nil
return fmt.Sprintf("http://%s", s.Address), nil
}
// serveWebSocket used to serve a web socket proxied connection

View File

@@ -3,7 +3,7 @@ package router
import (
"github.com/micro/go-micro/api/resolver"
"github.com/micro/go-micro/api/resolver/micro"
"github.com/micro/go-micro/cmd"
"github.com/micro/go-micro/config/cmd"
"github.com/micro/go-micro/registry"
)

View File

@@ -3,28 +3,28 @@ package broker
// Broker is an interface used for asynchronous messaging.
type Broker interface {
Init(...Option) error
Options() Options
Address() string
Connect() error
Disconnect() error
Init(...Option) error
Publish(string, *Message, ...PublishOption) error
Subscribe(string, Handler, ...SubscribeOption) (Subscriber, error)
Publish(topic string, m *Message, opts ...PublishOption) error
Subscribe(topic string, h Handler, opts ...SubscribeOption) (Subscriber, error)
String() string
}
// Handler is used to process messages via a subscription of a topic.
// The handler is passed a publication interface which contains the
// message and optional Ack method to acknowledge receipt of the message.
type Handler func(Publication) error
type Handler func(Event) error
type Message struct {
Header map[string]string
Body []byte
}
// Publication is given to a subscription handler for processing
type Publication interface {
// Event is given to a subscription handler for processing
type Event interface {
Topic() string
Message() *Message
Ack() error

View File

@@ -14,13 +14,11 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
@@ -30,8 +28,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
@@ -41,8 +38,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
Address: "localhost:8888",
},
},
},

View File

@@ -13,8 +13,6 @@ import (
"net/http"
"net/url"
"runtime"
"strconv"
"strings"
"sync"
"time"
@@ -59,7 +57,7 @@ type httpSubscriber struct {
hb *httpBroker
}
type httpPublication struct {
type httpEvent struct {
m *Message
t string
}
@@ -155,15 +153,15 @@ func newHttpBroker(opts ...Option) Broker {
return h
}
func (h *httpPublication) Ack() error {
func (h *httpEvent) Ack() error {
return nil
}
func (h *httpPublication) Message() *Message {
func (h *httpEvent) Message() *Message {
return h.m
}
func (h *httpPublication) Topic() string {
func (h *httpEvent) Topic() string {
return h.t
}
@@ -323,7 +321,7 @@ func (h *httpBroker) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
p := &httpPublication{m: m, t: topic}
p := &httpEvent{m: m, t: topic}
id := req.Form.Get("id")
h.RLock()
@@ -403,6 +401,7 @@ func (h *httpBroker) Connect() error {
go func() {
h.run(l)
h.Lock()
h.opts.Addrs = []string{addr}
h.address = addr
h.Unlock()
}()
@@ -542,7 +541,7 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
vals := url.Values{}
vals.Add("id", node.Id)
uri := fmt.Sprintf("%s://%s:%d%s?%s", scheme, node.Address, node.Port, DefaultSubPath, vals.Encode())
uri := fmt.Sprintf("%s://%s%s?%s", scheme, node.Address, DefaultSubPath, vals.Encode())
r, err := h.c.Post(uri, "application/json", bytes.NewReader(b))
if err != nil {
return err
@@ -613,12 +612,15 @@ func (h *httpBroker) Publish(topic string, msg *Message, opts ...PublishOption)
}
func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeOption) (Subscriber, error) {
var err error
var host, port string
options := NewSubscribeOptions(opts...)
// parse address for host, port
parts := strings.Split(h.Address(), ":")
host := strings.Join(parts[:len(parts)-1], ":")
port, _ := strconv.Atoi(parts[len(parts)-1])
host, port, err = net.SplitHostPort(h.Address())
if err != nil {
return nil, err
}
addr, err := maddr.Extract(host)
if err != nil {
@@ -637,8 +639,7 @@ func (h *httpBroker) Subscribe(topic string, handler Handler, opts ...SubscribeO
// register service
node := &registry.Node{
Id: id,
Address: addr,
Port: port,
Address: mnet.HostPort(addr, port),
Metadata: map[string]string{
"secure": fmt.Sprintf("%t", secure),
},

View File

@@ -47,7 +47,7 @@ func sub(be *testing.B, c int) {
done := make(chan bool, c)
for i := 0; i < c; i++ {
sub, err := b.Subscribe(topic, func(p Publication) error {
sub, err := b.Subscribe(topic, func(p Event) error {
done <- true
m := p.Message()
@@ -107,7 +107,7 @@ func pub(be *testing.B, c int) {
done := make(chan bool, c*4)
sub, err := b.Subscribe(topic, func(p Publication) error {
sub, err := b.Subscribe(topic, func(p Event) error {
done <- true
m := p.Message()
if string(m.Body) != string(msg.Body) {
@@ -175,7 +175,7 @@ func TestBroker(t *testing.T) {
done := make(chan bool)
sub, err := b.Subscribe("test", func(p Publication) error {
sub, err := b.Subscribe("test", func(p Event) error {
m := p.Message()
if string(m.Body) != string(msg.Body) {
@@ -224,7 +224,7 @@ func TestConcurrentSubBroker(t *testing.T) {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
sub, err := b.Subscribe("test", func(p Publication) error {
sub, err := b.Subscribe("test", func(p Event) error {
defer wg.Done()
m := p.Message()
@@ -279,7 +279,7 @@ func TestConcurrentPubBroker(t *testing.T) {
var wg sync.WaitGroup
sub, err := b.Subscribe("test", func(p Publication) error {
sub, err := b.Subscribe("test", func(p Event) error {
defer wg.Done()
m := p.Message()

View File

@@ -3,21 +3,26 @@ package memory
import (
"errors"
"math/rand"
"sync"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/broker"
maddr "github.com/micro/go-micro/util/addr"
mnet "github.com/micro/go-micro/util/net"
)
type memoryBroker struct {
opts broker.Options
addr string
sync.RWMutex
connected bool
Subscribers map[string][]*memorySubscriber
}
type memoryPublication struct {
type memoryEvent struct {
topic string
message *broker.Message
}
@@ -35,7 +40,7 @@ func (m *memoryBroker) Options() broker.Options {
}
func (m *memoryBroker) Address() string {
return ""
return m.addr
}
func (m *memoryBroker) Connect() error {
@@ -46,6 +51,15 @@ func (m *memoryBroker) Connect() error {
return nil
}
addr, err := maddr.Extract("::")
if err != nil {
return err
}
i := rand.Intn(20000)
// set addr with port
addr = mnet.HostPort(addr, 10000+i)
m.addr = addr
m.connected = true
return nil
@@ -72,19 +86,19 @@ func (m *memoryBroker) Init(opts ...broker.Option) error {
}
func (m *memoryBroker) Publish(topic string, message *broker.Message, opts ...broker.PublishOption) error {
m.Lock()
defer m.Unlock()
m.RLock()
if !m.connected {
m.RUnlock()
return errors.New("not connected")
}
subs, ok := m.Subscribers[topic]
m.RUnlock()
if !ok {
return nil
}
p := &memoryPublication{
p := &memoryEvent{
topic: topic,
message: message,
}
@@ -99,12 +113,12 @@ func (m *memoryBroker) Publish(topic string, message *broker.Message, opts ...br
}
func (m *memoryBroker) Subscribe(topic string, handler broker.Handler, opts ...broker.SubscribeOption) (broker.Subscriber, error) {
m.Lock()
defer m.Unlock()
m.RLock()
if !m.connected {
m.RUnlock()
return nil, errors.New("not connected")
}
m.RUnlock()
var options broker.SubscribeOptions
for _, o := range opts {
@@ -119,7 +133,9 @@ func (m *memoryBroker) Subscribe(topic string, handler broker.Handler, opts ...b
opts: options,
}
m.Lock()
m.Subscribers[topic] = append(m.Subscribers[topic], sub)
m.Unlock()
go func() {
<-sub.exit
@@ -142,15 +158,15 @@ func (m *memoryBroker) String() string {
return "memory"
}
func (m *memoryPublication) Topic() string {
func (m *memoryEvent) Topic() string {
return m.topic
}
func (m *memoryPublication) Message() *broker.Message {
func (m *memoryEvent) Message() *broker.Message {
return m.message
}
func (m *memoryPublication) Ack() error {
func (m *memoryEvent) Ack() error {
return nil
}
@@ -169,6 +185,7 @@ func (m *memorySubscriber) Unsubscribe() error {
func NewBroker(opts ...broker.Option) broker.Broker {
var options broker.Options
rand.Seed(time.Now().UnixNano())
for _, o := range opts {
o(&options)
}

View File

@@ -17,7 +17,7 @@ func TestMemoryBroker(t *testing.T) {
topic := "test"
count := 10
fn := func(p broker.Publication) error {
fn := func(p broker.Event) error {
return nil
}

View File

@@ -1,14 +0,0 @@
package client
import (
"bytes"
)
type buffer struct {
*bytes.Buffer
}
func (b *buffer) Close() error {
b.Buffer.Reset()
return nil
}

View File

@@ -10,7 +10,7 @@ import (
// Client is the interface used to make requests to services.
// It supports Request/Response via Transport and Publishing via the Broker.
// It also supports bidiectional streaming of requests.
// It also supports bidirectional streaming of requests.
type Client interface {
Init(...Option) error
Options() Options

View File

@@ -14,13 +14,11 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
@@ -30,8 +28,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
@@ -41,8 +38,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
Address: "localhost:8888",
},
},
},

View File

@@ -1,14 +0,0 @@
package grpc
import (
"bytes"
)
type buffer struct {
*bytes.Buffer
}
func (b *buffer) Close() error {
b.Buffer.Reset()
return nil
}

View File

@@ -4,8 +4,11 @@ import (
"fmt"
"strings"
b "bytes"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/json-iterator/go"
jsoniter "github.com/json-iterator/go"
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/codec/bytes"
"github.com/micro/go-micro/codec/jsonrpc"
@@ -19,6 +22,8 @@ type protoCodec struct{}
type bytesCodec struct{}
type wrapCodec struct{ encoding.Codec }
var jsonpbMarshaler = &jsonpb.Marshaler{}
var (
defaultGRPCCodecs = map[string]encoding.Codec{
"application/json": jsonCodec{},
@@ -110,10 +115,20 @@ func (bytesCodec) Name() string {
}
func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
if pb, ok := v.(proto.Message); ok {
s, err := jsonpbMarshaler.MarshalToString(pb)
return []byte(s), err
}
return json.Marshal(v)
}
func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
if pb, ok := v.(proto.Message); ok {
return jsonpb.Unmarshal(b.NewReader(data), pb)
}
return json.Unmarshal(data, v)
}

View File

@@ -2,7 +2,6 @@
package grpc
import (
"bytes"
"context"
"crypto/tls"
"fmt"
@@ -12,13 +11,14 @@ import (
"github.com/micro/go-micro/broker"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/errors"
"github.com/micro/go-micro/metadata"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/transport"
"github.com/micro/go-micro/util/buf"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/encoding"
@@ -33,7 +33,7 @@ type grpcClient struct {
func init() {
encoding.RegisterCodec(wrapCodec{jsonCodec{}})
encoding.RegisterCodec(wrapCodec{jsonCodec{}})
encoding.RegisterCodec(wrapCodec{protoCodec{}})
encoding.RegisterCodec(wrapCodec{bytesCodec{}})
}
@@ -59,14 +59,14 @@ func (g *grpcClient) next(request client.Request, opts client.CallOptions) (sele
// get proxy address
if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 {
opts.Address = prx
opts.Address = []string{prx}
}
// return remote address
if len(opts.Address) > 0 {
return func() (*registry.Node, error) {
return &registry.Node{
Address: opts.Address,
Address: opts.Address[0],
}, nil
}, nil
}
@@ -84,9 +84,6 @@ func (g *grpcClient) next(request client.Request, opts client.CallOptions) (sele
func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.Request, rsp interface{}, opts client.CallOptions) error {
address := node.Address
if node.Port > 0 {
address = fmt.Sprintf("%s:%d", address, node.Port)
}
header := make(map[string]string)
if md, ok := metadata.FromContext(ctx); ok {
@@ -130,7 +127,7 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
ch := make(chan error, 1)
go func() {
err := cc.Invoke(ctx, methodToGRPC(req.Service(), req.Endpoint()), req.Body(), rsp, grpc.ForceCodec(cf))
err := cc.Invoke(ctx, methodToGRPC(req.Service(), req.Endpoint()), req.Body(), rsp, grpc.CallContentSubtype(cf.Name()))
ch <- microError(err)
}()
@@ -146,9 +143,6 @@ func (g *grpcClient) call(ctx context.Context, node *registry.Node, req client.R
func (g *grpcClient) stream(ctx context.Context, node *registry.Node, req client.Request, opts client.CallOptions) (client.Stream, error) {
address := node.Address
if node.Port > 0 {
address = fmt.Sprintf("%s:%d", address, node.Port)
}
header := make(map[string]string)
if md, ok := metadata.FromContext(ctx); ok {
@@ -295,7 +289,7 @@ func (g *grpcClient) Options() client.Options {
}
func (g *grpcClient) NewMessage(topic string, msg interface{}, opts ...client.MessageOption) client.Message {
return newGRPCPublication(topic, msg, g.opts.ContentType, opts...)
return newGRPCEvent(topic, msg, g.opts.ContentType, opts...)
}
func (g *grpcClient) NewRequest(service, method string, req interface{}, reqOpts ...client.RequestOption) client.Request {
@@ -372,9 +366,9 @@ func (g *grpcClient) Call(ctx context.Context, req client.Request, rsp interface
var gerr error
for i := 0; i <= callOpts.Retries; i++ {
go func() {
go func(i int) {
ch <- call(i)
}()
}(i)
select {
case <-ctx.Done():
@@ -455,10 +449,10 @@ func (g *grpcClient) Stream(ctx context.Context, req client.Request, opts ...cli
var grr error
for i := 0; i <= callOpts.Retries; i++ {
go func() {
go func(i int) {
s, err := call(i)
ch <- response{s, err}
}()
}(i)
select {
case <-ctx.Done():
@@ -497,8 +491,9 @@ func (g *grpcClient) Publish(ctx context.Context, p client.Message, opts ...clie
return errors.InternalServerError("go.micro.client", err.Error())
}
b := &buffer{bytes.NewBuffer(nil)}
if err := cf(b).Write(&codec.Message{Type: codec.Publication}, p.Payload()); err != nil {
b := buf.New(nil)
if err := cf(b).Write(&codec.Message{Type: codec.Event}, p.Payload()); err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}

View File

@@ -3,14 +3,12 @@ package grpc
import (
"context"
"net"
"strconv"
"strings"
"testing"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/registry/memory"
"github.com/micro/go-micro/selector"
pgrpc "google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
@@ -36,10 +34,6 @@ func TestGRPCClient(t *testing.T) {
go s.Serve(l)
defer s.Stop()
parts := strings.Split(l.Addr().String(), ":")
port, _ := strconv.Atoi(parts[len(parts)-1])
addr := strings.Join(parts[:len(parts)-1], ":")
// create mock registry
r := memory.NewRegistry()
@@ -50,8 +44,7 @@ func TestGRPCClient(t *testing.T) {
Nodes: []*registry.Node{
&registry.Node{
Id: "test-1",
Address: addr,
Port: port,
Address: l.Addr().String(),
},
},
})

View File

@@ -4,13 +4,13 @@ import (
"github.com/micro/go-micro/client"
)
type grpcPublication struct {
type grpcEvent struct {
topic string
contentType string
payload interface{}
}
func newGRPCPublication(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message {
func newGRPCEvent(topic string, payload interface{}, contentType string, opts ...client.MessageOption) client.Message {
var options client.MessageOptions
for _, o := range opts {
o(&options)
@@ -20,21 +20,21 @@ func newGRPCPublication(topic string, payload interface{}, contentType string, o
contentType = options.ContentType
}
return &grpcPublication{
return &grpcEvent{
payload: payload,
topic: topic,
contentType: contentType,
}
}
func (g *grpcPublication) ContentType() string {
func (g *grpcEvent) ContentType() string {
return g.contentType
}
func (g *grpcPublication) Topic() string {
func (g *grpcEvent) Topic() string {
return g.topic
}
func (g *grpcPublication) Payload() interface{} {
func (g *grpcEvent) Payload() interface{} {
return g.payload
}

View File

@@ -5,9 +5,9 @@ import (
"time"
"github.com/micro/go-micro/broker"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/transport"
)
@@ -43,8 +43,8 @@ type Options struct {
type CallOptions struct {
SelectOptions []selector.SelectOption
// Address of remote host
Address string
// Address of remote hosts
Address []string
// Backoff func
Backoff BackoffFunc
// Check if retriable func
@@ -245,8 +245,8 @@ func WithExchange(e string) PublishOption {
}
}
// WithAddress sets the remote address to use rather than using service discovery
func WithAddress(a string) CallOption {
// WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
o.Address = a
}

114
client/pool/default.go Normal file
View File

@@ -0,0 +1,114 @@
package pool
import (
"sync"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/transport"
)
type pool struct {
size int
ttl time.Duration
tr transport.Transport
sync.Mutex
conns map[string][]*poolConn
}
type poolConn struct {
transport.Client
id string
created time.Time
}
func newPool(options Options) *pool {
return &pool{
size: options.Size,
tr: options.Transport,
ttl: options.TTL,
conns: make(map[string][]*poolConn),
}
}
func (p *pool) Close() error {
p.Lock()
for k, c := range p.conns {
for _, conn := range c {
conn.Client.Close()
}
delete(p.conns, k)
}
p.Unlock()
return nil
}
// NoOp the Close since we manage it
func (p *poolConn) Close() error {
return nil
}
func (p *poolConn) Id() string {
return p.id
}
func (p *poolConn) Created() time.Time {
return p.created
}
func (p *pool) Get(addr string, opts ...transport.DialOption) (Conn, error) {
p.Lock()
conns := p.conns[addr]
// while we have conns check age and then return one
// otherwise we'll create a new conn
for len(conns) > 0 {
conn := conns[len(conns)-1]
conns = conns[:len(conns)-1]
p.conns[addr] = conns
// if conn is old kill it and move on
if d := time.Since(conn.Created()); d > p.ttl {
conn.Client.Close()
continue
}
// we got a good conn, lets unlock and return it
p.Unlock()
return conn, nil
}
p.Unlock()
// create new conn
c, err := p.tr.Dial(addr, opts...)
if err != nil {
return nil, err
}
return &poolConn{
Client: c,
id: uuid.New().String(),
created: time.Now(),
}, nil
}
func (p *pool) Release(conn Conn, err error) error {
// don't store the conn if it has errored
if err != nil {
return conn.(*poolConn).Client.Close()
}
// otherwise put it back for reuse
p.Lock()
conns := p.conns[conn.Remote()]
if len(conns) >= p.size {
p.Unlock()
return conn.(*poolConn).Client.Close()
}
p.conns[conn.Remote()] = append(conns, conn.(*poolConn))
p.Unlock()
return nil
}

View File

@@ -1,4 +1,4 @@
package client
package pool
import (
"testing"
@@ -9,12 +9,17 @@ import (
)
func testPool(t *testing.T, size int, ttl time.Duration) {
// zero pool
p := newPool(size, ttl)
// mock transport
tr := memory.NewTransport()
options := Options{
TTL: ttl,
Size: size,
Transport: tr,
}
// zero pool
p := newPool(options)
// listen
l, err := tr.Listen(":0")
if err != nil {
@@ -43,7 +48,7 @@ func testPool(t *testing.T, size int, ttl time.Duration) {
for i := 0; i < 10; i++ {
// get a conn
c, err := p.getConn(l.Addr(), tr)
c, err := p.Get(l.Addr())
if err != nil {
t.Fatal(err)
}
@@ -67,7 +72,7 @@ func testPool(t *testing.T, size int, ttl time.Duration) {
}
// release the conn
p.release(l.Addr(), c, nil)
p.Release(c, nil)
p.Lock()
if i := len(p.conns[l.Addr()]); i > size {
@@ -78,7 +83,7 @@ func testPool(t *testing.T, size int, ttl time.Duration) {
}
}
func TestRPCPool(t *testing.T) {
func TestClientPool(t *testing.T) {
testPool(t, 0, time.Minute)
testPool(t, 2, time.Minute)
}

33
client/pool/options.go Normal file
View File

@@ -0,0 +1,33 @@
package pool
import (
"time"
"github.com/micro/go-micro/transport"
)
type Options struct {
Transport transport.Transport
TTL time.Duration
Size int
}
type Option func(*Options)
func Size(i int) Option {
return func(o *Options) {
o.Size = i
}
}
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
func TTL(t time.Duration) Option {
return func(o *Options) {
o.TTL = t
}
}

35
client/pool/pool.go Normal file
View File

@@ -0,0 +1,35 @@
// Package pool is a connection pool
package pool
import (
"time"
"github.com/micro/go-micro/transport"
)
// Pool is an interface for connection pooling
type Pool interface {
// Close the pool
Close() error
// Get a connection
Get(addr string, opts ...transport.DialOption) (Conn, error)
// Releaes the connection
Release(c Conn, status error) error
}
type Conn interface {
// unique id of connection
Id() string
// time it was created
Created() time.Time
// embedded connection
transport.Client
}
func NewPool(opts ...Option) Pool {
var options Options
for _, o := range opts {
o(&options)
}
return newPool(options)
}

View File

@@ -1,40 +1,45 @@
package client
import (
"bytes"
"context"
"fmt"
"net"
"os"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/broker"
"github.com/micro/go-micro/client/pool"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/errors"
"github.com/micro/go-micro/metadata"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/transport"
"github.com/micro/go-micro/util/buf"
)
type rpcClient struct {
once sync.Once
opts Options
pool *pool
pool pool.Pool
seq uint64
}
func newRpcClient(opt ...Option) Client {
opts := newOptions(opt...)
p := pool.NewPool(
pool.Size(opts.PoolSize),
pool.TTL(opts.PoolTTL),
pool.Transport(opts.Transport),
)
rc := &rpcClient{
once: sync.Once{},
opts: opts,
pool: newPool(opts.PoolSize, opts.PoolTTL),
pool: p,
seq: 0,
}
@@ -60,9 +65,6 @@ func (r *rpcClient) newCodec(contentType string) (codec.NewCodec, error) {
func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request, resp interface{}, opts CallOptions) error {
address := node.Address
if node.Port > 0 {
address = fmt.Sprintf("%s:%d", address, node.Port)
}
msg := &transport.Message{
Header: make(map[string]string),
@@ -95,13 +97,13 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
}
var grr error
c, err := r.pool.getConn(address, r.opts.Transport, transport.WithTimeout(opts.DialTimeout))
c, err := r.pool.Get(address, transport.WithTimeout(opts.DialTimeout))
if err != nil {
return errors.InternalServerError("go.micro.client", "connection error: %v", err)
}
defer func() {
// defer execution of release
r.pool.release(address, c, grr)
r.pool.Release(c, grr)
}()
seq := atomic.LoadUint64(&r.seq)
@@ -160,9 +162,6 @@ func (r *rpcClient) call(ctx context.Context, node *registry.Node, req Request,
func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request, opts CallOptions) (Stream, error) {
address := node.Address
if node.Port > 0 {
address = fmt.Sprintf("%s:%d", address, node.Port)
}
msg := &transport.Message{
Header: make(map[string]string),
@@ -253,17 +252,22 @@ func (r *rpcClient) stream(ctx context.Context, node *registry.Node, req Request
func (r *rpcClient) Init(opts ...Option) error {
size := r.opts.PoolSize
ttl := r.opts.PoolTTL
tr := r.opts.Transport
for _, o := range opts {
o(&r.opts)
}
// update pool configuration if the options changed
if size != r.opts.PoolSize || ttl != r.opts.PoolTTL {
r.pool.Lock()
r.pool.size = r.opts.PoolSize
r.pool.ttl = int64(r.opts.PoolTTL.Seconds())
r.pool.Unlock()
if size != r.opts.PoolSize || ttl != r.opts.PoolTTL || tr != r.opts.Transport {
// close existing pool
r.pool.Close()
// create new pool
r.pool = pool.NewPool(
pool.Size(r.opts.PoolSize),
pool.TTL(r.opts.PoolTTL),
pool.Transport(r.opts.Transport),
)
}
return nil
@@ -283,29 +287,26 @@ func (r *rpcClient) next(request Request, opts CallOptions) (selector.Next, erro
// get proxy address
if prx := os.Getenv("MICRO_PROXY_ADDRESS"); len(prx) > 0 {
opts.Address = prx
opts.Address = []string{prx}
}
// return remote address
if len(opts.Address) > 0 {
address := opts.Address
port := 0
nodes := make([]*registry.Node, len(opts.Address))
host, sport, err := net.SplitHostPort(opts.Address)
if err == nil {
address = host
port, _ = strconv.Atoi(sport)
}
return func() (*registry.Node, error) {
return &registry.Node{
for i, address := range opts.Address {
nodes[i] = &registry.Node{
Address: address,
Port: port,
// Set the protocol
Metadata: map[string]string{
"protocol": "mucp",
},
}, nil
}
}
// crude return method
return func() (*registry.Node, error) {
return nodes[time.Now().Unix()%int64(len(nodes))], nil
}, nil
}
@@ -537,10 +538,13 @@ func (r *rpcClient) Publish(ctx context.Context, msg Message, opts ...PublishOpt
if err != nil {
return errors.InternalServerError("go.micro.client", err.Error())
}
b := &buffer{bytes.NewBuffer(nil)}
// new buffer
b := buf.New(nil)
if err := cf(b).Write(&codec.Message{
Target: topic,
Type: codec.Publication,
Type: codec.Event,
Header: map[string]string{
"Micro-Id": id,
"Micro-Topic": msg.Topic(),

View File

@@ -5,10 +5,10 @@ import (
"fmt"
"testing"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/errors"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/registry/memory"
"github.com/micro/go-micro/selector"
)
func newTestRegistry() registry.Registry {
@@ -22,8 +22,7 @@ func TestCallAddress(t *testing.T) {
var called bool
service := "test.service"
endpoint := "Test.Endpoint"
address := "10.1.10.1"
port := 8080
address := "10.1.10.1:8080"
wrap := func(cf CallFunc) CallFunc {
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error {
@@ -41,10 +40,6 @@ func TestCallAddress(t *testing.T) {
return fmt.Errorf("expected address: %s got %s", address, node.Address)
}
if node.Port != port {
return fmt.Errorf("expected address: %d got %d", port, node.Port)
}
// don't do the call
return nil
}
@@ -60,7 +55,7 @@ func TestCallAddress(t *testing.T) {
req := c.NewRequest(service, endpoint, nil)
// test calling remote address
if err := c.Call(context.Background(), req, nil, WithAddress(fmt.Sprintf("%s:%d", address, port))); err != nil {
if err := c.Call(context.Background(), req, nil, WithAddress(address)); err != nil {
t.Fatal("call with address error", err)
}
@@ -114,8 +109,7 @@ func TestCallWrapper(t *testing.T) {
id := "test.1"
service := "test.service"
endpoint := "Test.Endpoint"
address := "10.1.10.1"
port := 8080
address := "10.1.10.1:8080"
wrap := func(cf CallFunc) CallFunc {
return func(ctx context.Context, node *registry.Node, req Request, rsp interface{}, opts CallOptions) error {
@@ -152,7 +146,6 @@ func TestCallWrapper(t *testing.T) {
&registry.Node{
Id: id,
Address: address,
Port: port,
},
},
})

View File

@@ -1,87 +0,0 @@
package client
import (
"sync"
"time"
"github.com/micro/go-micro/transport"
)
type pool struct {
size int
ttl int64
sync.Mutex
conns map[string][]*poolConn
}
type poolConn struct {
transport.Client
created int64
}
func newPool(size int, ttl time.Duration) *pool {
return &pool{
size: size,
ttl: int64(ttl.Seconds()),
conns: make(map[string][]*poolConn),
}
}
// NoOp the Close since we manage it
func (p *poolConn) Close() error {
return nil
}
func (p *pool) getConn(addr string, tr transport.Transport, opts ...transport.DialOption) (*poolConn, error) {
p.Lock()
conns := p.conns[addr]
now := time.Now().Unix()
// while we have conns check age and then return one
// otherwise we'll create a new conn
for len(conns) > 0 {
conn := conns[len(conns)-1]
conns = conns[:len(conns)-1]
p.conns[addr] = conns
// if conn is old kill it and move on
if d := now - conn.created; d > p.ttl {
conn.Client.Close()
continue
}
// we got a good conn, lets unlock and return it
p.Unlock()
return conn, nil
}
p.Unlock()
// create new conn
c, err := tr.Dial(addr, opts...)
if err != nil {
return nil, err
}
return &poolConn{c, time.Now().Unix()}, nil
}
func (p *pool) release(addr string, conn *poolConn, err error) {
// don't store the conn if it has errored
if err != nil {
conn.Client.Close()
return
}
// otherwise put it back for reuse
p.Lock()
conns := p.conns[addr]
if len(conns) >= p.size {
p.Unlock()
conn.Client.Close()
return
}
p.conns[addr] = append(conns, conn)
p.Unlock()
}

View File

@@ -14,13 +14,11 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
@@ -30,8 +28,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
@@ -41,8 +38,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
Address: "localhost:8888",
},
},
},

View File

@@ -2,11 +2,12 @@
package dns
import (
"fmt"
"net"
"strconv"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
)
type dnsSelector struct {
@@ -66,8 +67,7 @@ func (d *dnsSelector) Select(service string, opts ...selector.SelectOption) (sel
for _, node := range srv {
nodes = append(nodes, &registry.Node{
Id: node.Target,
Address: node.Target,
Port: int(node.Port),
Address: fmt.Sprintf("%s:%d", node.Target, node.Port),
})
}

View File

@@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/client/selector"
)
// Set the registry cache ttl

View File

@@ -2,7 +2,7 @@
package registry
import (
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/client/selector"
)
// NewSelector returns a new registry selector

View File

@@ -0,0 +1,272 @@
// Package router is a network/router selector
package router
import (
"context"
"os"
"sort"
"sync"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/router"
pb "github.com/micro/go-micro/router/proto"
)
type routerSelector struct {
opts selector.Options
// the router
r router.Router
// the client we have
c client.Client
// the client for the remote router
rs pb.RouterService
// name of the router
name string
// address of the remote router
addr string
// whether to use the remote router
remote bool
}
type clientKey struct{}
type routerKey struct{}
// getRoutes returns the routes whether they are remote or local
func (r *routerSelector) getRoutes(service string) ([]router.Route, error) {
if !r.remote {
// lookup router for routes for the service
return r.r.Lookup(router.NewQuery(
router.QueryService(service),
))
}
// lookup the remote router
var addrs []string
// set the remote address if specified
if len(r.addr) > 0 {
addrs = append(addrs, r.addr)
} else {
// we have a name so we need to check the registry
services, err := r.c.Options().Registry.GetService(r.name)
if err != nil {
return nil, err
}
for _, service := range services {
for _, node := range service.Nodes {
addrs = append(addrs, node.Address)
}
}
}
// no router addresses available
if len(addrs) == 0 {
return nil, selector.ErrNoneAvailable
}
var pbRoutes *pb.LookupResponse
var err error
// TODO: implement backoff and retries
for _, addr := range addrs {
// call the router
pbRoutes, err = r.rs.Lookup(context.Background(), &pb.LookupRequest{
Query: &pb.Query{
Service: service,
},
}, client.WithAddress(addr))
if err != nil {
continue
}
break
}
// errored out
if err != nil {
return nil, err
}
// no routes
if pbRoutes == nil {
return nil, selector.ErrNoneAvailable
}
var routes []router.Route
// convert from pb to []*router.Route
for _, r := range pbRoutes.Routes {
routes = append(routes, router.Route{
Service: r.Service,
Address: r.Address,
Gateway: r.Gateway,
Network: r.Network,
Link: r.Link,
Metric: int(r.Metric),
})
}
return routes, nil
}
func (r *routerSelector) Init(opts ...selector.Option) error {
// no op
return nil
}
func (r *routerSelector) Options() selector.Options {
return r.opts
}
func (r *routerSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) {
// TODO: pull routes asynchronously and cache
routes, err := r.getRoutes(service)
if err != nil {
return nil, err
}
// no routes return not found error
if len(routes) == 0 {
return nil, selector.ErrNotFound
}
// TODO: apply filters by pseudo constructing service
// sort the routes based on metric
sort.Slice(routes, func(i, j int) bool {
return routes[i].Metric < routes[j].Metric
})
// roundrobin assuming routes are in metric preference order
var i int
var mtx sync.Mutex
return func() (*registry.Node, error) {
// get index and increment counter with every call to next
mtx.Lock()
idx := i
i++
mtx.Unlock()
// get route based on idx
route := routes[idx%len(routes)]
// defaults to gateway and no port
address := route.Address
if len(route.Gateway) > 0 {
address = route.Gateway
}
// return as a node
return &registry.Node{
// TODO: add id and metadata if we can
Address: address,
}, nil
}, nil
}
func (r *routerSelector) Mark(service string, node *registry.Node, err error) {
// TODO: pass back metrics or information to the router
return
}
func (r *routerSelector) Reset(service string) {
// TODO: reset the metrics or information at the router
return
}
func (r *routerSelector) Close() error {
// stop the router advertisements
return r.r.Stop()
}
func (r *routerSelector) String() string {
return "router"
}
// NewSelector returns a new router based selector
func NewSelector(opts ...selector.Option) selector.Selector {
options := selector.Options{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
// set default registry if not set
if options.Registry == nil {
options.Registry = registry.DefaultRegistry
}
// try get router from the context
r, ok := options.Context.Value(routerKey{}).(router.Router)
if !ok {
// TODO: Use router.DefaultRouter?
r = router.NewRouter(
router.Registry(options.Registry),
)
}
// try get client from the context
c, ok := options.Context.Value(clientKey{}).(client.Client)
if !ok {
c = client.DefaultClient
}
// get the router from env vars if its a remote service
remote := true
routerName := os.Getenv("MICRO_ROUTER")
routerAddress := os.Getenv("MICRO_ROUTER_ADDRESS")
// start the router advertisements if we're running it locally
if len(routerName) == 0 && len(routerAddress) == 0 {
go r.Advertise()
remote = false
}
return &routerSelector{
opts: options,
// set the internal router
r: r,
// set the client
c: c,
// set the router client
rs: pb.NewRouterService(routerName, c),
// name of the router
name: routerName,
// address of router
addr: routerAddress,
// let ourselves know to use the remote router
remote: remote,
}
}
// WithClient sets the client for the request
func WithClient(c client.Client) selector.Option {
return func(o *selector.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, clientKey{}, c)
}
}
// WithRouter sets the router as an option
func WithRouter(r router.Router) selector.Option {
return func(o *selector.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, routerKey{}, r)
}
}

View File

@@ -2,11 +2,8 @@
package static
import (
"net"
"strconv"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
)
// staticSelector is a static selector
@@ -26,20 +23,10 @@ func (s *staticSelector) Options() selector.Options {
}
func (s *staticSelector) Select(service string, opts ...selector.SelectOption) (selector.Next, error) {
var port int
addr, pt, err := net.SplitHostPort(service)
if err != nil {
addr = service
port = 0
} else {
port, _ = strconv.Atoi(pt)
}
return func() (*registry.Node, error) {
return &registry.Node{
Id: service,
Address: addr,
Port: port,
Address: service,
}, nil
}, nil
}

View File

@@ -14,13 +14,11 @@ func TestStrategies(t *testing.T) {
Nodes: []*registry.Node{
&registry.Node{
Id: "test1-1",
Address: "10.0.0.1",
Port: 1001,
Address: "10.0.0.1:1001",
},
&registry.Node{
Id: "test1-2",
Address: "10.0.0.2",
Port: 1002,
Address: "10.0.0.2:1002",
},
},
},
@@ -30,13 +28,11 @@ func TestStrategies(t *testing.T) {
Nodes: []*registry.Node{
&registry.Node{
Id: "test1-3",
Address: "10.0.0.3",
Port: 1003,
Address: "10.0.0.3:1003",
},
&registry.Node{
Id: "test1-4",
Address: "10.0.0.4",
Port: 1004,
Address: "10.0.0.4:1004",
},
},
},

View File

@@ -9,7 +9,7 @@ const (
Error MessageType = iota
Request
Response
Publication
Event
)
type MessageType int

View File

@@ -33,7 +33,7 @@ func (j *jsonCodec) Write(m *codec.Message, b interface{}) error {
return j.c.Write(m, b)
case codec.Response, codec.Error:
return j.s.Write(m, b)
case codec.Publication:
case codec.Event:
data, err := json.Marshal(b)
if err != nil {
return err
@@ -54,7 +54,7 @@ func (j *jsonCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
return j.s.ReadHeader(m)
case codec.Response:
return j.c.ReadHeader(m)
case codec.Publication:
case codec.Event:
_, err := io.Copy(j.buf, j.rwc)
return err
default:
@@ -69,7 +69,7 @@ func (j *jsonCodec) ReadBody(b interface{}) error {
return j.s.ReadBody(b)
case codec.Response:
return j.c.ReadBody(b)
case codec.Publication:
case codec.Event:
if b != nil {
return json.Unmarshal(j.buf.Bytes(), b)
}

View File

@@ -99,7 +99,7 @@ func (c *protoCodec) Write(m *codec.Message, b interface{}) error {
return err
}
}
case codec.Publication:
case codec.Event:
data, err := proto.Marshal(b.(proto.Message))
if err != nil {
return err
@@ -141,7 +141,7 @@ func (c *protoCodec) ReadHeader(m *codec.Message, mt codec.MessageType) error {
m.Method = rtmp.GetServiceMethod()
m.Id = fmt.Sprintf("%d", rtmp.GetSeq())
m.Error = rtmp.GetError()
case codec.Publication:
case codec.Event:
_, err := io.Copy(c.buf, c.rwc)
return err
default:
@@ -159,7 +159,7 @@ func (c *protoCodec) ReadBody(b interface{}) error {
if err != nil {
return err
}
case codec.Publication:
case codec.Event:
data = c.buf.Bytes()
default:
return fmt.Errorf("Unrecognised message type: %v", c.mt)

View File

@@ -14,13 +14,11 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
@@ -30,8 +28,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
@@ -41,8 +38,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
Address: "localhost:8888",
},
},
},

View File

@@ -1,6 +1,6 @@
# Config [![GoDoc](https://godoc.org/github.com/micro/go-micro/config?status.svg)](https://godoc.org/github.com/micro/go-micro/config)
Go Config is a pluggable dynamic config library.
Config is a pluggable dynamic config package
Most config in applications are statically configured or include complex logic to load from multiple sources.
Go Config makes this easy, pluggable and mergeable. You'll never have to deal with config in the same way again.

View File

@@ -32,9 +32,10 @@ import (
rmem "github.com/micro/go-micro/registry/memory"
// selectors
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/selector/dns"
"github.com/micro/go-micro/selector/static"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/client/selector/dns"
"github.com/micro/go-micro/client/selector/router"
"github.com/micro/go-micro/client/selector/static"
// transports
"github.com/micro/go-micro/transport"
@@ -196,6 +197,7 @@ var (
"default": selector.NewSelector,
"dns": dns.NewSelector,
"cache": selector.NewSelector,
"router": router.NewSelector,
"static": static.NewSelector,
}

View File

@@ -5,8 +5,8 @@ import (
"github.com/micro/go-micro/broker"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/client/selector"
"github.com/micro/go-micro/registry"
"github.com/micro/go-micro/selector"
"github.com/micro/go-micro/server"
"github.com/micro/go-micro/transport"
)

View File

@@ -8,9 +8,25 @@ import (
"testing"
"time"
"github.com/micro/go-micro/config/source/env"
"github.com/micro/go-micro/config/source/file"
)
func createFileForIssue18(t *testing.T, content string) *os.File {
data := []byte(content)
path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
fh, err := os.Create(path)
if err != nil {
t.Error(err)
}
_, err = fh.Write(data)
if err != nil {
t.Error(err)
}
return fh
}
func createFileForTest(t *testing.T) *os.File {
data := []byte(`{"foo": "bar"}`)
path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
@@ -26,7 +42,7 @@ func createFileForTest(t *testing.T) *os.File {
return fh
}
func TestLoadWithGoodFile(t *testing.T) {
func TestConfigLoadWithGoodFile(t *testing.T) {
fh := createFileForTest(t)
path := fh.Name()
defer func() {
@@ -44,7 +60,7 @@ func TestLoadWithGoodFile(t *testing.T) {
}
}
func TestLoadWithInvalidFile(t *testing.T) {
func TestConfigLoadWithInvalidFile(t *testing.T) {
fh := createFileForTest(t)
path := fh.Name()
defer func() {
@@ -68,34 +84,35 @@ func TestLoadWithInvalidFile(t *testing.T) {
}
}
func TestConsul(t *testing.T) {
/*consulSource := consul.NewSource(
// optionally specify consul address; default to localhost:8500
consul.WithAddress("131.150.38.111:8500"),
// optionally specify prefix; defaults to /micro/config
consul.WithPrefix("/project"),
// optionally strip the provided prefix from the keys, defaults to false
consul.StripPrefix(true),
consul.WithDatacenter("dc1"),
consul.WithToken("xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"),
func TestConfigMerge(t *testing.T) {
fh := createFileForIssue18(t, `{
"amqp": {
"host": "rabbit.platform",
"port": 80
},
"handler": {
"exchange": "springCloudBus"
}
}`)
path := fh.Name()
defer func() {
fh.Close()
os.Remove(path)
}()
os.Setenv("AMQP_HOST", "rabbit.testing.com")
conf := NewConfig()
conf.Load(
file.NewSource(
file.WithPath(path),
),
env.NewSource(),
)
// Create new config
conf := NewConfig()
// Load file source
err := conf.Load(consulSource)
if err != nil {
t.Error(err)
return
actualHost := conf.Get("amqp", "host").String("backup")
if actualHost != "rabbit.testing.com" {
t.Fatalf("Expected %v but got %v",
"rabbit.testing.com",
actualHost)
}
m := conf.Map()
t.Log("m: ", m)
v := conf.Get("project", "dc111", "port")
t.Log("v: ", v.Int(13))*/
t.Log("OK")
}

View File

@@ -1,60 +0,0 @@
package config
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/micro/go-micro/config/source/env"
"github.com/micro/go-micro/config/source/file"
)
func createFileForIssue18(t *testing.T, content string) *os.File {
data := []byte(content)
path := filepath.Join(os.TempDir(), fmt.Sprintf("file.%d", time.Now().UnixNano()))
fh, err := os.Create(path)
if err != nil {
t.Error(err)
}
_, err = fh.Write(data)
if err != nil {
t.Error(err)
}
return fh
}
func TestIssue18(t *testing.T) {
fh := createFileForIssue18(t, `{
"amqp": {
"host": "rabbit.platform",
"port": 80
},
"handler": {
"exchange": "springCloudBus"
}
}`)
path := fh.Name()
defer func() {
fh.Close()
os.Remove(path)
}()
os.Setenv("AMQP_HOST", "rabbit.testing.com")
conf := NewConfig()
conf.Load(
file.NewSource(
file.WithPath(path),
),
env.NewSource(),
)
actualHost := conf.Get("amqp", "host").String("backup")
if actualHost != "rabbit.testing.com" {
t.Fatalf("Expected %v but got %v",
"rabbit.testing.com",
actualHost)
}
}

View File

@@ -1,39 +1,85 @@
package json
import (
"reflect"
"testing"
"github.com/micro/go-micro/config/source"
)
func TestValues(t *testing.T) {
data := []byte(`{"foo": "bar", "baz": {"bar": "cat"}}`)
emptyStr := ""
testData := []struct {
path []string
value string
csdata []byte
path []string
accepter interface{}
value interface{}
}{
{
[]byte(`{"foo": "bar", "baz": {"bar": "cat"}}`),
[]string{"foo"},
emptyStr,
"bar",
},
{
[]byte(`{"foo": "bar", "baz": {"bar": "cat"}}`),
[]string{"baz", "bar"},
emptyStr,
"cat",
},
}
values, err := newValues(&source.ChangeSet{
Data: data,
})
for idx, test := range testData {
values, err := newValues(&source.ChangeSet{
Data: test.csdata,
})
if err != nil {
t.Fatal(err)
}
if err != nil {
t.Fatal(err)
}
for _, test := range testData {
if v := values.Get(test.path...).String(""); v != test.value {
t.Fatalf("Expected %s got %s for path %v", test.value, v, test.path)
err = values.Get(test.path...).Scan(&test.accepter)
if err != nil {
t.Fatal(err)
}
if test.accepter != test.value {
t.Fatalf("No.%d Expected %v got %v for path %v", idx, test.value, test.accepter, test.path)
}
}
}
func TestStructArray(t *testing.T) {
type T struct {
Foo string
}
emptyTSlice := []T{}
testData := []struct {
csdata []byte
accepter []T
value []T
}{
{
[]byte(`[{"foo": "bar"}]`),
emptyTSlice,
[]T{T{Foo: "bar"}},
},
}
for idx, test := range testData {
values, err := newValues(&source.ChangeSet{
Data: test.csdata,
})
if err != nil {
t.Fatal(err)
}
err = values.Get().Scan(&test.accepter)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(test.accepter, test.value) {
t.Fatalf("No.%d Expected %v got %v", idx, test.value, test.accepter)
}
}
}

View File

@@ -9,7 +9,7 @@ import (
"github.com/imdario/mergo"
"github.com/micro/cli"
"github.com/micro/go-micro/cmd"
"github.com/micro/go-micro/config/cmd"
"github.com/micro/go-micro/config/source"
)

View File

@@ -6,7 +6,7 @@ import (
"testing"
"github.com/micro/cli"
"github.com/micro/go-micro/cmd"
"github.com/micro/go-micro/config/cmd"
"github.com/micro/go-micro/config/source"
)

View File

@@ -4,7 +4,7 @@ The consul source reads config from consul key/values
## Consul Format
The consul source expects keys under the default prefix `micro/config`
The consul source expects keys under the default prefix `/micro/config`
Values are expected to be json
@@ -29,8 +29,8 @@ Specify source with data
consulSource := consul.NewSource(
// optionally specify consul address; default to localhost:8500
consul.WithAddress("10.0.0.10:8500"),
// optionally specify prefix; defaults to micro/config
consul.WithPrefix("my/prefix"),
// optionally specify prefix; defaults to /micro/config
consul.WithPrefix("/my/prefix"),
// optionally strip the provided prefix from the keys, defaults to false
consul.StripPrefix(true),
)

View File

@@ -21,7 +21,7 @@ type consul struct {
var (
// DefaultPrefix is the prefix that consul keys will be assumed to have if you
// haven't specified one
DefaultPrefix = "micro/config/"
DefaultPrefix = "/micro/config/"
)
func (c *consul) Read() (*source.ChangeSet, error) {
@@ -74,6 +74,11 @@ func NewSource(opts ...source.Option) source.Source {
// use default config
config := api.DefaultConfig()
// use the consul config passed in the options if any
if co, ok := options.Context.Value(configKey{}).(*api.Config); ok {
config = co
}
// check if there are any addrs
a, ok := options.Context.Value(addressKey{}).(string)
if ok {

View File

@@ -3,6 +3,7 @@ package consul
import (
"context"
"github.com/hashicorp/consul/api"
"github.com/micro/go-micro/config/source"
)
@@ -11,6 +12,7 @@ type prefixKey struct{}
type stripPrefixKey struct{}
type dcKey struct{}
type tokenKey struct{}
type configKey struct{}
// WithAddress sets the consul address
func WithAddress(a string) source.Option {
@@ -61,3 +63,13 @@ func WithToken(p string) source.Option {
o.Context = context.WithValue(o.Context, tokenKey{}, p)
}
}
// WithConfig set consul-specific options
func WithConfig(c *api.Config) source.Option {
return func(o *source.Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, configKey{}, c)
}
}

View File

@@ -8,40 +8,80 @@ import (
"github.com/micro/go-micro/config/encoder"
)
type configValue interface {
Value() interface{}
Decode(encoder.Encoder, []byte) error
}
type configArrayValue struct {
v []interface{}
}
func (a *configArrayValue) Value() interface{} { return a.v }
func (a *configArrayValue) Decode(e encoder.Encoder, b []byte) error {
return e.Decode(b, &a.v)
}
type configMapValue struct {
v map[string]interface{}
}
func (m *configMapValue) Value() interface{} { return m.v }
func (m *configMapValue) Decode(e encoder.Encoder, b []byte) error {
return e.Decode(b, &m.v)
}
func makeMap(e encoder.Encoder, kv api.KVPairs, stripPrefix string) (map[string]interface{}, error) {
data := make(map[string]interface{})
// consul guarantees lexicographic order, so no need to sort
for _, v := range kv {
pathString := strings.TrimPrefix(strings.TrimPrefix(v.Key, stripPrefix), "/")
var val map[string]interface{}
pathString := strings.TrimPrefix(strings.TrimPrefix(v.Key, strings.TrimPrefix(stripPrefix, "/")), "/")
if pathString == "" {
continue
}
var val configValue
var err error
// ensure a valid value is stored at this location
if len(v.Value) > 0 {
if err := e.Decode(v.Value, &val); err != nil {
// try to decode into map value or array value
arrayV := &configArrayValue{v: []interface{}{}}
mapV := &configMapValue{v: map[string]interface{}{}}
switch {
case arrayV.Decode(e, v.Value) == nil:
val = arrayV
case mapV.Decode(e, v.Value) == nil:
val = mapV
default:
return nil, fmt.Errorf("faild decode value. path: %s, error: %s", pathString, err)
}
}
// set target at the root
target := data
// then descend to the target location, creating as we go, if need be
if pathString != "" {
path := strings.Split(pathString, "/")
// find (or create) the location we want to put this value at
for _, dir := range path {
if _, ok := target[dir]; !ok {
target[dir] = make(map[string]interface{})
}
target = target[dir].(map[string]interface{})
path := strings.Split(pathString, "/")
// find (or create) the leaf node we want to put this value at
for _, dir := range path[:len(path)-1] {
if _, ok := target[dir]; !ok {
target[dir] = make(map[string]interface{})
}
target = target[dir].(map[string]interface{})
}
leafDir := path[len(path)-1]
// copy over the keys from the value
for k := range val {
target[k] = val[k]
switch val.(type) {
case *configArrayValue:
target[leafDir] = val.Value()
case *configMapValue:
target[leafDir] = make(map[string]interface{})
target = target[leafDir].(map[string]interface{})
mapv := val.Value().(map[string]interface{})
for k := range mapv {
target[k] = mapv[k]
}
}
}

2
data/data.go Normal file
View File

@@ -0,0 +1,2 @@
// Package data is an interface for data access
package data

View File

@@ -32,10 +32,16 @@ func (m *memoryStore) Dump() ([]*store.Record, error) {
d := v.r.Expiry
t := time.Since(v.c)
// expired
if d > time.Duration(0) && t > d {
continue
if d > time.Duration(0) {
// expired
if t > d {
continue
}
// update expiry
v.r.Expiry -= t
v.c = time.Now()
}
values = append(values, v.r)
}
@@ -56,8 +62,13 @@ func (m *memoryStore) Read(key string) (*store.Record, error) {
t := time.Since(v.c)
// expired
if d > time.Duration(0) && t > d {
return nil, store.ErrNotFound
if d > time.Duration(0) {
if t > d {
return nil, store.ErrNotFound
}
// update expiry
v.r.Expiry -= t
v.c = time.Now()
}
return v.r, nil

View File

@@ -0,0 +1,37 @@
package memory
import (
"testing"
"time"
"github.com/micro/go-micro/data/store"
)
func TestReadRecordExpire(t *testing.T) {
s := NewStore()
var (
key = "foo"
expire = 100 * time.Millisecond
)
rec := &store.Record{
Key: key,
Value: nil,
Expiry: expire,
}
s.Write(rec)
rrec, err := s.Read(key)
if err != nil {
t.Fatal(err)
}
if rrec.Expiry >= expire {
t.Fatal("expiry of read record is not changed")
}
time.Sleep(expire)
if _, err := s.Read(key); err != store.ErrNotFound {
t.Fatal("expire elapsed, but key still accessable")
}
}

46
go.mod
View File

@@ -3,7 +3,7 @@ module github.com/micro/go-micro
go 1.12
require (
cloud.google.com/go v0.40.0 // indirect
cloud.google.com/go v0.41.0 // indirect
github.com/BurntSushi/toml v0.3.1
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect
github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect
@@ -17,16 +17,14 @@ require (
github.com/fsouza/go-dockerclient v1.4.1
github.com/ghodss/yaml v1.0.0
github.com/gliderlabs/ssh v0.2.2 // indirect
github.com/go-kit/kit v0.9.0 // indirect
github.com/go-log/log v0.1.0
github.com/go-playground/locales v0.12.1 // indirect
github.com/go-playground/universal-translator v0.16.0 // indirect
github.com/golang/mock v1.3.1 // indirect
github.com/golang/protobuf v1.3.1
github.com/google/btree v1.0.0 // indirect
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f // indirect
github.com/golang/protobuf v1.3.2
github.com/google/uuid v1.1.1
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/gorilla/handlers v1.4.0
github.com/gorilla/handlers v1.4.1
github.com/gorilla/mux v1.7.3 // indirect
github.com/gorilla/websocket v1.4.0
github.com/hashicorp/consul/api v1.1.0
github.com/hashicorp/go-immutable-radix v1.1.0 // indirect
@@ -42,20 +40,21 @@ require (
github.com/imdario/mergo v0.3.7
github.com/joncalhoun/qson v0.0.0-20170526102502-8a9cab3a62b1
github.com/json-iterator/go v1.1.6
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c // indirect
github.com/kisielk/errcheck v1.2.0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pty v1.1.5 // indirect
github.com/kr/pty v1.1.8 // indirect
github.com/leodido/go-urn v1.1.0 // indirect
github.com/lucas-clemente/quic-go v0.11.2
github.com/marten-seemann/qtls v0.2.4 // indirect
github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/micro/cli v0.2.0
github.com/micro/mdns v0.1.0
github.com/miekg/dns v1.1.14 // indirect
github.com/micro/mdns v0.2.0
github.com/miekg/dns v1.1.15 // indirect
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/hashstructure v1.0.0
github.com/nats-io/nats.go v1.8.1
github.com/nats-io/nkeys v0.1.0 // indirect
github.com/nlopes/slack v0.5.0
github.com/olekukonko/tablewriter v0.0.1
github.com/onsi/ginkgo v1.8.0 // indirect
@@ -63,23 +62,22 @@ require (
github.com/pkg/errors v0.8.1
github.com/posener/complete v1.2.1 // indirect
github.com/prometheus/common v0.6.0 // indirect
github.com/prometheus/procfs v0.0.3 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/stretchr/objx v0.2.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
go.opencensus.io v0.22.0 // indirect
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522 // indirect
golang.org/x/image v0.0.0-20190618124811-92942e4437e2 // indirect
golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f // indirect
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9 // indirect
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9 // indirect
golang.org/x/mod v0.1.0 // indirect
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
golang.org/x/sys v0.0.0-20190621062556-bf70e4678053 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
golang.org/x/tools v0.0.0-20190620191750-1fa568393b23 // indirect
google.golang.org/appengine v1.6.1 // indirect
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 // indirect
google.golang.org/grpc v1.21.1
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 // indirect
golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8 // indirect
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect
google.golang.org/grpc v1.22.0
gopkg.in/go-playground/validator.v9 v9.29.0
gopkg.in/src-d/go-billy.v4 v4.3.1 // indirect
gopkg.in/src-d/go-git.v4 v4.12.0
gopkg.in/telegram-bot-api.v4 v4.6.4
honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b // indirect

49
go.sum
View File

@@ -2,6 +2,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -39,6 +40,7 @@ github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882b
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/docker v0.7.3-0.20190309235953-33c3200e0d16 h1:dmUn0SuGx7unKFwxyeQ/oLUHhEfZosEDrpmYM+6MTuc=
@@ -65,6 +67,7 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev
github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-log/log v0.1.0 h1:wudGTNsiGzrD5ZjgIkVZ517ugi2XRe9Q/xRCzwEO4/U=
github.com/go-log/log v0.1.0/go.mod h1:4mBwpdRMFLiuXZDCwU2lKQFsoSCo72j3HqBK9d81N2M=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@@ -85,6 +88,8 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
@@ -101,7 +106,10 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.4.1 h1:BHvcRGJe/TrL+OqFxoKQGddTgeibiOjaBssV5a/N9sw=
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA=
@@ -170,6 +178,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8=
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c h1:VAx3LRNjVNvjtgO7KFRuT/3aye/0zJvwn01rHSfoolo=
github.com/kevinburke/ssh_config v0.0.0-20190630040420-2e50c441276c/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -179,15 +189,21 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031 h1:wjcGvgllMOQw8wNYFH6acq/KlTAdjKMSo1EUYybHXto=
github.com/lucas-clemente/quic-go v0.7.1-0.20190710050138-1441923ab031/go.mod h1:lb5aAxL68VvhZ00e7yYuQVK/9FLggtYy4qo7oI5qzqA=
github.com/lucas-clemente/quic-go v0.11.2 h1:Mop0ac3zALaBR3wGs6j8OYe/tcFvFsxTUFMkE/7yUOI=
github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw=
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
github.com/marten-seemann/qtls v0.2.3 h1:0yWJ43C62LsZt08vuQJDK1uC1czUc3FJeCLPoNAI4vA=
github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/marten-seemann/qtls v0.2.4 h1:mCJ6i1jAqcsm9XODrSGvXECodoAb1STta+TkxJCwCnE=
github.com/marten-seemann/qtls v0.2.4/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/marten-seemann/qtls v0.3.1 h1:ySYIvhFjFY2JsNHY6VACvomMEDy3EvdPA6yciUFAiHw=
github.com/marten-seemann/qtls v0.3.1/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@@ -199,11 +215,17 @@ github.com/micro/cli v0.2.0 h1:ut3rV5JWqZjsXIa2MvGF+qMUP8DAUTvHX9Br5gO4afA=
github.com/micro/cli v0.2.0/go.mod h1:jRT9gmfVKWSS6pkKcXQ8YhUyj6bzwxK8Fp5b0Y7qNnk=
github.com/micro/mdns v0.1.0 h1:fuLybUsfynbigJmCot/54i+gwe0hpc/vtCMvWt2WfDI=
github.com/micro/mdns v0.1.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc=
github.com/micro/mdns v0.1.1-0.20190729112526-ef68c9635478 h1:L6jnZZ763dMLlvst8P0dWHa1WbUu7ppUY1q3AY2hhIU=
github.com/micro/mdns v0.1.1-0.20190729112526-ef68c9635478/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc=
github.com/micro/mdns v0.2.0 h1:/+/n2PSiJURrXsBIGtfCz0hex/XYKqVsn51GAGdFrOE=
github.com/micro/mdns v0.2.0/go.mod h1:KJ0dW7KmicXU2BV++qkLlmHYcVv7/hHnbtguSWt9Aoc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.3 h1:1g0r1IvskvgL8rR+AcHzUA+oFmGcQlaIm4IqakufeMM=
github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.14 h1:wkQWn9wIp4mZbwW8XV6Km6owkvRPbOiV004ZM2CkGvA=
github.com/miekg/dns v1.1.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -228,6 +250,8 @@ github.com/nats-io/nats.go v1.8.1 h1:6lF/f1/NN6kzUDBz6pyvQDEXO39jqXcWRLu/tKjtOUQ
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
github.com/nats-io/nkeys v0.0.2 h1:+qM7QpgXnvDDixitZtQUBDY9w/s9mu1ghS+JIbsrx6M=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nlopes/slack v0.5.0 h1:NbIae8Kd0NpqaEI3iUrsuS0KbcEDhzhc939jLW5fNm0=
@@ -266,6 +290,7 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -308,11 +333,15 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmV
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190627132806-fd42eb6b336f/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190618124811-92942e4437e2/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -320,6 +349,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190607214518-6fa95d984e88/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -331,6 +361,7 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -342,6 +373,8 @@ golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -372,6 +405,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190621062556-bf70e4678053 h1:T0MJjz97TtCXa3ZNW2Oenb3KQWB91K965zMEbIJ4ThA=
golang.org/x/sys v0.0.0-20190621062556-bf70e4678053/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ=
golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -389,11 +425,16 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190530171427-2b03ca6e44eb/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190620191750-1fa568393b23/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190710184609-286818132824/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -408,10 +449,15 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601 h1:9VBRTdmgQxbs6HE0sUnMrSWNePppAJU07NYvX5dIB04=
google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190626174449-989357319d63/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 h1:5pOB7se0B2+IssELuQUs6uoBgYJenkU2AQlvopc2sRw=
google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -423,6 +469,8 @@ gopkg.in/src-d/go-billy.v4 v4.2.1 h1:omN5CrMrMcQ+4I8bJ0wEhOBPanIRWzFC953IiXKdYzo
gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek=
gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-billy.v4 v4.3.1 h1:OkK1DmefDy1Z6Veu82wdNj/cLpYORhdX4qdaYCPwc7s=
gopkg.in/src-d/go-billy.v4 v4.3.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.11.0 h1:cJwWgJ0DXifrNrXM6RGN1Y2yR60Rr1zQ9Q5DX5S9qgU=
@@ -441,5 +489,6 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190614002413-cb51c254f01b/go.mod h1:JlmFZigtG9vBVR3QGIQ9g/Usz4BzH+Xm6Z8iHQWRYUw=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

288
network/link/default.go Normal file
View File

@@ -0,0 +1,288 @@
// Package link provides a measured transport.Socket link
package link
import (
"io"
"sync"
"time"
"github.com/micro/go-micro/config/options"
"github.com/micro/go-micro/transport"
)
type link struct {
sync.RWMutex
// the link id
id string
// the remote end to dial
addr string
// channel used to close the link
closed chan bool
// if its connected
connected bool
// the transport to use
transport transport.Transport
// the send queue to the socket
sendQueue chan *transport.Message
// the recv queue to the socket
recvQueue chan *transport.Message
// the socket for this link
socket transport.Socket
// determines the cost of the link
// based on queue length and roundtrip
length int
weight int
}
func newLink(options options.Options) *link {
// default values
var sock transport.Socket
var addr string
id := "local"
tr := transport.DefaultTransport
lid, ok := options.Values().Get("link.id")
if ok {
id = lid.(string)
}
laddr, ok := options.Values().Get("link.address")
if ok {
addr = laddr.(string)
}
ltr, ok := options.Values().Get("link.transport")
if ok {
tr = ltr.(transport.Transport)
}
lsock, ok := options.Values().Get("link.socket")
if ok {
sock = lsock.(transport.Socket)
}
l := &link{
// the remote end to dial
addr: addr,
// transport to dial link
transport: tr,
// the socket to use
// this is nil if not specified
socket: sock,
// unique id assigned to the link
id: id,
// the closed channel used to close the conn
closed: make(chan bool),
// then send queue
sendQueue: make(chan *transport.Message, 128),
// the receive queue
recvQueue: make(chan *transport.Message, 128),
}
// return the link
return l
}
// link methods
// process processes messages on the send and receive queues.
func (l *link) process() {
go func() {
for {
m := new(transport.Message)
if err := l.recv(m); err != nil {
return
}
select {
case l.recvQueue <- m:
case <-l.closed:
return
}
}
}()
// messages sent
i := 0
length := 0
for {
select {
case m := <-l.sendQueue:
t := time.Now()
// send the message
if err := l.send(m); err != nil {
return
}
// get header size, body size and time taken
hl := len(m.Header)
bl := len(m.Body)
d := time.Since(t)
// don't calculate on empty messages
if hl == 0 && bl == 0 {
continue
}
// increment sent
i++
// time take to send some bits and bytes
td := float64(hl+bl) / float64(d.Nanoseconds())
// increase the scale
td += 1
// judge the length
length = int(td) / (length + int(td))
// every 10 messages update length
if (i % 10) == 1 {
// cost average the length
// save it
l.Lock()
l.length = length
l.Unlock()
}
case <-l.closed:
return
}
}
}
// send a message over the link
func (l *link) send(m *transport.Message) error {
// TODO: measure time taken and calculate length/rate
// send via the transport socket
return l.socket.Send(m)
}
// recv a message on the link
func (l *link) recv(m *transport.Message) error {
if m.Header == nil {
m.Header = make(map[string]string)
}
// receive the transport message
return l.socket.Recv(m)
}
// Connect attempts to connect to an address and sets the socket
func (l *link) Connect() error {
l.Lock()
if l.connected {
l.Unlock()
return nil
}
defer l.Unlock()
// replace closed
l.closed = make(chan bool)
// assume existing socket
if len(l.addr) == 0 {
go l.process()
return nil
}
// dial the endpoint
c, err := l.transport.Dial(l.addr)
if err != nil {
return err
}
// set the socket
l.socket = c
// kick start the processing
go l.process()
return nil
}
// Close the link
func (l *link) Close() error {
select {
case <-l.closed:
return nil
default:
close(l.closed)
l.Lock()
l.connected = false
l.Unlock()
return l.socket.Close()
}
}
// returns the node id
func (l *link) Id() string {
l.RLock()
defer l.RUnlock()
return l.id
}
// the remote ip of the link
func (l *link) Remote() string {
l.RLock()
defer l.RUnlock()
return l.socket.Remote()
}
// the local ip of the link
func (l *link) Local() string {
l.RLock()
defer l.RUnlock()
return l.socket.Local()
}
// length/rate of the link
func (l *link) Length() int {
l.RLock()
defer l.RUnlock()
return l.length
}
// weight checks the size of the queues
func (l *link) Weight() int {
return len(l.sendQueue) + len(l.recvQueue)
}
// Accept accepts a message on the socket
func (l *link) Recv(m *transport.Message) error {
select {
case <-l.closed:
return io.EOF
case rm := <-l.recvQueue:
*m = *rm
return nil
}
// never reach
return nil
}
// Send sends a message on the socket immediately
func (l *link) Send(m *transport.Message) error {
select {
case <-l.closed:
return io.EOF
case l.sendQueue <- m:
}
return nil
}
func (l *link) Status() string {
select {
case <-l.closed:
return "closed"
default:
return "connected"
}
}

60
network/link/link.go Normal file
View File

@@ -0,0 +1,60 @@
// Package link provides a measured link on top of a transport.Socket
package link
import (
"errors"
"github.com/micro/go-micro/config/options"
"github.com/micro/go-micro/transport"
)
// Link is a layer on top of a transport socket with the
// buffering send and recv queue's with the ability to
// measure the actual transport link and reconnect if
// an address is specified.
type Link interface {
// provides the transport.Socket interface
transport.Socket
// Connect connects the link. It must be called first
// if there's an expectation to create a new socket.
Connect() error
// Id of the link is "local" if not specified
Id() string
// Status of the link
Status() string
// Depth of the buffers
Weight() int
// Rate of the link
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...))
}
// Sets the link id which otherwise defaults to "local"
func Id(id string) options.Option {
return options.WithValue("link.id", id)
}
// The address to use for the link. Connect must be
// called for this to be used, its otherwise unused.
func Address(a string) options.Option {
return options.WithValue("link.address", a)
}
// The transport to use for the link where we
// want to dial the connection first.
func Transport(t transport.Transport) options.Option {
return options.WithValue("link.transport", t)
}
// Socket sets the socket to use instead of dialing.
func Socket(s transport.Socket) options.Option {
return options.WithValue("link.socket", s)
}

View File

@@ -1,47 +1,2 @@
// Package network is a package for defining a network overlay
// Package network is for creating internetworks
package network
import (
"github.com/micro/go-micro/config/options"
)
// Network is an interface defining networks or graphs
type Network interface {
options.Options
// Id of this node
Id() uint64
// Connect to a node
Connect(id uint64) (Link, error)
// Close the network connection
Close() error
// Accept messages on the network
Accept() (*Message, error)
// Send a message to the network
Send(*Message) error
// Retrieve list of connections
Links() ([]Link, error)
}
// Node represents a network node
type Node interface {
// Node is a network. Network is a node.
Network
}
// Link is a connection to another node
type Link interface {
// remote node
Node
// length of link which dictates speed
Length() int
// weight of link which dictates curvature
Weight() int
}
// Message is the base type for opaque data
type Message []byte
var (
// TODO: set default network
DefaultNetwork Network
)

View File

@@ -0,0 +1,30 @@
// Package dns resolves names to dns srv records
package dns
import (
"fmt"
"net"
"github.com/micro/go-micro/network/resolver"
)
type Resolver struct{}
// Resolve assumes ID is a domain name e.g micro.mu
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
_, addrs, err := net.LookupSRV("network", "udp", name)
if err != nil {
return nil, err
}
var records []*resolver.Record
for _, addr := range addrs {
address := addr.Target
if addr.Port > 0 {
address = fmt.Sprintf("%s:%d", addr.Target, addr.Port)
}
records = append(records, &resolver.Record{
Address: address,
})
}
return records, nil
}

View File

@@ -0,0 +1,59 @@
// Package http resolves names to network addresses using a http request
package http
import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/micro/go-micro/network/resolver"
)
type Resolver struct {
// If not set, defaults to http
Proto string
// Path sets the path to lookup. Defaults to /network
Path string
}
// Resolve assumes ID is a domain which can be converted to a http://name/network request
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
proto := "http"
path := "/network"
if len(r.Proto) > 0 {
proto = r.Proto
}
if len(r.Path) > 0 {
path = r.Path
}
uri := &url.URL{
Scheme: proto,
Path: path,
Host: name,
}
rsp, err := http.Get(uri.String())
if err != nil {
return nil, err
}
defer rsp.Body.Close()
b, err := ioutil.ReadAll(rsp.Body)
if err != nil {
return nil, err
}
// encoding format is assumed to be json
var records []*resolver.Record
if err := json.Unmarshal(b, &records); err != nil {
return nil, err
}
return records, nil
}

View File

@@ -0,0 +1,37 @@
// Package registry resolves names using the go-micro registry
package registry
import (
"github.com/micro/go-micro/network/resolver"
"github.com/micro/go-micro/registry"
)
type Resolver struct {
// Registry is the registry to use otherwise we use the defaul
Registry registry.Registry
}
// Resolve assumes ID is a domain name e.g micro.mu
func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
reg := r.Registry
if reg == nil {
reg = registry.DefaultRegistry
}
services, err := reg.GetService(name)
if err != nil {
return nil, err
}
var records []*resolver.Record
for _, service := range services {
for _, node := range service.Nodes {
records = append(records, &resolver.Record{
Address: node.Address,
})
}
}
return records, nil
}

View File

@@ -0,0 +1,15 @@
// Package resolver resolves network names to addresses
package resolver
// Resolver is network resolver. It's used to find network nodes
// 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(name string) ([]*Record, error)
}
// A resolved record
type Record struct {
Address string `json:"address"`
}

View File

@@ -7,9 +7,9 @@ import (
"github.com/micro/cli"
"github.com/micro/go-micro/broker"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/cmd"
"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/selector"
"github.com/micro/go-micro/server"
"github.com/micro/go-micro/transport"
)

View File

@@ -1,25 +0,0 @@
# Go Proxy [![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![GoDoc](https://godoc.org/github.com/micro/go-proxy?status.svg)](https://godoc.org/github.com/micro/go-proxy)
Go Proxy is a proxy library for Go Micro.
## Overview
Go Micro is a distributed systems framework for client/server communication. It handles the details
around discovery, fault tolerance, rpc communication, etc. We may want to leverage this in broader ecosystems
which make use of standard http or we may also want to offload a number of requirements to a single proxy.
## Features
- **Transparent Proxy** - Proxy requests to any micro services through a single location. Go Proxy enables
you to write Go Micro proxies which handle and forward requests. This is good for incorporating wrappers.
- **Single Backend Router** - Enable the single backend router to proxy directly to your local app. The proxy
allows you to set a router which serves your backend service whether its http, grpc, etc.
- **Protocol Aware Handler** - Set a request handler which speaks your app protocol to make outbound requests.
Your app may not speak the MUCP protocol so it may be easier to translate internally.
- **Control Planes** - Additionally we support use of control planes to offload many distributed systems concerns.
* [x] [Consul](https://www.consul.io/docs/connect/native.html) - Using Connect-Native to provide secure mTLS.
* [x] [NATS](https://nats.io/) - Fully leveraging NATS as the control plane and data plane.

View File

@@ -3,14 +3,17 @@ package mucp
import (
"context"
"fmt"
"io"
"strings"
"sync"
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/codec"
"github.com/micro/go-micro/codec/bytes"
"github.com/micro/go-micro/config/options"
"github.com/micro/go-micro/proxy"
"github.com/micro/go-micro/router"
"github.com/micro/go-micro/server"
)
@@ -20,11 +23,21 @@ type Proxy struct {
// embed options
options.Options
// Endpoint specified the fixed service endpoint to call.
// Endpoint specifies the fixed service endpoint to call.
Endpoint string
// The client to use for outbound requests
Client client.Client
// The router for routes
Router router.Router
// A fib of routes service:address
sync.RWMutex
Routes map[string]map[uint64]router.Route
// The channel to monitor watcher errors
errChan chan error
}
// read client request and write to server
@@ -62,28 +75,140 @@ func readLoop(r server.Request, s client.Stream) error {
}
}
// ServeRequest honours the server.Router interface
func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
// set default client
if p.Client == nil {
p.Client = client.DefaultClient
// toNodes returns a list of node addresses from given routes
func toNodes(routes map[uint64]router.Route) []string {
var nodes []string
for _, node := range routes {
address := node.Address
if len(node.Gateway) > 0 {
address = node.Gateway
}
nodes = append(nodes, address)
}
return nodes
}
func (p *Proxy) getRoute(service string) ([]string, error) {
// lookup the route cache first
p.Lock()
routes, ok := p.Routes[service]
if ok {
p.Unlock()
return toNodes(routes), nil
}
p.Routes[service] = make(map[uint64]router.Route)
p.Unlock()
// if the router is broken return error
if status := p.Router.Status(); status.Code == router.Error {
return nil, status.Error
}
opts := []client.CallOption{}
// lookup the routes in the router
results, err := p.Router.Lookup(router.NewQuery(router.QueryService(service)))
if err != nil {
return nil, err
}
// update the proxy cache
p.Lock()
for _, route := range results {
p.Routes[service][route.Hash()] = route
}
routes = p.Routes[service]
p.Unlock()
return toNodes(routes), nil
}
// manageRouteCache applies action on a given route to Proxy route cache
func (p *Proxy) manageRouteCache(route router.Route, action string) error {
switch action {
case "create", "update":
if _, ok := p.Routes[route.Service]; !ok {
p.Routes[route.Service] = make(map[uint64]router.Route)
}
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)
}
return nil
}
// watchRoutes watches service routes and updates proxy cache
func (p *Proxy) watchRoutes() {
// this is safe to do as the only way watchRoutes returns is
// when some error is written into error channel - we want to bail then
defer close(p.errChan)
// route watcher
w, err := p.Router.Watch()
if err != nil {
p.errChan <- err
return
}
for {
event, err := w.Next()
if err != nil {
p.errChan <- err
return
}
p.Lock()
if err := p.manageRouteCache(event.Route, fmt.Sprintf("%s", event.Type)); err != nil {
// TODO: should we bail here?
p.Unlock()
continue
}
p.Unlock()
}
}
// ServeRequest honours the server.Router interface
func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server.Response) error {
// service name
service := req.Service()
endpoint := req.Endpoint()
var addresses []string
// call a specific backend
// call a specific backend endpoint either by name or address
if len(p.Endpoint) > 0 {
// address:port
if parts := strings.Split(p.Endpoint, ":"); len(parts) > 1 {
opts = append(opts, client.WithAddress(p.Endpoint))
// use as service name
addresses = []string{p.Endpoint}
} else {
// get route for endpoint from router
addr, err := p.getRoute(p.Endpoint)
if err != nil {
return err
}
// set the address
addresses = addr
// set the name
service = p.Endpoint
}
} else {
// no endpoint was specified just lookup the route
// get route for endpoint from router
addr, err := p.getRoute(service)
if err != nil {
return err
}
addresses = addr
}
var opts []client.CallOption
// set address if available
if len(addresses) > 0 {
opts = append(opts, client.WithAddress(addresses...))
}
// read initial request
@@ -108,28 +233,39 @@ func (p *Proxy) ServeRequest(ctx context.Context, req server.Request, rsp server
// get raw response
resp := stream.Response()
// route watcher error
var watchErr error
// create server response write loop
for {
// read backend response body
body, err := resp.Read()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
select {
case err := <-p.errChan:
if err != nil {
watchErr = err
}
return watchErr
default:
// read backend response body
body, err := resp.Read()
if err == io.EOF {
return nil
} else if err != nil {
return err
}
// read backend response header
hdr := resp.Header()
// read backend response header
hdr := resp.Header()
// write raw response header to client
rsp.WriteHeader(hdr)
// write raw response header to client
rsp.WriteHeader(hdr)
// write raw response body to client
err = rsp.Write(body)
if err == io.EOF {
return nil
} else if err != nil {
return err
// write raw response body to client
err = rsp.Write(body)
if err == io.EOF {
return nil
} else if err != nil {
return err
}
}
}
@@ -162,5 +298,28 @@ func NewProxy(opts ...options.Option) proxy.Proxy {
p.Client = c.(client.Client)
}
// set the default client
if p.Client == nil {
p.Client = client.DefaultClient
}
// get router
r, ok := p.Options.Values().Get("proxy.router")
if ok {
p.Router = r.(router.Router)
}
// create default router and start it
if p.Router == nil {
p.Router = router.DefaultRouter
}
// routes cache
p.Routes = make(map[string]map[uint64]router.Route)
// watch router service routes
p.errChan = make(chan error, 1)
go p.watchRoutes()
return p
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/micro/go-micro/client"
"github.com/micro/go-micro/config/options"
"github.com/micro/go-micro/router"
"github.com/micro/go-micro/server"
)
@@ -29,3 +30,8 @@ func WithEndpoint(e string) options.Option {
func WithClient(c client.Client) options.Option {
return options.WithValue("proxy.client", c)
}
// WithRouter specifies the router to use
func WithRouter(r router.Router) options.Option {
return options.WithValue("proxy.router", r)
}

View File

@@ -80,41 +80,6 @@ func (c *cache) quit() bool {
}
}
// cp copies a service. Because we're caching handing back pointers would
// create a race condition, so we do this instead its fast enough
func (c *cache) cp(current []*registry.Service) []*registry.Service {
var services []*registry.Service
for _, service := range current {
// copy service
s := new(registry.Service)
*s = *service
// copy nodes
var nodes []*registry.Node
for _, node := range service.Nodes {
n := new(registry.Node)
*n = *node
nodes = append(nodes, n)
}
s.Nodes = nodes
// copy endpoints
var eps []*registry.Endpoint
for _, ep := range service.Endpoints {
e := new(registry.Endpoint)
*e = *ep
eps = append(eps, e)
}
s.Endpoints = eps
// append service
services = append(services, s)
}
return services
}
func (c *cache) del(service string) {
delete(c.cache, service)
delete(c.ttls, service)
@@ -132,7 +97,7 @@ func (c *cache) get(service string) ([]*registry.Service, error) {
// got services && within ttl so return cache
if c.isValid(services, ttl) {
// make a copy
cp := c.cp(services)
cp := registry.Copy(services)
// unlock the read
c.RUnlock()
// return servics
@@ -149,7 +114,7 @@ func (c *cache) get(service string) ([]*registry.Service, error) {
// cache results
c.Lock()
c.set(service, c.cp(services))
c.set(service, registry.Copy(services))
c.Unlock()
return services, nil
@@ -360,18 +325,27 @@ func (c *cache) run(service string) {
// watch loops the next event and calls update
// it returns if there's an error
func (c *cache) watch(w registry.Watcher) error {
defer w.Stop()
// used to stop the watch
stop := make(chan bool)
// manage this loop
go func() {
defer w.Stop()
select {
// wait for exit
<-c.exit
w.Stop()
case <-c.exit:
return
// we've been stopped
case <-stop:
return
}
}()
for {
res, err := w.Next()
if err != nil {
close(stop)
return err
}
c.update(res)

View File

@@ -7,6 +7,7 @@ import (
"net"
"net/http"
"runtime"
"strconv"
"sync"
"time"
@@ -16,10 +17,12 @@ import (
)
type consulRegistry struct {
Address string
Client *consul.Client
Address []string
opts registry.Options
client *consul.Client
config *consul.Config
// connect enabled
connect bool
@@ -122,12 +125,12 @@ func configure(c *consulRegistry, opts ...registry.Option) {
config.HttpClient.Timeout = c.opts.Timeout
}
// create the client
client, _ := consul.NewClient(config)
// set address
c.Address = c.opts.Addrs
// set address/client
c.Address = config.Address
c.Client = client
c.config = config
c.Client()
}
func (c *consulRegistry) Init(opts ...registry.Option) error {
@@ -147,7 +150,7 @@ func (c *consulRegistry) Deregister(s *registry.Service) error {
c.Unlock()
node := s.Nodes[0]
return c.Client.Agent().ServiceDeregister(node.Id)
return c.Client().Agent().ServiceDeregister(node.Id)
}
func (c *consulRegistry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
@@ -192,7 +195,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register
if time.Since(lastChecked) <= getDeregisterTTL(regInterval) {
return nil
}
services, _, err := c.Client.Health().Checks(s.Name, c.queryOptions)
services, _, err := c.Client().Health().Checks(s.Name, c.queryOptions)
if err == nil {
for _, v := range services {
if v.ServiceID == node.Id {
@@ -203,7 +206,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register
} else {
// if the err is nil we're all good, bail out
// if not, we don't know what the state is, so full re-register
if err := c.Client.Agent().PassTTL("service:"+node.Id, ""); err == nil {
if err := c.Client().Agent().PassTTL("service:"+node.Id, ""); err == nil {
return nil
}
}
@@ -220,7 +223,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register
deregTTL := getDeregisterTTL(regInterval)
check = &consul.AgentServiceCheck{
TCP: fmt.Sprintf("%s:%d", node.Address, node.Port),
TCP: node.Address,
Interval: fmt.Sprintf("%v", regInterval),
DeregisterCriticalServiceAfter: fmt.Sprintf("%v", deregTTL),
}
@@ -235,13 +238,16 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register
}
}
host, pt, _ := net.SplitHostPort(node.Address)
port, _ := strconv.Atoi(pt)
// register the service
asr := &consul.AgentServiceRegistration{
ID: node.Id,
Name: s.Name,
Tags: tags,
Port: node.Port,
Address: node.Address,
Port: port,
Address: host,
Check: check,
}
@@ -252,7 +258,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register
}
}
if err := c.Client.Agent().ServiceRegister(asr); err != nil {
if err := c.Client().Agent().ServiceRegister(asr); err != nil {
return err
}
@@ -268,7 +274,7 @@ func (c *consulRegistry) Register(s *registry.Service, opts ...registry.Register
}
// pass the healthcheck
return c.Client.Agent().PassTTL("service:"+node.Id, "")
return c.Client().Agent().PassTTL("service:"+node.Id, "")
}
func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) {
@@ -277,9 +283,9 @@ func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) {
// if we're connect enabled only get connect services
if c.connect {
rsp, _, err = c.Client.Health().Connect(name, "", false, c.queryOptions)
rsp, _, err = c.Client().Health().Connect(name, "", false, c.queryOptions)
} else {
rsp, _, err = c.Client.Health().Service(name, "", false, c.queryOptions)
rsp, _, err = c.Client().Health().Service(name, "", false, c.queryOptions)
}
if err != nil {
return nil, err
@@ -334,8 +340,7 @@ func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) {
svc.Nodes = append(svc.Nodes, &registry.Node{
Id: id,
Address: address,
Port: s.Service.Port,
Address: fmt.Sprintf("%s:%d", address, s.Service.Port),
Metadata: decodeMetadata(s.Service.Tags),
})
}
@@ -348,7 +353,7 @@ func (c *consulRegistry) GetService(name string) ([]*registry.Service, error) {
}
func (c *consulRegistry) ListServices() ([]*registry.Service, error) {
rsp, _, err := c.Client.Catalog().Services(c.queryOptions)
rsp, _, err := c.Client().Catalog().Services(c.queryOptions)
if err != nil {
return nil, err
}
@@ -374,6 +379,28 @@ func (c *consulRegistry) Options() registry.Options {
return c.opts
}
func (c *consulRegistry) Client() *consul.Client {
if c.client != nil {
return c.client
}
if len(c.Address) == 0 {
tmp, _ := consul.NewClient(c.config)
return tmp
}
c.config.Address = c.Address[0]
tmpClint, _ := consul.NewClient(c.config)
_, err := tmpClint.Agent().Host()
if err != nil {
c.Address = c.Address[1:]
return c.Client()
}
c.client = tmpClint
return c.client
}
func NewRegistry(opts ...registry.Option) registry.Registry {
cr := &consulRegistry{
opts: registry.Options{},

View File

@@ -50,22 +50,24 @@ func newConsulTestRegistry(r *mockRegistry) (*consulRegistry, func()) {
}
cfg := consul.DefaultConfig()
cfg.Address = l.Addr().String()
cl, _ := consul.NewClient(cfg)
go newMockServer(r, l)
return &consulRegistry{
Address: cfg.Address,
Client: cl,
opts: registry.Options{},
register: make(map[string]uint64),
lastChecked: make(map[string]time.Time),
queryOptions: &consul.QueryOptions{
AllowStale: true,
},
}, func() {
l.Close()
}
var cr = &consulRegistry{
config: cfg,
Address: []string{cfg.Address},
opts: registry.Options{},
register: make(map[string]uint64),
lastChecked: make(map[string]time.Time),
queryOptions: &consul.QueryOptions{
AllowStale: true,
},
}
cr.Client()
return cr, func() {
l.Close()
}
}
func newServiceList(svc []*consul.ServiceEntry) []byte {

View File

@@ -1,6 +1,7 @@
package consul
import (
"fmt"
"log"
"os"
"sync"
@@ -44,7 +45,7 @@ func newConsulWatcher(cr *consulRegistry, opts ...registry.WatchOption) (registr
}
wp.Handler = cw.handle
go wp.RunWithClientAndLogger(cr.Client, log.New(os.Stderr, "", log.LstdFlags))
go wp.RunWithClientAndLogger(cr.Client(), log.New(os.Stderr, "", log.LstdFlags))
cw.wp = wp
return cw, nil
@@ -102,8 +103,7 @@ func (cw *consulWatcher) serviceHandler(idx uint64, data interface{}) {
svc.Nodes = append(svc.Nodes, &registry.Node{
Id: id,
Address: address,
Port: e.Service.Port,
Address: fmt.Sprintf("%s:%d", address, e.Service.Port),
Metadata: decodeMetadata(e.Service.Tags),
})
}
@@ -209,7 +209,7 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
})
if err == nil {
wp.Handler = cw.serviceHandler
go wp.RunWithClientAndLogger(cw.r.Client, log.New(os.Stderr, "", log.LstdFlags))
go wp.RunWithClientAndLogger(cw.r.Client(), log.New(os.Stderr, "", log.LstdFlags))
cw.watchers[service] = wp
cw.next <- &registry.Result{Action: "create", Service: &registry.Service{Name: service}}
}
@@ -224,9 +224,14 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
cw.RUnlock()
// remove unknown services from registry
// save the things we want to delete
deleted := make(map[string][]*registry.Service)
for service, _ := range rservices {
if _, ok := services[service]; !ok {
cw.Lock()
// save this before deleting
deleted[service] = cw.services[service]
delete(cw.services, service)
cw.Unlock()
}
@@ -237,6 +242,11 @@ func (cw *consulWatcher) handle(idx uint64, data interface{}) {
if _, ok := services[service]; !ok {
w.Stop()
delete(cw.watchers, service)
for _, oldService := range deleted[service] {
// send a delete for the service nodes that we're removing
cw.next <- &registry.Result{Action: "delete", Service: oldService}
}
// sent the empty list as the last resort to indicate to delete the entire service
cw.next <- &registry.Result{Action: "delete", Service: &registry.Service{Name: service}}
}
}

View File

@@ -626,7 +626,7 @@ func (g *gossipRegistry) run() {
g.services[u.Service.Name] = []*registry.Service{u.Service}
} else {
g.services[u.Service.Name] = addServices(service, []*registry.Service{u.Service})
g.services[u.Service.Name] = registry.Merge(service, []*registry.Service{u.Service})
}
g.Unlock()
@@ -645,7 +645,7 @@ func (g *gossipRegistry) run() {
case actionTypeDelete:
g.Lock()
if service, ok := g.services[u.Service.Name]; ok {
if services := delServices(service, []*registry.Service{u.Service}); len(services) == 0 {
if services := registry.Remove(service, []*registry.Service{u.Service}); len(services) == 0 {
delete(g.services, u.Service.Name)
} else {
g.services[u.Service.Name] = services
@@ -706,7 +706,7 @@ func (g *gossipRegistry) Register(s *registry.Service, opts ...registry.Register
if service, ok := g.services[s.Name]; !ok {
g.services[s.Name] = []*registry.Service{s}
} else {
g.services[s.Name] = addServices(service, []*registry.Service{s})
g.services[s.Name] = registry.Merge(service, []*registry.Service{s})
}
g.Unlock()
@@ -754,7 +754,7 @@ func (g *gossipRegistry) Deregister(s *registry.Service) error {
g.Lock()
if service, ok := g.services[s.Name]; ok {
if services := delServices(service, []*registry.Service{s}); len(services) == 0 {
if services := registry.Remove(service, []*registry.Service{s}); len(services) == 0 {
delete(g.services, s.Name)
} else {
g.services[s.Name] = services

View File

@@ -1,134 +0,0 @@
package gossip
import (
"github.com/micro/go-micro/registry"
)
func cp(current []*registry.Service) []*registry.Service {
var services []*registry.Service
for _, service := range current {
// copy service
s := new(registry.Service)
*s = *service
// copy nodes
var nodes []*registry.Node
for _, node := range service.Nodes {
n := new(registry.Node)
*n = *node
nodes = append(nodes, n)
}
s.Nodes = nodes
// copy endpoints
var eps []*registry.Endpoint
for _, ep := range service.Endpoints {
e := new(registry.Endpoint)
*e = *ep
eps = append(eps, e)
}
s.Endpoints = eps
// append service
services = append(services, s)
}
return services
}
func addNodes(old, neu []*registry.Node) []*registry.Node {
var nodes []*registry.Node
// add all new nodes
for _, n := range neu {
node := *n
nodes = append(nodes, &node)
}
// look at old nodes
for _, o := range old {
var exists bool
// check against new nodes
for _, n := range nodes {
// ids match then skip
if o.Id == n.Id {
exists = true
break
}
}
// keep old node
if !exists {
node := *o
nodes = append(nodes, &node)
}
}
return nodes
}
func addServices(old, neu []*registry.Service) []*registry.Service {
var srv []*registry.Service
for _, s := range neu {
var seen bool
for _, o := range old {
if o.Version == s.Version {
sp := new(registry.Service)
// make copy
*sp = *o
// set nodes
sp.Nodes = addNodes(o.Nodes, s.Nodes)
// mark as seen
seen = true
srv = append(srv, sp)
break
}
}
if !seen {
srv = append(srv, cp([]*registry.Service{s})...)
}
}
return srv
}
func delNodes(old, del []*registry.Node) []*registry.Node {
var nodes []*registry.Node
for _, o := range old {
var rem bool
for _, n := range del {
if o.Id == n.Id {
rem = true
break
}
}
if !rem {
nodes = append(nodes, o)
}
}
return nodes
}
func delServices(old, del []*registry.Service) []*registry.Service {
var services []*registry.Service
for _, o := range old {
var rem bool
for _, s := range del {
if o.Version == s.Version {
s.Nodes = delNodes(s.Nodes, o.Nodes)
if len(s.Nodes) == 0 {
rem = true
break
}
}
}
if !rem {
services = append(services, o)
}
}
return services
}

View File

@@ -3,7 +3,9 @@ package registry
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
@@ -127,14 +129,22 @@ func (m *mdnsRegistry) Register(service *Service, opts ...RegisterOption) error
continue
}
//
host, pt, err := net.SplitHostPort(node.Address)
if err != nil {
gerr = err
continue
}
port, _ := strconv.Atoi(pt)
// we got here, new node
s, err := mdns.NewMDNSService(
node.Id,
service.Name,
"",
"",
node.Port,
[]net.IP{net.ParseIP(node.Address)},
port,
[]net.IP{net.ParseIP(host)},
txt,
)
if err != nil {
@@ -238,8 +248,7 @@ func (m *mdnsRegistry) GetService(service string) ([]*Service, error) {
s.Nodes = append(s.Nodes, &Node{
Id: strings.TrimSuffix(e.Name, "."+p.Service+"."+p.Domain+"."),
Address: e.AddrV4.String(),
Port: e.Port,
Address: fmt.Sprintf("%s:%d", e.AddrV4.String(), e.Port),
Metadata: txt.Metadata,
})

View File

@@ -13,8 +13,7 @@ func TestMDNS(t *testing.T) {
Nodes: []*Node{
&Node{
Id: "test1-1",
Address: "10.0.0.1",
Port: 10001,
Address: "10.0.0.1:10001",
Metadata: map[string]string{
"foo": "bar",
},
@@ -27,8 +26,7 @@ func TestMDNS(t *testing.T) {
Nodes: []*Node{
&Node{
Id: "test2-1",
Address: "10.0.0.2",
Port: 10002,
Address: "10.0.0.2:10002",
Metadata: map[string]string{
"foo2": "bar2",
},
@@ -41,8 +39,7 @@ func TestMDNS(t *testing.T) {
Nodes: []*Node{
&Node{
Id: "test3-1",
Address: "10.0.0.3",
Port: 10003,
Address: "10.0.0.3:10003",
Metadata: map[string]string{
"foo3": "bar3",
},
@@ -92,10 +89,6 @@ func TestMDNS(t *testing.T) {
if node.Address != service.Nodes[0].Address {
t.Fatalf("Expected node address %s got %s", service.Nodes[0].Address, node.Address)
}
if node.Port != service.Nodes[0].Port {
t.Fatalf("Expected node port %d got %d", service.Nodes[0].Port, node.Port)
}
}
services, err := r.ListServices()

View File

@@ -1,7 +1,7 @@
package registry
import (
"errors"
"fmt"
"strings"
"github.com/micro/mdns"
@@ -53,8 +53,7 @@ func (m *mdnsWatcher) Next() (*Result, error) {
service.Nodes = append(service.Nodes, &Node{
Id: strings.TrimSuffix(e.Name, "."+service.Name+".local."),
Address: e.AddrV4.String(),
Port: e.Port,
Address: fmt.Sprintf("%s:%d", e.AddrV4.String(), e.Port),
Metadata: txt.Metadata,
})
@@ -63,7 +62,7 @@ func (m *mdnsWatcher) Next() (*Result, error) {
Service: service,
}, nil
case <-m.exit:
return nil, errors.New("watcher stopped")
return nil, ErrWatcherStopped
}
}
}

View File

@@ -1,76 +0,0 @@
package memory
import (
"github.com/micro/go-micro/registry"
)
func addNodes(old, neu []*registry.Node) []*registry.Node {
for _, n := range neu {
var seen bool
for i, o := range old {
if o.Id == n.Id {
seen = true
old[i] = n
break
}
}
if !seen {
old = append(old, n)
}
}
return old
}
func addServices(old, neu []*registry.Service) []*registry.Service {
for _, s := range neu {
var seen bool
for i, o := range old {
if o.Version == s.Version {
s.Nodes = addNodes(o.Nodes, s.Nodes)
seen = true
old[i] = s
break
}
}
if !seen {
old = append(old, s)
}
}
return old
}
func delNodes(old, del []*registry.Node) []*registry.Node {
var nodes []*registry.Node
for _, o := range old {
var rem bool
for _, n := range del {
if o.Id == n.Id {
rem = true
break
}
}
if !rem {
nodes = append(nodes, o)
}
}
return nodes
}
func delServices(old, del []*registry.Service) []*registry.Service {
var services []*registry.Service
for i, o := range old {
var rem bool
for _, s := range del {
if o.Version == s.Version {
old[i].Nodes = delNodes(o.Nodes, s.Nodes)
if len(old[i].Nodes) == 0 {
rem = true
}
}
}
if !rem {
services = append(services, o)
}
}
return services
}

View File

@@ -1,78 +0,0 @@
package memory
import (
"testing"
"github.com/micro/go-micro/registry"
)
func TestDelServices(t *testing.T) {
services := []*registry.Service{
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
{
Id: "foo-123",
Address: "localhost",
Port: 9999,
},
},
},
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
{
Id: "foo-123",
Address: "localhost",
Port: 6666,
},
},
},
}
servs := delServices([]*registry.Service{services[0]}, []*registry.Service{services[1]})
if i := len(servs); i > 0 {
t.Errorf("Expected 0 nodes, got %d: %+v", i, servs)
}
t.Logf("Services %+v", servs)
}
func TestDelNodes(t *testing.T) {
services := []*registry.Service{
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
{
Id: "foo-123",
Address: "localhost",
Port: 9999,
},
{
Id: "foo-321",
Address: "localhost",
Port: 6666,
},
},
},
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
{
Id: "foo-123",
Address: "localhost",
Port: 6666,
},
},
},
}
nodes := delNodes(services[0].Nodes, services[1].Nodes)
if i := len(nodes); i != 1 {
t.Errorf("Expected only 1 node, got %d: %+v", i, nodes)
}
t.Logf("Nodes %+v", nodes)
}

View File

@@ -22,17 +22,6 @@ var (
timeout = time.Millisecond * 10
)
/*
// Setup sets mock data
func (m *Registry) Setup() {
m.Lock()
defer m.Unlock()
// add some memory data
m.Services = Data
}
*/
func (m *Registry) watch(r *registry.Result) {
var watchers []*Watcher
@@ -66,7 +55,7 @@ func (m *Registry) Init(opts ...registry.Option) error {
m.Lock()
for k, v := range getServices(m.options.Context) {
s := m.Services[k]
m.Services[k] = addServices(s, v)
m.Services[k] = registry.Merge(s, v)
}
m.Unlock()
return nil
@@ -76,20 +65,20 @@ func (m *Registry) Options() registry.Options {
return m.options
}
func (m *Registry) GetService(service string) ([]*registry.Service, error) {
func (m *Registry) GetService(name string) ([]*registry.Service, error) {
m.RLock()
s, ok := m.Services[service]
if !ok || len(s) == 0 {
m.RUnlock()
service, ok := m.Services[name]
m.RUnlock()
if !ok {
return nil, registry.ErrNotFound
}
m.RUnlock()
return s, nil
return service, nil
}
func (m *Registry) ListServices() ([]*registry.Service, error) {
m.RLock()
var services []*registry.Service
m.RLock()
for _, service := range m.Services {
services = append(services, service...)
}
@@ -99,11 +88,14 @@ func (m *Registry) ListServices() ([]*registry.Service, error) {
func (m *Registry) Register(s *registry.Service, opts ...registry.RegisterOption) error {
go m.watch(&registry.Result{Action: "update", Service: s})
m.Lock()
services := addServices(m.Services[s.Name], []*registry.Service{s})
m.Services[s.Name] = services
if service, ok := m.Services[s.Name]; !ok {
m.Services[s.Name] = []*registry.Service{s}
} else {
m.Services[s.Name] = registry.Merge(service, []*registry.Service{s})
}
m.Unlock()
return nil
}
@@ -111,9 +103,15 @@ func (m *Registry) Deregister(s *registry.Service) error {
go m.watch(&registry.Result{Action: "delete", Service: s})
m.Lock()
services := delServices(m.Services[s.Name], []*registry.Service{s})
m.Services[s.Name] = services
if service, ok := m.Services[s.Name]; ok {
if service := registry.Remove(service, []*registry.Service{s}); len(service) == 0 {
delete(m.Services, s.Name)
} else {
m.Services[s.Name] = service
}
}
m.Unlock()
return nil
}

View File

@@ -15,13 +15,11 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.0-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
@@ -31,8 +29,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.1-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
@@ -42,8 +39,7 @@ var (
Nodes: []*registry.Node{
{
Id: "foo-1.0.3-345",
Address: "localhost",
Port: 8888,
Address: "localhost:8888",
},
},
},
@@ -55,13 +51,11 @@ var (
Nodes: []*registry.Node{
{
Id: "bar-1.0.0-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "bar-1.0.0-321",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
@@ -71,8 +65,7 @@ var (
Nodes: []*registry.Node{
{
Id: "bar-1.0.1-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},

View File

@@ -11,7 +11,6 @@ type Service struct {
type Node struct {
Id string `json:"id"`
Address string `json:"address"`
Port int `json:"port"`
Metadata map[string]string `json:"metadata"`
}

142
registry/util.go Normal file
View File

@@ -0,0 +1,142 @@
package registry
func addNodes(old, neu []*Node) []*Node {
nodes := make([]*Node, len(neu))
// add all new nodes
for i, n := range neu {
node := *n
nodes[i] = &node
}
// look at old nodes
for _, o := range old {
var exists bool
// check against new nodes
for _, n := range nodes {
// ids match then skip
if o.Id == n.Id {
exists = true
break
}
}
// keep old node
if !exists {
node := *o
nodes = append(nodes, &node)
}
}
return nodes
}
func delNodes(old, del []*Node) []*Node {
var nodes []*Node
for _, o := range old {
var rem bool
for _, n := range del {
if o.Id == n.Id {
rem = true
break
}
}
if !rem {
nodes = append(nodes, o)
}
}
return nodes
}
// Copy makes a copy of services
func Copy(current []*Service) []*Service {
services := make([]*Service, len(current))
for i, service := range current {
// copy service
s := new(Service)
*s = *service
// copy nodes
nodes := make([]*Node, len(service.Nodes))
for j, node := range service.Nodes {
n := new(Node)
*n = *node
nodes[j] = n
}
s.Nodes = nodes
// copy endpoints
eps := make([]*Endpoint, len(service.Endpoints))
for j, ep := range service.Endpoints {
e := new(Endpoint)
*e = *ep
eps[j] = e
}
s.Endpoints = eps
// append service
services[i] = s
}
return services
}
// Merge merges two lists of services and returns a new copy
func Merge(olist []*Service, nlist []*Service) []*Service {
var srv []*Service
for _, n := range nlist {
var seen bool
for _, o := range olist {
if o.Version == n.Version {
sp := new(Service)
// make copy
*sp = *o
// set nodes
sp.Nodes = addNodes(o.Nodes, n.Nodes)
// mark as seen
seen = true
srv = append(srv, sp)
break
} else {
sp := new(Service)
// make copy
*sp = *o
srv = append(srv, sp)
}
}
if !seen {
srv = append(srv, Copy([]*Service{n})...)
}
}
return srv
}
// Remove removes services and returns a new copy
func Remove(old, del []*Service) []*Service {
var services []*Service
for _, o := range old {
srv := new(Service)
*srv = *o
var rem bool
for _, s := range del {
if srv.Version == s.Version {
srv.Nodes = delNodes(srv.Nodes, s.Nodes)
if len(srv.Nodes) == 0 {
rem = true
}
}
}
if !rem {
services = append(services, srv)
}
}
return services
}

View File

@@ -1,70 +1,63 @@
package gossip
package registry
import (
"testing"
"github.com/micro/go-micro/registry"
)
func TestDelServices(t *testing.T) {
services := []*registry.Service{
func TestRemove(t *testing.T) {
services := []*Service{
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
Nodes: []*Node{
{
Id: "foo-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
},
},
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
Nodes: []*Node{
{
Id: "foo-123",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
}
servs := delServices([]*registry.Service{services[0]}, []*registry.Service{services[1]})
servs := Remove([]*Service{services[0]}, []*Service{services[1]})
if i := len(servs); i > 0 {
t.Errorf("Expected 0 nodes, got %d: %+v", i, servs)
}
t.Logf("Services %+v", servs)
}
func TestDelNodes(t *testing.T) {
services := []*registry.Service{
func TestRemoveNodes(t *testing.T) {
services := []*Service{
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
Nodes: []*Node{
{
Id: "foo-123",
Address: "localhost",
Port: 9999,
Address: "localhost:9999",
},
{
Id: "foo-321",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},
{
Name: "foo",
Version: "1.0.0",
Nodes: []*registry.Node{
Nodes: []*Node{
{
Id: "foo-123",
Address: "localhost",
Port: 6666,
Address: "localhost:6666",
},
},
},

View File

@@ -12,8 +12,7 @@ func TestWatcher(t *testing.T) {
Nodes: []*Node{
&Node{
Id: "test1-1",
Address: "10.0.0.1",
Port: 10001,
Address: "10.0.0.1:10001",
Metadata: map[string]string{
"foo": "bar",
},
@@ -26,8 +25,7 @@ func TestWatcher(t *testing.T) {
Nodes: []*Node{
&Node{
Id: "test2-1",
Address: "10.0.0.2",
Port: 10002,
Address: "10.0.0.2:10002",
Metadata: map[string]string{
"foo2": "bar2",
},
@@ -40,8 +38,7 @@ func TestWatcher(t *testing.T) {
Nodes: []*Node{
&Node{
Id: "test3-1",
Address: "10.0.0.3",
Port: 10003,
Address: "10.0.0.3:10003",
Metadata: map[string]string{
"foo3": "bar3",
},
@@ -77,10 +74,6 @@ func TestWatcher(t *testing.T) {
if node.Address != service.Nodes[0].Address {
t.Fatalf("Expected node address %s got %s", service.Nodes[0].Address, node.Address)
}
if node.Port != service.Nodes[0].Port {
t.Fatalf("Expected node port %d got %d", service.Nodes[0].Port, node.Port)
}
}
// new registry

713
router/default.go Normal file
View File

@@ -0,0 +1,713 @@
package router
import (
"fmt"
"math"
"sort"
"strings"
"sync"
"time"
"github.com/google/uuid"
"github.com/micro/go-micro/registry"
)
const (
// AdvertiseEventsTick is time interval in which the router advertises route updates
AdvertiseEventsTick = 5 * time.Second
// AdvertiseTableTick is time interval in which router advertises all routes found in routing table
AdvertiseTableTick = 1 * time.Minute
// AdvertiseFlushTick is time the yet unconsumed advertisements are flush i.e. discarded
AdvertiseFlushTick = 15 * time.Second
// AdvertSuppress is advert suppression threshold
AdvertSuppress = 2000.0
// AdvertRecover is advert recovery threshold
AdvertRecover = 750.0
// DefaultAdvertTTL is default advertisement TTL
DefaultAdvertTTL = 1 * time.Minute
// DeletePenalty penalises route deletion
DeletePenalty = 1000.0
// UpdatePenalty penalises route updates
UpdatePenalty = 500.0
// PenaltyHalfLife is the time the advert penalty decays to half its value
PenaltyHalfLife = 2.0
// MaxSuppressTime defines time after which the suppressed advert is deleted
MaxSuppressTime = 5 * time.Minute
)
var (
// PenaltyDecay is a coefficient which controls the speed the advert penalty decays
PenaltyDecay = math.Log(2) / PenaltyHalfLife
)
// router implements default router
type router struct {
sync.RWMutex
// embed the table
table *table
opts Options
status Status
exit chan struct{}
errChan chan error
eventChan chan *Event
advertWg *sync.WaitGroup
wg *sync.WaitGroup
// advert subscribers
subscribers map[string]chan *Advert
}
// newRouter creates new router and returns it
func newRouter(opts ...Option) Router {
// get default options
options := DefaultOptions()
// apply requested options
for _, o := range opts {
o(&options)
}
r := &router{
table: newTable(),
opts: options,
status: Status{Code: Stopped, Error: nil},
advertWg: &sync.WaitGroup{},
wg: &sync.WaitGroup{},
subscribers: make(map[string]chan *Advert),
}
go r.run()
return r
}
// Init initializes router with given options
func (r *router) Init(opts ...Option) error {
for _, o := range opts {
o(&r.opts)
}
return nil
}
// Options returns router options
func (r *router) Options() Options {
return r.opts
}
func (r *router) Table() Table {
return r.table
}
// manageRoute applies action on a given route
func (r *router) manageRoute(route Route, action string) error {
switch action {
case "create":
if err := r.table.Create(route); err != nil && err != ErrDuplicateRoute {
return fmt.Errorf("failed adding route for service %s: %s", route.Service, err)
}
case "update":
if err := r.table.Update(route); err != nil && err != ErrDuplicateRoute {
return fmt.Errorf("failed updating route for service %s: %s", route.Service, err)
}
case "delete":
if err := r.table.Delete(route); err != nil && err != ErrRouteNotFound {
return fmt.Errorf("failed deleting route for service %s: %s", route.Service, err)
}
default:
return fmt.Errorf("failed to manage route for service %s. Unknown action: %s", route.Service, action)
}
return nil
}
// manageServiceRoutes applies action to all routes of the service.
// It returns error of the action fails with error.
func (r *router) manageServiceRoutes(service *registry.Service, action string) error {
// action is the routing table action
action = strings.ToLower(action)
// take route action on each service node
for _, node := range service.Nodes {
route := Route{
Service: service.Name,
Address: node.Address,
Gateway: "",
Network: r.opts.Network,
Link: DefaultLink,
Metric: DefaultLocalMetric,
}
if err := r.manageRoute(route, action); err != nil {
return err
}
}
return nil
}
// manageRegistryRoutes applies action to all routes of each service found in the registry.
// It returns error if either the services failed to be listed or the routing table action fails.
func (r *router) manageRegistryRoutes(reg registry.Registry, action string) error {
services, err := reg.ListServices()
if err != nil {
return fmt.Errorf("failed listing services: %v", err)
}
// add each service node as a separate route
for _, service := range services {
// get the service to retrieve all its info
srvs, err := reg.GetService(service.Name)
if err != nil {
continue
}
// manage the routes for all returned services
for _, srv := range srvs {
if err := r.manageServiceRoutes(srv, action); err != nil {
return err
}
}
}
return nil
}
// watchRegistry watches registry and updates routing table based on the received events.
// It returns error if either the registry watcher fails with error or if the routing table update fails.
func (r *router) watchRegistry(w registry.Watcher) error {
// wait in the background for the router to stop
// when the router stops, stop the watcher and exit
r.wg.Add(1)
exit := make(chan bool)
defer func() {
// close the exit channel when the go routine finishes
close(exit)
r.wg.Done()
}()
go func() {
defer w.Stop()
select {
case <-r.exit:
return
case <-exit:
return
}
}()
var watchErr error
for {
res, err := w.Next()
if err != nil {
if err != registry.ErrWatcherStopped {
watchErr = err
}
break
}
if err := r.manageServiceRoutes(res.Service, res.Action); err != nil {
return err
}
}
return watchErr
}
// watchTable watches routing table entries and either adds or deletes locally registered service to/from network registry
// It returns error if the locally registered services either fails to be added/deleted to/from network registry.
func (r *router) watchTable(w Watcher) error {
// wait in the background for the router to stop
// when the router stops, stop the watcher and exit
r.wg.Add(1)
exit := make(chan bool)
defer func() {
// close the exit channel when the go routine finishes
close(exit)
r.wg.Done()
}()
go func() {
defer w.Stop()
select {
case <-r.exit:
return
case <-exit:
return
}
}()
var watchErr error
for {
event, err := w.Next()
if err != nil {
if err != ErrWatcherStopped {
watchErr = err
}
break
}
select {
case <-r.exit:
close(r.eventChan)
return nil
case r.eventChan <- event:
}
}
// close event channel on error
close(r.eventChan)
return watchErr
}
// publishAdvert publishes router advert to advert channel
// NOTE: this might cease to be a dedicated method in the future
func (r *router) publishAdvert(advType AdvertType, events []*Event) {
defer r.advertWg.Done()
a := &Advert{
Id: r.opts.Id,
Type: advType,
TTL: DefaultAdvertTTL,
Timestamp: time.Now(),
Events: events,
}
r.RLock()
for _, sub := range r.subscribers {
// check the exit chan first
select {
case <-r.exit:
r.RUnlock()
return
default:
}
// now send the message
select {
case sub <- a:
default:
}
}
r.RUnlock()
}
// advertiseTable advertises the whole routing table to the network
func (r *router) advertiseTable() error {
// create table advertisement ticker
ticker := time.NewTicker(AdvertiseTableTick)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// list routing table routes to announce
routes, err := r.table.List()
if err != nil {
return 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: Update,
Timestamp: time.Now(),
Route: route,
}
events[i] = event
}
// advertise all routes as Update events to subscribers
if len(events) > 0 {
r.advertWg.Add(1)
go r.publishAdvert(RouteUpdate, events)
}
case <-r.exit:
return nil
}
}
}
// routeAdvert contains a list of route events to be advertised
type routeAdvert struct {
events []*Event
// lastUpdate records the time of the last advert update
lastUpdate time.Time
// penalty is current advert penalty
penalty float64
// isSuppressed flags the advert suppression
isSuppressed bool
// suppressTime records the time interval the advert has been suppressed for
suppressTime time.Time
}
// advertiseEvents advertises routing table events
// It suppresses unhealthy flapping events and advertises healthy events upstream.
func (r *router) advertiseEvents() error {
// ticker to periodically scan event for advertising
ticker := time.NewTicker(AdvertiseEventsTick)
defer ticker.Stop()
// advertMap is a map of advert events
advertMap := make(map[uint64]*routeAdvert)
// routing table watcher
tableWatcher, err := r.Watch()
if err != nil {
return fmt.Errorf("failed creating routing table watcher: %v", err)
}
r.wg.Add(1)
go func() {
defer r.wg.Done()
select {
case r.errChan <- r.watchTable(tableWatcher):
case <-r.exit:
}
}()
for {
select {
case <-ticker.C:
var events []*Event
// collect all events which are not flapping
for key, advert := range advertMap {
// decay the event penalty
delta := time.Since(advert.lastUpdate).Seconds()
advert.penalty = advert.penalty * math.Exp(-delta*PenaltyDecay)
// suppress/recover the event based on its penalty level
switch {
case advert.penalty > AdvertSuppress && !advert.isSuppressed:
advert.isSuppressed = true
advert.suppressTime = time.Now()
case advert.penalty < AdvertRecover && advert.isSuppressed:
advert.isSuppressed = false
}
// max suppression time threshold has been reached, delete the advert
if advert.isSuppressed {
if time.Since(advert.suppressTime) > MaxSuppressTime {
delete(advertMap, key)
continue
}
}
if !advert.isSuppressed {
for _, event := range advert.events {
e := new(Event)
*e = *event
events = append(events, e)
// delete the advert from the advertMap
delete(advertMap, key)
}
}
}
// advertise all Update events to subscribers
if len(events) > 0 {
r.advertWg.Add(1)
go r.publishAdvert(RouteUpdate, events)
}
case e := <-r.eventChan:
// if event is nil, continue
if e == nil {
continue
}
// determine the event penalty
var penalty float64
switch e.Type {
case Update:
penalty = UpdatePenalty
case Delete:
penalty = DeletePenalty
}
// check if we have already registered the route
// we use the route hash as advertMap key
hash := e.Route.Hash()
advert, ok := advertMap[hash]
if !ok {
events := []*Event{e}
advert = &routeAdvert{
events: events,
penalty: penalty,
lastUpdate: time.Now(),
}
advertMap[hash] = advert
continue
}
// attempt to squash last two events if possible
lastEvent := advert.events[len(advert.events)-1]
if lastEvent.Type == e.Type {
advert.events[len(advert.events)-1] = e
} else {
advert.events = append(advert.events, e)
}
// update event penalty and recorded timestamp
advert.lastUpdate = time.Now()
advert.penalty += penalty
case <-r.exit:
// first wait for the advertiser to finish
r.advertWg.Wait()
return nil
}
}
}
// watchErrors watches router errors and takes appropriate actions
func (r *router) watchErrors() {
var err error
select {
case <-r.exit:
case err = <-r.errChan:
}
r.Lock()
defer r.Unlock()
if r.status.Code != Stopped {
// notify all goroutines to finish
close(r.exit)
// drain the advertise channel only if advertising
if r.status.Code == Advertising {
// drain the event channel
for range r.eventChan {
}
}
// mark the router as Stopped and set its Error to nil
r.status = Status{Code: Stopped, Error: nil}
}
if err != nil {
r.status = Status{Code: Error, Error: err}
}
}
// Run runs the router.
func (r *router) run() {
r.Lock()
defer r.Unlock()
switch r.status.Code {
case Stopped, Error:
// add all local service routes into the routing table
if err := r.manageRegistryRoutes(r.opts.Registry, "create"); err != nil {
r.status = Status{Code: Error, Error: fmt.Errorf("failed adding registry routes: %s", err)}
return
}
// add default gateway into routing table
if r.opts.Gateway != "" {
// note, the only non-default value is the gateway
route := Route{
Service: "*",
Address: "*",
Gateway: r.opts.Gateway,
Network: "*",
Metric: DefaultLocalMetric,
}
if err := r.table.Create(route); err != nil {
r.status = Status{Code: Error, Error: fmt.Errorf("failed adding default gateway route: %s", err)}
return
}
}
// create error and exit channels
r.errChan = make(chan error, 1)
r.exit = make(chan struct{})
// registry watcher
regWatcher, err := r.opts.Registry.Watch()
if err != nil {
r.status = Status{Code: Error, Error: fmt.Errorf("failed creating registry watcher: %v", err)}
return
}
r.wg.Add(1)
go func() {
defer r.wg.Done()
select {
case r.errChan <- r.watchRegistry(regWatcher):
case <-r.exit:
}
}()
// watch for errors and cleanup
r.wg.Add(1)
go func() {
defer r.wg.Done()
r.watchErrors()
}()
// mark router as Running and set its Error to nil
r.status = Status{Code: Running, Error: nil}
return
}
return
}
// Advertise stars advertising the routes to the network and returns the advertisements channel to consume from.
// If the router is already advertising it returns the channel to consume from.
// It returns error if either the router is not running or if the routing table fails to list the routes to advertise.
func (r *router) Advertise() (<-chan *Advert, error) {
r.Lock()
defer r.Unlock()
switch r.status.Code {
case Advertising:
advertChan := make(chan *Advert)
r.subscribers[uuid.New().String()] = advertChan
return advertChan, nil
case Running:
// list routing table routes to announce
routes, err := r.table.List()
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
}
// create event channels
r.eventChan = make(chan *Event)
// advertise your presence
r.advertWg.Add(1)
go r.publishAdvert(Announce, events)
r.wg.Add(1)
go func() {
defer r.wg.Done()
select {
case r.errChan <- r.advertiseEvents():
case <-r.exit:
}
}()
r.advertWg.Add(1)
go func() {
defer r.advertWg.Done()
// advertise the whole routing table
select {
case r.errChan <- r.advertiseTable():
case <-r.exit:
}
}()
// mark router as Running and set its Error to nil
r.status = Status{Code: Advertising, Error: nil}
// create advert channel
advertChan := make(chan *Advert)
r.subscribers[uuid.New().String()] = advertChan
return advertChan, nil
case Stopped:
return nil, fmt.Errorf("not running")
}
return nil, fmt.Errorf("error: %s", r.status.Error)
}
// Process updates the routing table using the advertised values
func (r *router) Process(a *Advert) error {
// NOTE: event sorting might not be necessary
// copy update events intp new slices
events := make([]*Event, len(a.Events))
copy(events, a.Events)
// sort events by timestamp
sort.Slice(events, func(i, j int) bool {
return events[i].Timestamp.Before(events[j].Timestamp)
})
for _, event := range events {
// create a copy of the route
route := event.Route
action := event.Type
if err := r.manageRoute(route, fmt.Sprintf("%s", action)); err != nil {
return fmt.Errorf("failed applying action %s to routing table: %s", action, err)
}
}
return nil
}
func (r *router) Lookup(q Query) ([]Route, error) {
return r.table.Query(q)
}
func (r *router) Watch(opts ...WatchOption) (Watcher, error) {
return r.table.Watch(opts...)
}
// Status returns router status
func (r *router) Status() Status {
r.RLock()
defer r.RUnlock()
// make a copy of the status
status := r.status
return status
}
// Stop stops the router
func (r *router) Stop() error {
r.Lock()
// only close the channel if the router is running and/or advertising
if r.status.Code == Running || r.status.Code == Advertising {
// notify all goroutines to finish
close(r.exit)
// drain the advertise channel only if advertising
if r.status.Code == Advertising {
// drain the event channel
for range r.eventChan {
}
}
// close advert subscribers
for id, sub := range r.subscribers {
// close the channel
close(sub)
// delete the subscriber
delete(r.subscribers, id)
}
// mark the router as Stopped and set its Error to nil
r.status = Status{Code: Stopped, Error: nil}
}
r.Unlock()
// wait for all goroutines to finish
r.wg.Wait()
return nil
}
// String prints debugging information about router
func (r *router) String() string {
return "default"
}

View File

@@ -1,323 +0,0 @@
package router
import (
"fmt"
"net"
"strconv"
"strings"
"sync"
"time"
"github.com/micro/go-micro/registry"
"github.com/olekukonko/tablewriter"
)
var (
// AdvertiseTick defines how often in seconds do we scal the local registry
// to advertise the local services to the network registry
AdvertiseTick = 5 * time.Second
// AdvertiseTTL defines network registry TTL in seconds
// NOTE: this is a rather arbitrary picked value subject to change
AdvertiseTTL = 120 * time.Second
)
type router struct {
opts Options
exit chan struct{}
wg *sync.WaitGroup
}
// newRouter creates new router and returns it
func newRouter(opts ...Option) Router {
// get default options
options := DefaultOptions()
// apply requested options
for _, o := range opts {
o(&options)
}
return &router{
opts: options,
exit: make(chan struct{}),
wg: &sync.WaitGroup{},
}
}
// Init initializes router with given options
func (r *router) Init(opts ...Option) error {
for _, o := range opts {
o(&r.opts)
}
return nil
}
// Options returns router options
func (r *router) Options() Options {
return r.opts
}
// ID returns router ID
func (r *router) ID() string {
return r.opts.ID
}
// Table returns routing table
func (r *router) Table() Table {
return r.opts.Table
}
// Address returns router's bind address
func (r *router) Address() string {
return r.opts.Address
}
// Network returns the address router advertises to the network
func (r *router) Network() string {
return r.opts.Advertise
}
// Advertise advertises the router routes to the network.
// Advertise is a blocking function. It launches multiple goroutines that watch
// service registries and advertise the router routes to other routers in the network.
// It returns error if any of the launched goroutines fail with error.
func (r *router) Advertise() error {
// add local service routes into the routing table
if err := r.addServiceRoutes(r.opts.Registry, DefaultLocalMetric); err != nil {
return fmt.Errorf("failed adding routes for local services: %v", err)
}
// add network service routes into the routing table
if err := r.addServiceRoutes(r.opts.Network, DefaultNetworkMetric); err != nil {
return fmt.Errorf("failed adding routes for network services: %v", err)
}
node, err := r.parseToNode()
if err != nil {
return fmt.Errorf("failed to parse router into service node: %v", err)
}
localWatcher, err := r.opts.Registry.Watch()
if err != nil {
return fmt.Errorf("failed to create local registry watcher: %v", err)
}
networkWatcher, err := r.opts.Network.Watch()
if err != nil {
return fmt.Errorf("failed to create network registry watcher: %v", err)
}
// error channel collecting goroutine errors
errChan := make(chan error, 3)
r.wg.Add(1)
go func() {
defer r.wg.Done()
// watch local registry and register routes in routine table
errChan <- r.manageServiceRoutes(localWatcher, DefaultLocalMetric)
}()
r.wg.Add(1)
go func() {
defer r.wg.Done()
// watch network registry and register routes in routine table
errChan <- r.manageServiceRoutes(networkWatcher, DefaultNetworkMetric)
}()
r.wg.Add(1)
go func() {
defer r.wg.Done()
// watch local registry and advertise local service to the network
errChan <- r.advertiseToNetwork(node)
}()
return <-errChan
}
// addServiceRoutes adds all services in given registry to the routing table.
// NOTE: this is a one-off operation done when bootstrapping the routing table of the new router.
// It returns error if either the services could not be listed or if the routes could not be added to the routing table.
func (r *router) addServiceRoutes(reg registry.Registry, metric int) error {
services, err := reg.ListServices()
if err != nil {
return fmt.Errorf("failed to list services: %v", err)
}
for _, service := range services {
route := Route{
Destination: service.Name,
Router: r,
Network: r.opts.Advertise,
Metric: metric,
}
if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
return fmt.Errorf("error adding route for service: %s", service.Name)
}
}
return nil
}
// parseToNode parses router into registry.Node and returns the result.
// It returns error if the router network address could not be parsed into host and port.
func (r *router) parseToNode() (*registry.Node, error) {
// split router address to host and port part
addr, portStr, err := net.SplitHostPort(r.opts.Advertise)
if err != nil {
return nil, fmt.Errorf("could not parse router address: %v", err)
}
// try to parse network port into integer
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("could not parse router network address: %v", err)
}
node := &registry.Node{
Id: r.opts.ID,
Address: addr,
Port: port,
}
return node, nil
}
// advertiseToNetwork periodically scans local registry and registers (i.e. advertises) all the local services in the network registry.
// It returns error if either the local services failed to be listed or if it fails to register local service in network registry.
func (r *router) advertiseToNetwork(node *registry.Node) error {
// ticker to periodically scan the local registry
ticker := time.NewTicker(AdvertiseTick)
for {
select {
case <-r.exit:
return nil
case <-ticker.C:
// list all local services
services, err := r.opts.Registry.ListServices()
if err != nil {
return fmt.Errorf("failed to list local services: %v", err)
}
// loop through all registered local services and register them in the network registry
for _, service := range services {
svc := &registry.Service{
Name: service.Name,
Nodes: []*registry.Node{node},
}
// register the local service in the network registry
if err := r.opts.Network.Register(svc, registry.RegisterTTL(AdvertiseTTL)); err != nil {
return fmt.Errorf("failed to register service %s in network registry: %v", svc.Name, err)
}
}
}
}
}
// manageServiceRoutes watches services in given registry and updates the routing table accordingly.
// It returns error if the service registry watcher has stopped or if the routing table failed to be updated.
func (r *router) manageServiceRoutes(w registry.Watcher, metric int) error {
// wait in the background for the router to stop
// when the router stops, stop the watcher and exit
r.wg.Add(1)
go func() {
defer r.wg.Done()
<-r.exit
w.Stop()
}()
var watchErr error
for {
res, err := w.Next()
if err == registry.ErrWatcherStopped {
break
}
if err != nil {
watchErr = err
break
}
route := Route{
Destination: res.Service.Name,
Router: r,
Network: r.opts.Advertise,
Metric: metric,
}
switch res.Action {
case "create":
if len(res.Service.Nodes) > 0 {
// only return error if the route is not duplicate, but something else has failed
if err := r.opts.Table.Add(route); err != nil && err != ErrDuplicateRoute {
return fmt.Errorf("failed to add route for service: %v", res.Service.Name)
}
}
case "delete":
if len(res.Service.Nodes) < 1 {
// only return error if the route is present in the table, but something else has failed
if err := r.opts.Table.Delete(route); err != nil && err != ErrRouteNotFound {
return fmt.Errorf("failed to delete route for service: %v", res.Service.Name)
}
}
}
}
return watchErr
}
// Stop stops the router
func (r *router) Stop() error {
// notify all goroutines to finish
close(r.exit)
// wait for all goroutines to finish
r.wg.Wait()
// NOTE: we need a more efficient way of doing this e.g. network routes
// should ideally be autodeleted when the router stops gossiping
query := NewQuery(QueryRouter(r), QueryNetwork(r.opts.Advertise))
routes, err := r.opts.Table.Lookup(query)
if err != nil && err != ErrRouteNotFound {
return fmt.Errorf("failed to lookup routes for router %s: %v", r.opts.ID, err)
}
// parse router to registry.Node
node, err := r.parseToNode()
if err != nil {
return fmt.Errorf("failed to parse router into service node: %v", err)
}
for _, route := range routes {
service := &registry.Service{
Name: route.Destination,
Nodes: []*registry.Node{node},
}
if err := r.opts.Network.Deregister(service); err != nil {
return fmt.Errorf("failed to deregister service %s from network registry: %v", service.Name, err)
}
}
return nil
}
// String prints debugging information about router
func (r *router) String() string {
sb := &strings.Builder{}
table := tablewriter.NewWriter(sb)
table.SetHeader([]string{"ID", "Address", "Network", "Table"})
data := []string{
r.opts.ID,
r.opts.Address,
r.opts.Advertise,
fmt.Sprintf("%d", r.opts.Table.Size()),
}
table.Append(data)
// render table into sb
table.Render()
return sb.String()
}

View File

@@ -1,289 +0,0 @@
package router
import (
"fmt"
"hash"
"hash/fnv"
"strings"
"sync"
"github.com/google/uuid"
"github.com/olekukonko/tablewriter"
)
// TableOptions are routing table options
// TODO: table options TBD in the future
type TableOptions struct{}
// table is in memory routing table
type table struct {
// opts are table options
opts TableOptions
// m stores routing table map
m map[string]map[uint64]Route
// h hashes route entries
h hash.Hash64
// w is a list of table watchers
w map[string]*tableWatcher
sync.RWMutex
}
// newTable creates in memory routing table and returns it
func newTable(opts ...TableOption) Table {
// default options
var options TableOptions
// apply requested options
for _, o := range opts {
o(&options)
}
h := fnv.New64()
h.Reset()
return &table{
opts: options,
m: make(map[string]map[uint64]Route),
w: make(map[string]*tableWatcher),
h: h,
}
}
// Init initializes routing table with options
func (t *table) Init(opts ...TableOption) error {
for _, o := range opts {
o(&t.opts)
}
return nil
}
// Options returns routing table options
func (t *table) Options() TableOptions {
return t.opts
}
// Add adds a route to the routing table
func (t *table) Add(r Route) error {
destAddr := r.Destination
sum := t.hash(r)
t.Lock()
defer t.Unlock()
// check if the destination has any routes in the table
if _, ok := t.m[destAddr]; !ok {
t.m[destAddr] = make(map[uint64]Route)
t.m[destAddr][sum] = r
go t.sendEvent(&Event{Type: CreateEvent, Route: r})
return nil
}
// add new route to the table for the given destination
if _, ok := t.m[destAddr][sum]; !ok {
t.m[destAddr][sum] = r
go t.sendEvent(&Event{Type: CreateEvent, Route: r})
return nil
}
// only add the route if the route override is explicitly requested
if _, ok := t.m[destAddr][sum]; ok && r.Policy == OverrideIfExists {
t.m[destAddr][sum] = r
go t.sendEvent(&Event{Type: UpdateEvent, Route: r})
return nil
}
// if we reached this point without already returning the route already exists
// we return nil only if explicitly requested by the client
if r.Policy == IgnoreIfExists {
return nil
}
return ErrDuplicateRoute
}
// Delete deletes the route from the routing table
func (t *table) Delete(r Route) error {
t.Lock()
defer t.Unlock()
destAddr := r.Destination
sum := t.hash(r)
if _, ok := t.m[destAddr]; !ok {
return ErrRouteNotFound
}
delete(t.m[destAddr], sum)
go t.sendEvent(&Event{Type: DeleteEvent, Route: r})
return nil
}
// Update updates routing table with new route
func (t *table) Update(r Route) error {
destAddr := r.Destination
sum := t.hash(r)
t.Lock()
defer t.Unlock()
// check if the destAddr has ANY routes in the table
if _, ok := t.m[destAddr]; !ok {
return ErrRouteNotFound
}
// if the route has been found update it
if _, ok := t.m[destAddr][sum]; ok {
t.m[destAddr][sum] = r
go t.sendEvent(&Event{Type: UpdateEvent, Route: r})
return nil
}
return ErrRouteNotFound
}
// List returns a list of all routes in the table
func (t *table) List() ([]Route, error) {
t.RLock()
defer t.RUnlock()
var routes []Route
for _, rmap := range t.m {
for _, route := range rmap {
routes = append(routes, route)
}
}
return routes, nil
}
// Lookup queries routing table and returns all routes that match it
func (t *table) Lookup(q Query) ([]Route, error) {
t.RLock()
defer t.RUnlock()
var results []Route
for destAddr, routes := range t.m {
if q.Options().Destination != "*" {
if q.Options().Destination != destAddr {
continue
}
for _, route := range routes {
if q.Options().Network == "*" || q.Options().Network == route.Network {
if q.Options().Router.ID() == "*" || q.Options().Router.ID() == route.Router.ID() {
if route.Metric <= q.Options().Metric {
results = append(results, route)
}
}
}
}
}
if q.Options().Destination == "*" {
for _, route := range routes {
if q.Options().Network == "*" || q.Options().Network == route.Router.Network() {
if q.Options().Router.ID() == "*" || q.Options().Router.ID() == route.Router.ID() {
if route.Metric <= q.Options().Metric {
results = append(results, route)
}
}
}
}
}
}
if len(results) == 0 && q.Options().Policy != DiscardNoRoute {
return nil, ErrRouteNotFound
}
return results, nil
}
// Watch returns routing table entry watcher
func (t *table) Watch(opts ...WatchOption) (Watcher, error) {
// by default watch everything
wopts := WatchOptions{
Destination: "*",
Network: "*",
}
for _, o := range opts {
o(&wopts)
}
watcher := &tableWatcher{
opts: wopts,
resChan: make(chan *Event, 10),
done: make(chan struct{}),
}
t.Lock()
t.w[uuid.New().String()] = watcher
t.Unlock()
return watcher, nil
}
// sendEvent sends rules to all subscribe watchers
func (t *table) sendEvent(r *Event) {
t.RLock()
defer t.RUnlock()
for _, w := range t.w {
select {
case w.resChan <- r:
case <-w.done:
}
}
}
// Size returns the size of the routing table
func (t *table) Size() int {
t.RLock()
defer t.RUnlock()
return len(t.m)
}
// String returns debug information
func (t *table) String() string {
t.RLock()
defer t.RUnlock()
// this will help us build routing table string
sb := &strings.Builder{}
// create nice table printing structure
table := tablewriter.NewWriter(sb)
table.SetHeader([]string{"Destination", "Router", "Network", "Metric"})
for _, destRoute := range t.m {
for _, route := range destRoute {
strRoute := []string{
route.Destination,
route.Router.Address(),
route.Network,
fmt.Sprintf("%d", route.Metric),
}
table.Append(strRoute)
}
}
// render table into sb
table.Render()
return sb.String()
}
// hash hashes the route using router gateway and network address
func (t *table) hash(r Route) uint64 {
destAddr := r.Destination
routerAddr := r.Router.Address()
netAddr := r.Network
t.h.Reset()
t.h.Write([]byte(destAddr + routerAddr + netAddr))
return t.h.Sum64()
}

180
router/handler/router.go Normal file
View File

@@ -0,0 +1,180 @@
package handler
import (
"context"
"io"
"time"
"github.com/micro/go-micro/errors"
"github.com/micro/go-micro/router"
pb "github.com/micro/go-micro/router/proto"
)
// Router implements router handler
type Router struct {
Router router.Router
}
// Lookup looks up routes in the routing table and returns them
func (r *Router) Lookup(ctx context.Context, req *pb.LookupRequest, resp *pb.LookupResponse) error {
query := router.NewQuery(
router.QueryService(req.Query.Service),
)
routes, err := r.Router.Lookup(query)
if err != nil {
return errors.InternalServerError("go.micro.router", "failed to lookup routes: %v", err)
}
var respRoutes []*pb.Route
for _, route := range routes {
respRoute := &pb.Route{
Service: route.Service,
Address: route.Address,
Gateway: route.Gateway,
Network: route.Network,
Link: route.Link,
Metric: int64(route.Metric),
}
respRoutes = append(respRoutes, respRoute)
}
resp.Routes = respRoutes
return nil
}
func (r *Router) Advertise(ctx context.Context, req *pb.Request, stream pb.Router_AdvertiseStream) error {
advertChan, err := r.Router.Advertise()
if err != nil {
return errors.InternalServerError("go.micro.router", "failed to get adverts: %v", err)
}
for advert := range advertChan {
var events []*pb.Event
for _, event := range advert.Events {
route := &pb.Route{
Service: event.Route.Service,
Address: event.Route.Address,
Gateway: event.Route.Gateway,
Network: event.Route.Network,
Link: event.Route.Link,
Metric: int64(event.Route.Metric),
}
e := &pb.Event{
Type: pb.EventType(event.Type),
Timestamp: event.Timestamp.UnixNano(),
Route: route,
}
events = append(events, e)
}
advert := &pb.Advert{
Id: advert.Id,
Type: pb.AdvertType(advert.Type),
Timestamp: advert.Timestamp.UnixNano(),
Events: events,
}
// send the advert
err := stream.Send(advert)
if err == io.EOF {
return nil
}
if err != nil {
return errors.InternalServerError("go.micro.router", "error sending message %v", err)
}
}
return nil
}
func (r *Router) Process(ctx context.Context, req *pb.Advert, rsp *pb.ProcessResponse) error {
events := make([]*router.Event, len(req.Events))
for i, event := range req.Events {
route := router.Route{
Service: event.Route.Service,
Address: event.Route.Address,
Gateway: event.Route.Gateway,
Network: event.Route.Network,
Link: event.Route.Link,
Metric: int(event.Route.Metric),
}
events[i] = &router.Event{
Type: router.EventType(event.Type),
Timestamp: time.Unix(0, event.Timestamp),
Route: route,
}
}
advert := &router.Advert{
Id: req.Id,
Type: router.AdvertType(req.Type),
Timestamp: time.Unix(0, req.Timestamp),
TTL: time.Duration(req.Ttl),
Events: events,
}
if err := r.Router.Process(advert); err != nil {
return errors.InternalServerError("go.micro.router", "error publishing advert: %v", err)
}
return nil
}
func (r *Router) Status(ctx context.Context, req *pb.Request, rsp *pb.StatusResponse) error {
status := r.Router.Status()
rsp.Status = &pb.Status{
Code: status.Code.String(),
}
if status.Error != nil {
rsp.Status.Error = status.Error.Error()
}
return nil
}
// Watch streans routing table events
func (r *Router) Watch(ctx context.Context, req *pb.WatchRequest, stream pb.Router_WatchStream) error {
watcher, err := r.Router.Watch()
if err != nil {
return errors.InternalServerError("go.micro.router", "failed creating event watcher: %v", err)
}
defer stream.Close()
for {
event, err := watcher.Next()
if err == router.ErrWatcherStopped {
break
}
if err != nil {
return errors.InternalServerError("go.micro.router", "error watching events: %v", err)
}
route := &pb.Route{
Service: event.Route.Service,
Address: event.Route.Address,
Gateway: event.Route.Gateway,
Network: event.Route.Network,
Link: event.Route.Link,
Metric: int64(event.Route.Metric),
}
tableEvent := &pb.Event{
Type: pb.EventType(event.Type),
Timestamp: event.Timestamp.UnixNano(),
Route: route,
}
if err := stream.Send(tableEvent); err != nil {
return err
}
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More