Compare commits

...

73 Commits

Author SHA1 Message Date
650d167313 meter: add BuildLabels func that sorts and deletes duplicates
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 14:10:20 +03:00
c6ba2a91e6 meter: BuildName func to combine metric name with labels into string
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-21 12:39:59 +03:00
7ece08896f server: use 127.0.0.1:0 if no address provided
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-17 01:57:39 +06:00
dependabot[bot]
57f6f23294 build(deps): bump github.com/google/uuid from 1.2.0 to 1.3.0 (#53)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Commits](https://github.com/google/uuid/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-16 00:27:56 +03:00
09e6fa2fed flow: implement new methods, add Async ExecutionOption
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-16 00:17:16 +03:00
10a09a5c6f flow: improve store
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:56:34 +03:00
b4e5d9462a util/router: move some messages to Trace level
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:56:34 +03:00
96aa0b6906 store/memory: fix List
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 22:53:12 +03:00
f54658830d store/memory: fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 12:11:55 +03:00
1e43122660 store/memory: small fixups for flow usage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-15 11:59:35 +03:00
42800fa247 flow: improve steps handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:12:54 +03:00
5b9c810653 logger: add compile time test for interface compat
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:12:09 +03:00
c3def24bf4 store: add Wrappers support, create Namespace wrapper
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-14 17:11:37 +03:00
0d1ef31764 client: change AuthToken option signature
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-09 10:47:40 +03:00
d49afa230f logger: add omit logger
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-05 23:04:20 +03:00
e545eb4e13 logger: add wrapper support
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-05 22:32:47 +03:00
f28b107372 broker: fix RawMessage marshal
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 23:23:01 +03:00
c592fabe2a minor fixes
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 15:56:22 +03:00
eb107020c7 broker: replace Message.Body []byte slice to RawMessage
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-07-01 15:11:17 +03:00
bd4d4c363e flow improvements (#52)
* flow improvements

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-30 17:50:58 +03:00
2a548634fd config: add Save/Load options
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-20 23:57:13 +03:00
598dddc476 util/reflect: fix time.Time StructFields parsing
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-19 15:58:36 +03:00
887b48f1e7 util/reflect: improve StructFields func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-17 12:53:23 +03:00
6e55d07636 client: allow to publish body only
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-13 12:20:35 +03:00
919520219c client: WithBodyOnly publish option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-06-11 14:14:41 +03:00
60a5e737f8 util/reflect: return pointer from helper funcs
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:44:22 +03:00
34f0b209cc codec: add ability to control codec via struct tags
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-25 22:20:39 +03:00
ba8e1889fe dependabot
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-16 17:18:56 +03:00
dae5c57a60 Create dependabot.yml 2021-05-15 14:46:22 +03:00
ea590d57df meter/wrapper: add inflight request/message count (#47)
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-05-10 17:59:40 +03:00
Renovate Bot
9aa6969836 fix(deps): update golang.org/x/net commit hash to 4163338 2021-05-10 14:29:32 +00:00
Renovate Bot
c00c705c24 fix(deps): update golang.org/x/net commit hash to 16afe75 2021-05-08 09:02:01 +00:00
Renovate Bot
0239f795d8 fix(deps): update golang.org/x/net commit hash to 7fd8e65 2021-05-03 10:07:41 +00:00
Renovate Bot
e69b43881d fix(deps): update golang.org/x/net commit hash to f8dd838 2021-05-01 23:20:59 +00:00
3a48a613fe not fail on lint now
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 08:36:11 +03:00
86626c5922 fieldalignment of all structs to save memory
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 08:32:47 +03:00
ee11f39a2f fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
3bdfdd8fd2 meter: fix labels
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
6dfdff7fd8 fieldaligment
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
00a4785df3 fixup
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-27 00:03:18 +03:00
Renovate Bot
bae3b0ef94 fix(deps): update golang.org/x/net commit hash to 5f58ad6 2021-04-23 23:52:34 +00:00
Renovate Bot
89b0565062 fix(deps): update golang.org/x/net commit hash to 4e50805 2021-04-22 03:04:18 +00:00
1f8b0aeb61 store: remove unused Value type
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-22 00:57:06 +03:00
Renovate Bot
5b6f849e0a fix(deps): update golang.org/x/net commit hash to 798c215 2021-04-20 23:13:59 +00:00
Renovate Bot
3b416fffde fix(deps): update golang.org/x/net commit hash to d25e304 2021-04-20 15:04:17 +00:00
3a60103aed server: drop Internal option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-20 12:45:14 +03:00
41837a67f8 register: drop verbose values export
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-20 12:39:21 +03:00
852f19598d util/reflect: fix protobuf field name detection
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 11:34:28 +03:00
6537b35773 util/reflect: add interface merging
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-19 01:19:37 +03:00
b733f1316f remove stale generate stuff
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-16 17:36:27 +03:00
Renovate Bot
840af5574c fix(deps): update golang.org/x/net commit hash to e915ea6 2021-04-16 00:56:52 +00:00
Renovate Bot
56e5b7001c fix(deps): update golang.org/x/net commit hash to 0645797 2021-04-14 21:41:15 +00:00
Renovate Bot
11dc6fd752 fix(deps): update golang.org/x/net commit hash to afb366f 2021-04-10 11:09:36 +00:00
a2695d8699 util/reflect: rewrite struct merging with map
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-10 01:22:40 +03:00
618421de05 client: allow to set content-type for call
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-09 23:09:12 +03:00
Renovate Bot
30baaabd9f fix(deps): update golang.org/x/net commit hash to a5a99cb 2021-04-05 19:46:46 +00:00
df5bce1191 util/reflect: fix StructURLValues
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-03 11:50:23 +03:00
Renovate Bot
089d0fe4df fix(deps): update golang.org/x/net commit hash to 0fccb6f 2021-03-31 22:50:38 +00:00
a06f535303 util/reflect: add StructURLValues func
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-04-01 00:30:26 +03:00
Renovate Bot
eba586a329 fix(deps): update golang.org/x/net commit hash to cb1fcc7 2021-03-31 09:19:27 +00:00
Renovate Bot
d74a8645e8 fix(deps): update golang.org/x/net commit hash to e572328 2021-03-31 00:52:53 +00:00
Renovate Bot
5a00786192 fix(deps): update golang.org/x/net commit hash to cd0ac97 2021-03-30 22:28:38 +00:00
Renovate Bot
b3e9941634 fix(deps): update golang.org/x/net commit hash to c8897c2 2021-03-30 16:02:28 +00:00
Renovate Bot
a5a5904302 fix(deps): update golang.org/x/net commit hash to 22f4162 2021-03-30 11:44:00 +00:00
Renovate Bot
a59832e57e fix(deps): update golang.org/x/net commit hash to df645c7 2021-03-30 05:11:12 +00:00
0e42033e7f meter/handler: more idiomatic option handling
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-29 17:51:44 +03:00
52d8255974 service init with own context
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 23:42:02 +03:00
9830cb48a9 fix compilation
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:31:03 +03:00
92d7ab2105 regen handlers
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:28:01 +03:00
d2935ef399 meter/handler: fix proto and generated code
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:18:11 +03:00
ce4c96ae0a server/health: add health check handler
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2021-03-28 19:18:11 +03:00
Renovate Bot
14026d15be fix(deps): update golang.org/x/net commit hash to 61e0566 2021-03-27 01:29:30 +00:00
Renovate Bot
2df0c7643e fix(deps): update golang.org/x/net commit hash to 6b15177 2021-03-26 19:17:30 +00:00
119 changed files with 3499 additions and 1522 deletions

19
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Maintain dependencies for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
# Maintain dependencies for Golang
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

15
.github/generate.sh vendored
View File

@@ -1,15 +0,0 @@
#!/bin/bash -e
find . -type f -name '*.pb.*.go' -o -name '*.pb.go' -a ! -name 'message.pb.go' -delete
PROTOS=$(find . -type f -name '*.proto' | grep -v proto/google/api)
mkdir -p proto/google/api
curl -s -o proto/google/api/annotations.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/annotations.proto
curl -s -o proto/google/api/http.proto -L https://raw.githubusercontent.com/googleapis/googleapis/master/google/api/http.proto
for PROTO in $PROTOS; do
echo $PROTO
protoc -I./proto -I. -I$(dirname $PROTO) --go-grpc_out=paths=source_relative:. --go_out=paths=source_relative:. --micro_out=paths=source_relative:. $PROTO
done
rm -r proto

20
.github/renovate.json vendored
View File

@@ -1,20 +0,0 @@
{
"extends": [
"config:base"
],
"postUpdateOptions": ["gomodTidy"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch", "pin", "digest"],
"automerge": true
},
{
"groupName": "all deps",
"separateMajorMinor": true,
"groupSlug": "all",
"packagePatterns": [
"*"
]
}
]
}

13
.github/stale.sh vendored
View File

@@ -1,13 +0,0 @@
#!/bin/bash -ex
export PATH=$PATH:$(pwd)/bin
export GO111MODULE=on
export GOBIN=$(pwd)/bin
#go get github.com/rvflash/goup@v0.4.1
#goup -v ./...
#go get github.com/psampaz/go-mod-outdated@v0.6.0
go list -u -m -mod=mod -json all | go-mod-outdated -update -direct -ci || true
#go list -u -m -json all | go-mod-outdated -update

View File

@@ -53,7 +53,7 @@ jobs:
continue-on-error: true
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30
version: v1.39
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.

View File

@@ -53,7 +53,7 @@ jobs:
continue-on-error: true
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.30
version: v1.39
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.

View File

@@ -1,30 +1,44 @@
run:
concurrency: 4
deadline: 5m
modules-download-mode: readonly
skip-files:
- ".*\\.pb\\.go$"
- ".*\\.pb\\.micro\\.go$"
issues-exit-code: 1
tests: true
linters-settings:
govet:
check-shadowing: true
enable:
- fieldalignment
linters:
disable-all: false
enable-all: false
enable:
- megacheck
- staticcheck
- deadcode
- varcheck
- gosimple
- unused
- prealloc
- scopelint
- gocritic
- goimports
- unconvert
- govet
- nakedret
- deadcode
- errcheck
- govet
- ineffassign
- staticcheck
- structcheck
- gosec
disable:
- maligned
- interfacer
- typecheck
- dupl
- unused
- varcheck
- bodyclose
- gci
- goconst
- gocritic
- gosimple
- gofmt
- gofumpt
- goimports
- golint
- gosec
- makezero
- misspell
- nakedret
- nestif
- nilerr
- noctx
- prealloc
- unconvert
- unparam
disable-all: false

View File

@@ -149,5 +149,4 @@ func TestValidate(t *testing.T) {
if err := Validate(epPcreInvalid); err == nil {
t.Fatalf("invalid pcre %v", epPcreInvalid.Path[0])
}
}

View File

@@ -6,10 +6,8 @@ import (
"github.com/unistack-org/micro/v3/logger"
)
var (
// DefaultMaxRecvSize specifies max recv size for handler
DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
)
// DefaultMaxRecvSize specifies max recv size for handler
var DefaultMaxRecvSize int64 = 1024 * 1024 * 100 // 10Mb
// Options struct holds handler options
type Options struct {

View File

@@ -15,12 +15,12 @@ import (
// NewResolver creates new subdomain api resolver
func NewResolver(parent resolver.Resolver, opts ...resolver.Option) resolver.Resolver {
options := resolver.NewOptions(opts...)
return &subdomainResolver{options, parent}
return &subdomainResolver{opts: options, Resolver: parent}
}
type subdomainResolver struct {
opts resolver.Options
resolver.Resolver
opts resolver.Options
}
// Resolve resolve endpoint based on subdomain

View File

@@ -19,9 +19,7 @@ type vpathResolver struct {
opts resolver.Options
}
var (
re = regexp.MustCompile("^v[0-9]+$")
)
var re = regexp.MustCompile("^v[0-9]+$")
// Resolve endpoint
func (r *vpathResolver) Resolve(req *http.Request, opts ...resolver.ResolveOption) (*resolver.Endpoint, error) {

View File

@@ -7,10 +7,8 @@ import (
"github.com/unistack-org/micro/v3/api"
)
var (
// DefaultRouter contains default router implementation
DefaultRouter Router
)
// DefaultRouter contains default router implementation
var DefaultRouter Router
// Router is used to determine an endpoint for a request
type Router interface {

View File

@@ -24,11 +24,11 @@ func TestVerify(t *testing.T) {
}
tt := []struct {
Name string
Rules []*Rule
Error error
Account *Account
Resource *Resource
Error error
Name string
Rules []*Rule
}{
{
Name: "NoRules",

View File

@@ -3,14 +3,13 @@ package broker
import (
"context"
"errors"
"github.com/unistack-org/micro/v3/metadata"
)
var (
// DefaultBroker default broker
DefaultBroker Broker = NewBroker()
)
// DefaultBroker default broker
var DefaultBroker Broker = NewBroker()
// Broker is an interface used for asynchronous messaging.
type Broker interface {
@@ -36,10 +35,31 @@ type Event interface {
Error() error
}
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
// Message is used to transfer data
type Message struct {
Header metadata.Metadata // contains message metadata
Body []byte // contains message body
Body RawMessage // contains message body
}
// Subscriber is a convenience return type for the Subscribe method

View File

@@ -13,27 +13,27 @@ import (
)
type memoryBroker struct {
opts Options
Subscribers map[string][]*memorySubscriber
addr string
opts Options
sync.RWMutex
connected bool
}
type memoryEvent struct {
opts Options
err error
message interface{}
topic string
opts Options
}
type memorySubscriber struct {
opts SubscribeOptions
ctx context.Context
exit chan bool
handler Handler
id string
topic string
opts SubscribeOptions
}
func (m *memoryBroker) Options() Options {

View File

@@ -32,7 +32,7 @@ func TestMemoryBroker(t *testing.T) {
"foo": "bar",
"id": fmt.Sprintf("%d", i),
},
Body: []byte(`hello world`),
Body: []byte(`"hello world"`),
}
if err := b.Publish(ctx, topic, message); err != nil {

View File

@@ -9,16 +9,10 @@ import (
"github.com/unistack-org/micro/v3/metadata"
)
var (
// DefaultCodecs will be used to encode/decode data
DefaultCodecs = map[string]codec.Codec{
//"application/json": cjson.NewCodec,
//"application/json-rpc": cjsonrpc.NewCodec,
//"application/protobuf": cproto.NewCodec,
//"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": codec.NewCodec(),
}
)
// DefaultCodecs will be used to encode/decode data
var DefaultCodecs = map[string]codec.Codec{
"application/octet-stream": codec.NewCodec(),
}
type noopClient struct {
opts Options
@@ -227,5 +221,8 @@ func (n *noopClient) Publish(ctx context.Context, p Message, opts ...PublishOpti
return n.opts.Broker.Publish(ctx, topic, &broker.Message{
Header: md,
Body: body,
}, broker.PublishContext(options.Context))
},
broker.PublishContext(options.Context),
broker.PublishBodyOnly(options.BodyOnly),
)
}

View File

@@ -19,8 +19,8 @@ import (
// Options holds client options
type Options struct {
// CallOptions contains default CallOptions
CallOptions CallOptions
// Selector used to select needed address
Selector selector.Selector
// Logger used to log messages
Logger logger.Logger
// Tracer used for tracing
@@ -31,30 +31,30 @@ type Options struct {
Meter meter.Meter
// Router used to get route
Router router.Router
// Selector used to select needed address
Selector selector.Selector
// Transport used for transfer messages
Transport transport.Transport
// Context is used for external options
Context context.Context
// Codecs map
Codecs map[string]codec.Codec
// Lookup func used to get destination addr
Lookup LookupFunc
// Codecs map
Codecs map[string]codec.Codec
// TLSConfig specifies tls.Config for secure connection
TLSConfig *tls.Config
// Proxy is used for proxy requests
Proxy string
// ContentType is Used to select codec
// ContentType is used to select codec
ContentType string
// Name is the client name
Name string
// Wrappers contains wrappers
Wrappers []Wrapper
// CallOptions contains default CallOptions
CallOptions CallOptions
// PoolSize connection pool size
PoolSize int
// PoolTTL connection pool ttl
PoolTTL time.Duration
// TLSConfig specifies tls.Config for secure connection
TLSConfig *tls.Config
}
// NewCallOptions creates new call options struct
@@ -80,6 +80,8 @@ type CallOptions struct {
Backoff BackoffFunc
// Network name
Network string
// Content-Type
ContentType string
// CallWrappers call wrappers
CallWrappers []CallWrapper
// SelectOptions selector options
@@ -94,8 +96,8 @@ type CallOptions struct {
RequestTimeout time.Duration
// DialTimeout dial timeout
DialTimeout time.Duration
// AuthToken flag
AuthToken bool
// AuthToken string
AuthToken string
}
// Context pass context to client
@@ -116,6 +118,8 @@ func NewPublishOptions(opts ...PublishOption) PublishOptions {
// PublishOptions holds publish options
type PublishOptions struct {
// BodyOnly will publish only message body
BodyOnly bool
// Context used for external options
Context context.Context
// Exchange topic exchange name
@@ -375,6 +379,13 @@ func WithExchange(e string) PublishOption {
}
}
// WithBodyOnly publish only message body
func WithBodyOnly(b bool) PublishOption {
return func(o *PublishOptions) {
o.BodyOnly = b
}
}
// PublishContext sets the context in publish options
func PublishContext(ctx context.Context) PublishOption {
return func(o *PublishOptions) {
@@ -382,6 +393,13 @@ func PublishContext(ctx context.Context) PublishOption {
}
}
// WithContentType specifies call content type
func WithContentType(ct string) CallOption {
return func(o *CallOptions) {
o.ContentType = ct
}
}
// WithAddress sets the remote addresses to use rather than using service discovery
func WithAddress(a ...string) CallOption {
return func(o *CallOptions) {
@@ -445,9 +463,9 @@ func WithDialTimeout(d time.Duration) CallOption {
// WithAuthToken is a CallOption which overrides the
// authorization header with the services own auth token
func WithAuthToken() CallOption {
func WithAuthToken(t string) CallOption {
return func(o *CallOptions) {
o.AuthToken = true
o.AuthToken = t
}
}
@@ -486,16 +504,16 @@ func WithMessageContentType(ct string) MessageOption {
}
}
// WithContentType specifies request content type
func WithContentType(ct string) RequestOption {
// StreamingRequest specifies that request is streaming
func StreamingRequest(b bool) RequestOption {
return func(o *RequestOptions) {
o.Stream = b
}
}
// RequestContentType specifies request content type
func RequestContentType(ct string) RequestOption {
return func(o *RequestOptions) {
o.ContentType = ct
}
}
// StreamingRequest specifies that request is streaming
func StreamingRequest() RequestOption {
return func(o *RequestOptions) {
o.Stream = true
}
}

View File

@@ -5,13 +5,13 @@ import (
)
type testRequest struct {
opts RequestOptions
codec codec.Codec
body interface{}
service string
method string
endpoint string
contentType string
service string
opts RequestOptions
}
func (r *testRequest) ContentType() string {

View File

@@ -28,6 +28,8 @@ var (
DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb
// DefaultCodec is the global default codec
DefaultCodec Codec = NewCodec()
// DefaultTagName specifies struct tag name to control codec Marshal/Unmarshal
DefaultTagName = "codec"
)
// MessageType specifies message type for codec

View File

@@ -5,8 +5,7 @@ import (
"io"
)
type noopCodec struct {
}
type noopCodec struct{}
func (c *noopCodec) ReadHeader(conn io.Reader, m *Message, t MessageType) error {
return nil

View File

@@ -6,10 +6,8 @@ import (
"errors"
)
var (
// DefaultConfig default config
DefaultConfig Config = NewConfig()
)
// DefaultConfig default config
var DefaultConfig Config = NewConfig()
var (
// ErrCodecMissing is returned when codec needed and not specified
@@ -22,34 +20,36 @@ var (
// Config is an interface abstraction for dynamic configuration
type Config interface {
// Name returns name of config
Name() string
// Init the config
Init(opts ...Option) error
// Options in the config
Options() Options
// Load config from sources
Load(context.Context) error
Load(context.Context, ...LoadOption) error
// Save config to sources
Save(context.Context) error
Save(context.Context, ...SaveOption) error
// Watch a value for changes
// Watch(interface{}) (Watcher, error)
//Watch(context.Context) (Watcher, error)
// String returns config type name
String() string
}
// Watcher is the config watcher
//type Watcher interface {
// Next() (, error)
// Stop() error
//}
type Watcher interface {
// Next() (, error)
Stop() error
}
// Load loads config from config sources
func Load(ctx context.Context, cs ...Config) error {
func Load(ctx context.Context, cs []Config, opts ...LoadOption) error {
var err error
for _, c := range cs {
if err = c.Init(); err != nil {
return err
}
if err = c.Load(ctx); err != nil {
if err = c.Load(ctx, opts...); err != nil {
return err
}
}

View File

@@ -25,18 +25,27 @@ func (c *defaultConfig) Init(opts ...Option) error {
return nil
}
func (c *defaultConfig) Load(ctx context.Context) error {
func (c *defaultConfig) Load(ctx context.Context, opts ...LoadOption) error {
for _, fn := range c.opts.BeforeLoad {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err
}
}
options := NewLoadOptions(opts...)
mopts := []func(*mergo.Config){mergo.WithTypeCheck}
if options.Override {
mopts = append(mopts, mergo.WithOverride)
}
if options.Append {
mopts = append(mopts, mergo.WithAppendSlice)
}
src, err := rutil.Zero(c.opts.Struct)
if err == nil {
valueOf := reflect.ValueOf(src)
if err = c.fillValues(ctx, valueOf); err == nil {
err = mergo.Merge(c.opts.Struct, src, mergo.WithOverride, mergo.WithTypeCheck, mergo.WithAppendSlice)
if err = c.fillValues(valueOf); err == nil {
err = mergo.Merge(c.opts.Struct, src, mopts...)
}
}
@@ -54,7 +63,7 @@ func (c *defaultConfig) Load(ctx context.Context) error {
}
//nolint:gocyclo
func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val string) error {
func (c *defaultConfig) fillValue(value reflect.Value, val string) error {
if !rutil.IsEmpty(value) {
return nil
}
@@ -71,10 +80,10 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
kv := strings.FieldsFunc(nval, func(c rune) bool { return c == '=' })
mkey := reflect.Indirect(reflect.New(kt))
mval := reflect.Indirect(reflect.New(et))
if err := c.fillValue(ctx, mkey, kv[0]); err != nil {
if err := c.fillValue(mkey, kv[0]); err != nil {
return err
}
if err := c.fillValue(ctx, mval, kv[1]); err != nil {
if err := c.fillValue(mval, kv[1]); err != nil {
return err
}
value.SetMapIndex(mkey, mval)
@@ -84,7 +93,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
value.Set(reflect.MakeSlice(reflect.SliceOf(value.Type().Elem()), len(nvals), len(nvals)))
for idx, nval := range nvals {
nvalue := reflect.Indirect(reflect.New(value.Type().Elem()))
if err := c.fillValue(ctx, nvalue, nval); err != nil {
if err := c.fillValue(nvalue, nval); err != nil {
return err
}
value.Index(idx).Set(nvalue)
@@ -173,7 +182,7 @@ func (c *defaultConfig) fillValue(ctx context.Context, value reflect.Value, val
return nil
}
func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) error {
func (c *defaultConfig) fillValues(valueOf reflect.Value) error {
var values reflect.Value
if valueOf.Kind() == reflect.Ptr {
@@ -200,7 +209,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
switch value.Kind() {
case reflect.Struct:
value.Set(reflect.Indirect(reflect.New(value.Type())))
if err := c.fillValues(ctx, value); err != nil {
if err := c.fillValues(value); err != nil {
return err
}
continue
@@ -214,7 +223,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
value.Set(reflect.New(value.Type().Elem()))
}
value = value.Elem()
if err := c.fillValues(ctx, value); err != nil {
if err := c.fillValues(value); err != nil {
return err
}
continue
@@ -224,7 +233,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
continue
}
if err := c.fillValue(ctx, value, tag); err != nil {
if err := c.fillValue(value, tag); err != nil {
return err
}
}
@@ -232,7 +241,7 @@ func (c *defaultConfig) fillValues(ctx context.Context, valueOf reflect.Value) e
return nil
}
func (c *defaultConfig) Save(ctx context.Context) error {
func (c *defaultConfig) Save(ctx context.Context, opts ...SaveOption) error {
for _, fn := range c.opts.BeforeSave {
if err := fn(ctx, c); err != nil && !c.opts.AllowFail {
return err

View File

@@ -10,30 +10,30 @@ import (
type Cfg struct {
StringValue string `default:"string_value"`
IntValue int `default:"99"`
IgnoreValue string `json:"-"`
StructValue struct {
StringValue string `default:"string_value"`
}
IntValue int `default:"99"`
}
func TestDefault(t *testing.T) {
ctx := context.Background()
conf := &Cfg{IntValue: 10}
blfn := func(ctx context.Context, cfg config.Config) error {
conf, ok := cfg.Options().Struct.(*Cfg)
nconf, ok := cfg.Options().Struct.(*Cfg)
if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
}
conf.StringValue = "before_load"
nconf.StringValue = "before_load"
return nil
}
alfn := func(ctx context.Context, cfg config.Config) error {
conf, ok := cfg.Options().Struct.(*Cfg)
nconf, ok := cfg.Options().Struct.(*Cfg)
if !ok {
return fmt.Errorf("failed to get Struct from options: %v", cfg.Options())
}
conf.StringValue = "after_load"
nconf.StringValue = "after_load"
return nil
}

View File

@@ -57,6 +57,52 @@ func NewOptions(opts ...Option) Options {
return options
}
// LoadOption function signature
type LoadOption func(o *LoadOptions)
// LoadOptions struct
type LoadOptions struct {
Override bool
Append bool
}
func NewLoadOptions(opts ...LoadOption) LoadOptions {
options := LoadOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// LoadOverride override values when load
func LoadOverride(b bool) LoadOption {
return func(o *LoadOptions) {
o.Override = b
}
}
// LoadAppend override values when load
func LoadAppend(b bool) LoadOption {
return func(o *LoadOptions) {
o.Append = b
}
}
// SaveOption function signature
type SaveOption func(o *SaveOptions)
// SaveOptions struct
type SaveOptions struct {
}
func NewSaveOptions(opts ...SaveOption) SaveOptions {
options := SaveOptions{}
for _, o := range opts {
o(&options)
}
return options
}
// AllowFail allows config source to fail
func AllowFail(b bool) Option {
return func(o *Options) {

View File

@@ -7,9 +7,9 @@ import (
)
type Config struct {
Value string
SubConfig *SubConfig
Config *Config
Value string
}
type SubConfig struct {

View File

@@ -17,7 +17,6 @@ func TestFromError(t *testing.T) {
if merr.Id != "go.micro.test" || merr.Code != 404 {
t.Fatalf("invalid conversation %v != %v", err, merr)
}
}
func TestEqual(t *testing.T) {
@@ -32,7 +31,6 @@ func TestEqual(t *testing.T) {
if Equal(err1, err3) {
t.Fatal("errors must be not equal")
}
}
func TestErrors(t *testing.T) {

34
flow/context.go Normal file
View File

@@ -0,0 +1,34 @@
package flow
import (
"context"
)
type flowKey struct{}
// FromContext returns Flow from context
func FromContext(ctx context.Context) (Flow, bool) {
if ctx == nil {
return nil, false
}
c, ok := ctx.Value(flowKey{}).(Flow)
return c, ok
}
// NewContext stores Flow to context
func NewContext(ctx context.Context, f Flow) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, flowKey{}, f)
}
// SetOption returns a function to setup a context with given value
func SetOption(k, v interface{}) Option {
return func(o *Options) {
if o.Context == nil {
o.Context = context.Background()
}
o.Context = context.WithValue(o.Context, k, v)
}
}

View File

@@ -44,10 +44,10 @@ func TestDag(t *testing.T) {
var steps [][]string
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]string, 1, 1)
steps = make([][]string, 1)
steps[0] = make([]string, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]string, idx+1, idx+1)
tsteps := make([][]string, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]string, 0, 1)

588
flow/default.go Normal file
View File

@@ -0,0 +1,588 @@
package flow
import (
"context"
"fmt"
"path/filepath"
"sync"
"github.com/google/uuid"
"github.com/silas/dag"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/metadata"
"github.com/unistack-org/micro/v3/store"
)
type microFlow struct {
opts Options
}
type microWorkflow struct {
id string
g *dag.AcyclicGraph
init bool
sync.RWMutex
opts Options
steps map[string]Step
status Status
}
func (w *microWorkflow) ID() string {
return w.id
}
func (w *microWorkflow) Steps() ([][]Step, error) {
return w.getSteps("", false)
}
func (w *microWorkflow) Status() Status {
return w.status
}
func (w *microWorkflow) AppendSteps(steps ...Step) error {
w.Lock()
for _, s := range steps {
w.steps[s.String()] = s
w.g.Add(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
w.Unlock()
return err
}
w.g.TransitiveReduction()
w.Unlock()
return nil
}
func (w *microWorkflow) RemoveSteps(steps ...Step) error {
// TODO: handle case when some step requires or required by removed step
w.Lock()
for _, s := range steps {
delete(w.steps, s.String())
w.g.Remove(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
w.Unlock()
return err
}
w.g.TransitiveReduction()
w.Unlock()
return nil
}
func (w *microWorkflow) getSteps(start string, reverse bool) ([][]Step, error) {
var steps [][]Step
var root dag.Vertex
var err error
fn := func(n dag.Vertex, idx int) error {
if idx == 0 {
steps = make([][]Step, 1)
steps[0] = make([]Step, 0, 1)
} else if idx >= len(steps) {
tsteps := make([][]Step, idx+1)
copy(tsteps, steps)
steps = tsteps
steps[idx] = make([]Step, 0, 1)
}
steps[idx] = append(steps[idx], n.(Step))
return nil
}
if start != "" {
var ok bool
w.RLock()
root, ok = w.steps[start]
w.RUnlock()
if !ok {
return nil, ErrStepNotExists
}
} else {
root, err = w.g.Root()
if err != nil {
return nil, err
}
}
if reverse {
err = w.g.SortedReverseDepthFirstWalk([]dag.Vertex{root}, fn)
} else {
err = w.g.SortedDepthFirstWalk([]dag.Vertex{root}, fn)
}
if err != nil {
return nil, err
}
return steps, nil
}
func (w *microWorkflow) Abort(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusAborted.String())})
}
func (w *microWorkflow) Suspend(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusSuspend.String())})
}
func (w *microWorkflow) Resume(ctx context.Context, eid string) error {
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
return workflowStore.Write(ctx, "status", &codec.Frame{Data: []byte(StatusRunning.String())})
}
func (w *microWorkflow) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error) {
w.Lock()
if !w.init {
if err := w.g.Validate(); err != nil {
w.Unlock()
return "", err
}
w.g.TransitiveReduction()
w.init = true
}
w.Unlock()
uid, err := uuid.NewRandom()
if err != nil {
return "", err
}
eid := uid.String()
stepStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("steps", eid))
workflowStore := store.NewNamespaceStore(w.opts.Store, filepath.Join("workflows", eid))
options := NewExecuteOptions(opts...)
steps, err := w.getSteps(options.Start, options.Reverse)
if err != nil {
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
return "", err
}
var wg sync.WaitGroup
cherr := make(chan error, 1)
chstatus := make(chan Status, 1)
nctx, cancel := context.WithCancel(ctx)
defer cancel()
nopts := make([]ExecuteOption, 0, len(opts)+5)
nopts = append(nopts,
ExecuteClient(w.opts.Client),
ExecuteTracer(w.opts.Tracer),
ExecuteLogger(w.opts.Logger),
ExecuteMeter(w.opts.Meter),
)
nopts = append(nopts, opts...)
done := make(chan struct{})
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
return eid, werr
}
for idx := range steps {
for nidx := range steps[idx] {
cstep := steps[idx][nidx]
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusPending.String())}); werr != nil {
return eid, werr
}
}
}
go func() {
for idx := range steps {
for nidx := range steps[idx] {
wStatus := &codec.Frame{}
if werr := workflowStore.Read(w.opts.Context, "status", wStatus); werr != nil {
cherr <- werr
return
}
if status := StringStatus[string(wStatus.Data)]; status != StatusRunning {
chstatus <- status
return
}
if w.opts.Logger.V(logger.TraceLevel) {
w.opts.Logger.Tracef(nctx, "will be executed %v", steps[idx][nidx])
}
cstep := steps[idx][nidx]
if len(cstep.Requires()) == 0 {
wg.Add(1)
go func(step Step) {
defer wg.Done()
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "req"), req); werr != nil {
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr
return
}
rsp, serr := step.Execute(nctx, req, nopts...)
if serr != nil {
step.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
} else {
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "rsp"), rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(step.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
}
}(cstep)
wg.Wait()
} else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "req"), req); werr != nil {
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusRunning.String())}); werr != nil {
cherr <- werr
return
}
rsp, serr := cstep.Execute(nctx, req, nopts...)
if serr != nil {
cstep.SetStatus(StatusFailure)
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), serr); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil && w.opts.Logger.V(logger.ErrorLevel) {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
}
cherr <- serr
return
} else {
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "rsp"), rsp); werr != nil {
w.opts.Logger.Errorf(ctx, "store write error: %v", werr)
cherr <- werr
return
}
if werr := stepStore.Write(ctx, filepath.Join(cstep.ID(), "status"), &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
cherr <- werr
return
}
}
}
}
}
close(done)
}()
if options.Async {
return eid, nil
}
logger.Tracef(ctx, "wait for finish or error")
select {
case <-nctx.Done():
err = nctx.Err()
case cerr := <-cherr:
err = cerr
case <-done:
close(cherr)
case <-chstatus:
close(chstatus)
return uid.String(), nil
}
switch {
case nctx.Err() != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusAborted.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
break
case err == nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusSuccess.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
break
case err != nil:
if werr := workflowStore.Write(w.opts.Context, "status", &codec.Frame{Data: []byte(StatusFailure.String())}); werr != nil {
w.opts.Logger.Errorf(w.opts.Context, "store error: %v", werr)
}
break
}
return uid.String(), err
}
func NewFlow(opts ...Option) Flow {
options := NewOptions(opts...)
return &microFlow{opts: options}
}
func (f *microFlow) Options() Options {
return f.opts
}
func (f *microFlow) Init(opts ...Option) error {
for _, o := range opts {
o(&f.opts)
}
if err := f.opts.Client.Init(); err != nil {
return err
}
if err := f.opts.Tracer.Init(); err != nil {
return err
}
if err := f.opts.Logger.Init(); err != nil {
return err
}
if err := f.opts.Meter.Init(); err != nil {
return err
}
if err := f.opts.Store.Init(); err != nil {
return err
}
return nil
}
func (f *microFlow) WorkflowList(ctx context.Context) ([]Workflow, error) {
return nil, nil
}
func (f *microFlow) WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error) {
w := &microWorkflow{opts: f.opts, id: id, g: &dag.AcyclicGraph{}, steps: make(map[string]Step, len(steps))}
for _, s := range steps {
w.steps[s.String()] = s
w.g.Add(s)
}
for _, dst := range steps {
for _, req := range dst.Requires() {
src, ok := w.steps[req]
if !ok {
return nil, ErrStepNotExists
}
w.g.Connect(dag.BasicEdge(src, dst))
}
}
if err := w.g.Validate(); err != nil {
return nil, err
}
w.g.TransitiveReduction()
w.init = true
return w, nil
}
func (f *microFlow) WorkflowRemove(ctx context.Context, id string) error {
return nil
}
func (f *microFlow) WorkflowSave(ctx context.Context, w Workflow) error {
return nil
}
func (f *microFlow) WorkflowLoad(ctx context.Context, id string) (Workflow, error) {
return nil, nil
}
type microCallStep struct {
opts StepOptions
service string
method string
rsp *Message
req *Message
status Status
}
func (s *microCallStep) Request() *Message {
return s.req
}
func (s *microCallStep) Response() *Message {
return s.rsp
}
func (s *microCallStep) ID() string {
return s.String()
}
func (s *microCallStep) Options() StepOptions {
return s.opts
}
func (s *microCallStep) Endpoint() string {
return s.method
}
func (s *microCallStep) Requires() []string {
return s.opts.Requires
}
func (s *microCallStep) Require(steps ...Step) error {
for _, step := range steps {
s.opts.Requires = append(s.opts.Requires, step.String())
}
return nil
}
func (s *microCallStep) String() string {
if s.opts.ID != "" {
return s.opts.ID
}
return fmt.Sprintf("%s.%s", s.service, s.method)
}
func (s *microCallStep) Name() string {
return s.String()
}
func (s *microCallStep) Hashcode() interface{} {
return s.String()
}
func (s *microCallStep) GetStatus() Status {
return s.status
}
func (s *microCallStep) SetStatus(status Status) {
s.status = status
}
func (s *microCallStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
options := NewExecuteOptions(opts...)
if options.Client == nil {
return nil, ErrMissingClient
}
rsp := &codec.Frame{}
copts := []client.CallOption{client.WithRetries(0)}
if options.Timeout > 0 {
copts = append(copts, client.WithRequestTimeout(options.Timeout), client.WithDialTimeout(options.Timeout))
}
nctx := metadata.NewOutgoingContext(ctx, req.Header)
err := options.Client.Call(nctx, options.Client.NewRequest(s.service, s.method, &codec.Frame{Data: req.Body}), rsp)
if err != nil {
return nil, err
}
md, _ := metadata.FromOutgoingContext(nctx)
return &Message{Header: md, Body: rsp.Data}, err
}
type microPublishStep struct {
opts StepOptions
topic string
req *Message
rsp *Message
status Status
}
func (s *microPublishStep) Request() *Message {
return s.req
}
func (s *microPublishStep) Response() *Message {
return s.rsp
}
func (s *microPublishStep) ID() string {
return s.String()
}
func (s *microPublishStep) Options() StepOptions {
return s.opts
}
func (s *microPublishStep) Endpoint() string {
return s.topic
}
func (s *microPublishStep) Requires() []string {
return s.opts.Requires
}
func (s *microPublishStep) Require(steps ...Step) error {
for _, step := range steps {
s.opts.Requires = append(s.opts.Requires, step.String())
}
return nil
}
func (s *microPublishStep) String() string {
if s.opts.ID != "" {
return s.opts.ID
}
return fmt.Sprintf("%s", s.topic)
}
func (s *microPublishStep) Name() string {
return s.String()
}
func (s *microPublishStep) Hashcode() interface{} {
return s.String()
}
func (s *microPublishStep) GetStatus() Status {
return s.status
}
func (s *microPublishStep) SetStatus(status Status) {
s.status = status
}
func (s *microPublishStep) Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error) {
return nil, nil
}
func NewCallStep(service string, name string, method string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microCallStep{service: service, method: name + "." + method, opts: options}
}
func NewPublishStep(topic string, opts ...StepOption) Step {
options := NewStepOptions(opts...)
return &microPublishStep{topic: topic, opts: options}
}

View File

@@ -1,7 +1,152 @@
// Package flow is an interface used for saga pattern messaging
// Package flow is an interface used for saga pattern microservice workflow
package flow
type Step interface {
// Endpoint returns service_name.service_method
Endpoint() string
import (
"context"
"errors"
"sync"
"sync/atomic"
"github.com/unistack-org/micro/v3/metadata"
)
var (
ErrStepNotExists = errors.New("step not exists")
ErrMissingClient = errors.New("client not set")
)
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can be used to delay decoding or precompute a encoding.
type RawMessage []byte
// MarshalJSON returns m as the JSON encoding of m.
func (m *RawMessage) MarshalJSON() ([]byte, error) {
if m == nil {
return []byte("null"), nil
}
return *m, nil
}
// UnmarshalJSON sets *m to a copy of data.
func (m *RawMessage) UnmarshalJSON(data []byte) error {
if m == nil {
return errors.New("RawMessage UnmarshalJSON on nil pointer")
}
*m = append((*m)[0:0], data...)
return nil
}
type Message struct {
Header metadata.Metadata
Body RawMessage
}
// Step represents dedicated workflow step
type Step interface {
// ID returns step id
ID() string
// Endpoint returns rpc endpoint service_name.service_method or broker topic
Endpoint() string
// Execute step run
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (*Message, error)
// Requires returns dependent steps
Requires() []string
// Options returns step options
Options() StepOptions
// Require add required steps
Require(steps ...Step) error
// String
String() string
// GetStatus returns step status
GetStatus() Status
// SetStatus sets the step status
SetStatus(Status)
// Request returns step request message
Request() *Message
// Response returns step response message
Response() *Message
}
type Status int
func (status Status) String() string {
return StatusString[status]
}
const (
StatusPending Status = iota
StatusRunning
StatusFailure
StatusSuccess
StatusAborted
StatusSuspend
)
var (
StatusString = map[Status]string{
StatusPending: "StatusPending",
StatusRunning: "StatusRunning",
StatusFailure: "StatusFailure",
StatusSuccess: "StatusSuccess",
StatusAborted: "StatusAborted",
StatusSuspend: "StatusSuspend",
}
StringStatus = map[string]Status{
"StatusPending": StatusPending,
"StatusRunning": StatusRunning,
"StatusFailure": StatusFailure,
"StatusSuccess": StatusSuccess,
"StatusAborted": StatusAborted,
"StatusSuspend": StatusSuspend,
}
)
// Workflow contains all steps to execute
type Workflow interface {
// ID returns id of the workflow
ID() string
// Execute workflow with args, return execution id and error
Execute(ctx context.Context, req *Message, opts ...ExecuteOption) (string, error)
// RemoveSteps remove steps from workflow
RemoveSteps(steps ...Step) error
// AppendSteps append steps to workflow
AppendSteps(steps ...Step) error
// Status returns workflow status
Status() Status
// Steps returns steps slice where parallel steps returned on the same level
Steps() ([][]Step, error)
// Suspend suspends execution
Suspend(ctx context.Context, eid string) error
// Resume resumes execution
Resume(ctx context.Context, eid string) error
// Abort abort execution
Abort(ctx context.Context, eid string) error
}
// Flow the base interface to interact with workflows
type Flow interface {
// Options returns options
Options() Options
// Init initialize
Init(...Option) error
// WorkflowCreate creates new workflow with specific id and steps
WorkflowCreate(ctx context.Context, id string, steps ...Step) (Workflow, error)
// WorkflowSave saves workflow
WorkflowSave(ctx context.Context, w Workflow) error
// WorkflowLoad loads workflow with specific id
WorkflowLoad(ctx context.Context, id string) (Workflow, error)
// WorkflowList lists all workflows
WorkflowList(ctx context.Context) ([]Workflow, error)
}
var (
flowMu sync.Mutex
atomicSteps atomic.Value
)
func RegisterStep(step Step) {
flowMu.Lock()
steps, _ := atomicSteps.Load().([]Step)
atomicSteps.Store(append(steps, step))
flowMu.Unlock()
}

230
flow/options.go Normal file
View File

@@ -0,0 +1,230 @@
package flow
import (
"context"
"time"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/meter"
"github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer"
)
// Option func
type Option func(*Options)
// Options server struct
type Options struct {
// Context holds the external options and can be used for flow shutdown
Context context.Context
// Client holds the client.Client
Client client.Client
// Tracer holds the tracer
Tracer tracer.Tracer
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Store used for intermediate results
Store store.Store
}
// NewOptions returns new options struct with default or passed values
func NewOptions(opts ...Option) Options {
options := Options{
Context: context.Background(),
Logger: logger.DefaultLogger,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Client: client.DefaultClient,
}
for _, o := range opts {
o(&options)
}
return options
}
// Logger sets the logger option
func Logger(l logger.Logger) Option {
return func(o *Options) {
o.Logger = l
}
}
// Meter sets the meter option
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
// Client to use for sync/async communication
func Client(c client.Client) Option {
return func(o *Options) {
o.Client = c
}
}
// Context specifies a context for the service.
// Can be used to signal shutdown of the flow
// Can be used for extra option values.
func Context(ctx context.Context) Option {
return func(o *Options) {
o.Context = ctx
}
}
// Tracer mechanism for distributed tracking
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
o.Tracer = t
}
}
// Store used for intermediate results
func Store(s store.Store) Option {
return func(o *Options) {
o.Store = s
}
}
// WorflowOption signature
type WorkflowOption func(*WorkflowOptions)
// WorkflowOptions holds workflow options
type WorkflowOptions struct {
ID string
Context context.Context
}
// WorkflowID set workflow id
func WorkflowID(id string) WorkflowOption {
return func(o *WorkflowOptions) {
o.ID = id
}
}
type ExecuteOptions struct {
// Client holds the client.Client
Client client.Client
// Tracer holds the tracer
Tracer tracer.Tracer
// Logger holds the logger
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Context can be used to abort execution or pass additional opts
Context context.Context
// Start step
Start string
// Reverse execution
Reverse bool
// Timeout for execution
Timeout time.Duration
// Async enables async execution
Async bool
}
type ExecuteOption func(*ExecuteOptions)
func ExecuteClient(c client.Client) ExecuteOption {
return func(o *ExecuteOptions) {
o.Client = c
}
}
func ExecuteTracer(t tracer.Tracer) ExecuteOption {
return func(o *ExecuteOptions) {
o.Tracer = t
}
}
func ExecuteLogger(l logger.Logger) ExecuteOption {
return func(o *ExecuteOptions) {
o.Logger = l
}
}
func ExecuteMeter(m meter.Meter) ExecuteOption {
return func(o *ExecuteOptions) {
o.Meter = m
}
}
func ExecuteContext(ctx context.Context) ExecuteOption {
return func(o *ExecuteOptions) {
o.Context = ctx
}
}
func ExecuteReverse(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Reverse = b
}
}
func ExecuteTimeout(td time.Duration) ExecuteOption {
return func(o *ExecuteOptions) {
o.Timeout = td
}
}
func ExecuteAsync(b bool) ExecuteOption {
return func(o *ExecuteOptions) {
o.Async = b
}
}
func NewExecuteOptions(opts ...ExecuteOption) ExecuteOptions {
options := ExecuteOptions{
Client: client.DefaultClient,
Logger: logger.DefaultLogger,
Tracer: tracer.DefaultTracer,
Meter: meter.DefaultMeter,
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
type StepOptions struct {
ID string
Context context.Context
Requires []string
Fallback string
}
type StepOption func(*StepOptions)
func NewStepOptions(opts ...StepOption) StepOptions {
options := StepOptions{
Context: context.Background(),
}
for _, o := range opts {
o(&options)
}
return options
}
func StepID(id string) StepOption {
return func(o *StepOptions) {
o.ID = id
}
}
func StepRequires(steps ...string) StepOption {
return func(o *StepOptions) {
o.Requires = steps
}
}
func StepFallback(step string) StepOption {
return func(o *StepOptions) {
o.Fallback = step
}
}

View File

@@ -1,3 +0,0 @@
package micro
//go:generate ./.github/generate.sh

4
go.mod
View File

@@ -5,9 +5,9 @@ go 1.16
require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ef-ds/deque v1.0.4
github.com/google/uuid v1.2.0
github.com/google/uuid v1.3.0
github.com/imdario/mergo v0.3.12
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056
golang.org/x/net v0.0.0-20210510120150-4163338589ed
)

12
go.sum
View File

@@ -2,20 +2,20 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI=
github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34 h1:vBfVmA5mZhsQa2jr1FOL9nfA37N/jnbBmi5XUfviVTI=
github.com/silas/dag v0.0.0-20210121180416-41cf55125c34/go.mod h1:7RTUFBdIRC9nZ7/3RyRNH1bdqIShrDejd1YbLwgPS+I=
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056 h1:sANdAef76Ioam9aQUUdcAqricwY/WUaMc4+7LY4eGg8=
golang.org/x/net v0.0.0-20210324205630-d1beb07c2056/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=

View File

@@ -21,9 +21,11 @@ func init() {
}
type defaultLogger struct {
opts Options
enc *json.Encoder
opts Options
sync.RWMutex
logFunc LogFunc
logfFunc LogfFunc
}
// Init(opts...) should only overwrite provided options
@@ -33,6 +35,15 @@ func (l *defaultLogger) Init(opts ...Option) error {
o(&l.opts)
}
l.enc = json.NewEncoder(l.opts.Out)
l.logFunc = l.Log
l.logfFunc = l.Logf
// wrap the Log func
for i := len(l.opts.Wrappers); i > 0; i-- {
l.logFunc = l.opts.Wrappers[i-1].Log(l.logFunc)
l.logfFunc = l.opts.Wrappers[i-1].Logf(l.logfFunc)
}
l.Unlock()
return nil
}
@@ -121,27 +132,27 @@ func (l *defaultLogger) Fatal(ctx context.Context, args ...interface{}) {
}
func (l *defaultLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, InfoLevel, msg, args...)
l.logfFunc(ctx, InfoLevel, msg, args...)
}
func (l *defaultLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, ErrorLevel, msg, args...)
l.logfFunc(ctx, ErrorLevel, msg, args...)
}
func (l *defaultLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, DebugLevel, msg, args...)
l.logfFunc(ctx, DebugLevel, msg, args...)
}
func (l *defaultLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, WarnLevel, msg, args...)
l.logfFunc(ctx, WarnLevel, msg, args...)
}
func (l *defaultLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, TraceLevel, msg, args...)
l.logfFunc(ctx, TraceLevel, msg, args...)
}
func (l *defaultLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
l.Logf(ctx, FatalLevel, msg, args...)
l.logfFunc(ctx, FatalLevel, msg, args...)
os.Exit(1)
}

View File

@@ -1,13 +1,15 @@
package logger
import (
"bytes"
"context"
"testing"
)
func TestLogger(t *testing.T) {
ctx := context.TODO()
l := NewLogger(WithLevel(TraceLevel))
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
@@ -15,4 +17,52 @@ func TestLogger(t *testing.T) {
l.Warn(ctx, "warn_msg1")
l.Fields(map[string]interface{}{"error": "test"}).Info(ctx, "error message")
l.Warn(ctx, "first", " ", "second")
if !bytes.Contains(buf.Bytes(), []byte(`"level":"trace","msg":"trace_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"warn","msg":"warn_msg1"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"error":"test","level":"info","msg":"error message"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"level":"warn","msg":"first second"`)) {
t.Fatalf("logger error, buf %s", buf.Bytes())
}
}
func TestLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(WithLevel(TraceLevel), WithOutput(buf))
if err := l.Init(WrapLogger(NewOmitWrapper())); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}
func TestOmitLoggerWrapper(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewOmitLogger(NewLogger(WithLevel(TraceLevel), WithOutput(buf)))
if err := l.Init(); err != nil {
t.Fatal(err)
}
type secret struct {
Name string
Passw string `logger:"omit"`
}
s := &secret{Name: "name", Passw: "secret"}
l.Errorf(ctx, "test %#+v", s)
if !bytes.Contains(buf.Bytes(), []byte(`logger.secret{Name:\"name\", Passw:\"\"}"`)) {
t.Fatalf("omit not works, struct: %v, output: %s", s, buf.Bytes())
}
}

View File

@@ -23,6 +23,8 @@ type Options struct {
CallerSkipCount int
// The logging level the logger should log
Level Level
// Wrappers logger wrapper that called before actual Log/Logf function
Wrappers []Wrapper
}
// NewOptions creates new options struct
@@ -76,8 +78,15 @@ func WithContext(ctx context.Context) Option {
}
// WithName sets the name
func withName(n string) Option {
func WithName(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// WrapLogger adds a logger Wrapper to a list of options passed into the logger
func WrapLogger(w Wrapper) Option {
return func(o *Options) {
o.Wrappers = append(o.Wrappers, w)
}
}

154
logger/wrapper.go Normal file
View File

@@ -0,0 +1,154 @@
package logger
import (
"context"
"reflect"
rutil "github.com/unistack-org/micro/v3/util/reflect"
)
// LogFunc function used for Log method
type LogFunc func(ctx context.Context, level Level, args ...interface{})
// LogfFunc function used for Logf method
type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
type Wrapper interface {
// Log logs message with needed level
Log(LogFunc) LogFunc
// Logf logs message with needed level
Logf(LogfFunc) LogfFunc
}
var (
_ Logger = &OmitLogger{}
)
type OmitLogger struct {
l Logger
}
func NewOmitLogger(l Logger) Logger {
return &OmitLogger{l: l}
}
func (w *OmitLogger) Init(opts ...Option) error {
return w.l.Init(append(opts, WrapLogger(NewOmitWrapper()))...)
}
func (w *OmitLogger) V(level Level) bool {
return w.l.V(level)
}
func (w *OmitLogger) Options() Options {
return w.l.Options()
}
func (w *OmitLogger) Fields(fields map[string]interface{}) Logger {
return w.l.Fields(fields)
}
func (w *OmitLogger) Info(ctx context.Context, args ...interface{}) {
w.l.Info(ctx, args...)
}
func (w *OmitLogger) Trace(ctx context.Context, args ...interface{}) {
w.l.Trace(ctx, args...)
}
func (w *OmitLogger) Debug(ctx context.Context, args ...interface{}) {
w.l.Debug(ctx, args...)
}
func (w *OmitLogger) Warn(ctx context.Context, args ...interface{}) {
w.l.Warn(ctx, args...)
}
func (w *OmitLogger) Error(ctx context.Context, args ...interface{}) {
w.l.Error(ctx, args...)
}
func (w *OmitLogger) Fatal(ctx context.Context, args ...interface{}) {
w.l.Fatal(ctx, args...)
}
func (w *OmitLogger) Infof(ctx context.Context, msg string, args ...interface{}) {
w.l.Infof(ctx, msg, args...)
}
func (w *OmitLogger) Tracef(ctx context.Context, msg string, args ...interface{}) {
w.l.Tracef(ctx, msg, args...)
}
func (w *OmitLogger) Debugf(ctx context.Context, msg string, args ...interface{}) {
w.l.Debugf(ctx, msg, args...)
}
func (w *OmitLogger) Warnf(ctx context.Context, msg string, args ...interface{}) {
w.l.Warnf(ctx, msg, args...)
}
func (w *OmitLogger) Errorf(ctx context.Context, msg string, args ...interface{}) {
w.l.Errorf(ctx, msg, args...)
}
func (w *OmitLogger) Fatalf(ctx context.Context, msg string, args ...interface{}) {
w.l.Fatalf(ctx, msg, args...)
}
func (w *OmitLogger) Log(ctx context.Context, level Level, args ...interface{}) {
w.l.Log(ctx, level, args...)
}
func (w *OmitLogger) Logf(ctx context.Context, level Level, msg string, args ...interface{}) {
w.l.Logf(ctx, level, msg, args...)
}
func (w *OmitLogger) String() string {
return w.l.String()
}
type OmitWrapper struct{}
func NewOmitWrapper() Wrapper {
return &OmitWrapper{}
}
func getArgs(args []interface{}) []interface{} {
nargs := make([]interface{}, 0, len(args))
var err error
for _, arg := range args {
val := reflect.ValueOf(arg)
switch val.Kind() {
case reflect.Ptr:
val = val.Elem()
}
narg := arg
if val.Kind() == reflect.Struct {
if narg, err = rutil.Zero(arg); err == nil {
rutil.CopyDefaults(narg, arg)
if flds, ferr := rutil.StructFields(narg); ferr == nil {
for _, fld := range flds {
if tv, ok := fld.Field.Tag.Lookup("logger"); ok && tv == "omit" {
fld.Value.Set(reflect.Zero(fld.Value.Type()))
}
}
}
}
}
nargs = append(nargs, narg)
}
return nargs
}
func (w *OmitWrapper) Log(fn LogFunc) LogFunc {
return func(ctx context.Context, level Level, args ...interface{}) {
fn(ctx, level, getArgs(args)...)
}
}
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
}

View File

@@ -70,21 +70,23 @@ type lWrapper struct {
opts Options
}
type ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
type ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
type ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
type ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
type ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
type ServerSubscriberObserver func(context.Context, server.Message, error) []string
type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, error) []string
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, error) []string
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, error) []string
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, error) []string
ServerHandlerObserver func(context.Context, server.Request, interface{}, error) []string
ServerSubscriberObserver func(context.Context, server.Message, error) []string
)
// Options struct for wrapper
type Options struct {
// Logger that used for log
Logger logger.Logger
// Level for logger
Level logger.Level
// Enabled flag
Enabled bool
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// ClientCallObservers funcs
ClientCallObservers []ClientCallObserver
// ClientStreamObservers funcs
@@ -93,12 +95,12 @@ type Options struct {
ClientPublishObservers []ClientPublishObserver
// ClientCallFuncObservers funcs
ClientCallFuncObservers []ClientCallFuncObserver
// ServerHandlerObservers funcs
ServerHandlerObservers []ServerHandlerObserver
// ServerSubscriberObservers funcs
ServerSubscriberObservers []ServerSubscriberObserver
// SkipEndpoints
SkipEndpoints []string
// Level for logger
Level logger.Level
// Enabled flag
Enabled bool
}
// Option func signature
@@ -213,7 +215,7 @@ func (l *lWrapper) Call(ctx context.Context, req client.Request, rsp interface{}
for _, o := range l.opts.ClientCallObservers {
labels = append(labels, o(ctx, req, rsp, opts, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -240,7 +242,7 @@ func (l *lWrapper) Stream(ctx context.Context, req client.Request, opts ...clien
for _, o := range l.opts.ClientStreamObservers {
labels = append(labels, o(ctx, req, opts, stream, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -267,7 +269,7 @@ func (l *lWrapper) Publish(ctx context.Context, msg client.Message, opts ...clie
for _, o := range l.opts.ClientPublishObservers {
labels = append(labels, o(ctx, msg, opts, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -294,7 +296,7 @@ func (l *lWrapper) ServerHandler(ctx context.Context, req server.Request, rsp in
for _, o := range l.opts.ServerHandlerObservers {
labels = append(labels, o(ctx, req, rsp, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -321,7 +323,7 @@ func (l *lWrapper) ServerSubscriber(ctx context.Context, msg server.Message) err
for _, o := range l.opts.ServerSubscriberObservers {
labels = append(labels, o(ctx, msg, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}
@@ -372,7 +374,7 @@ func (l *lWrapper) ClientCallFunc(ctx context.Context, addr string, req client.R
for _, o := range l.opts.ClientCallFuncObservers {
labels = append(labels, o(ctx, addr, req, rsp, opts, err)...)
}
fields := make(map[string]interface{}, int(len(labels)/2))
fields := make(map[string]interface{}, len(labels)/2)
for i := 0; i < len(labels); i += 2 {
fields[labels[i]] = labels[i+1]
}

View File

@@ -5,9 +5,11 @@ import (
"context"
)
type mdIncomingKey struct{}
type mdOutgoingKey struct{}
type mdKey struct{}
type (
mdIncomingKey struct{}
mdOutgoingKey struct{}
mdKey struct{}
)
// FromIncomingContext returns metadata from incoming ctx
// returned metadata shoud not be modified or race condition happens

View File

@@ -6,10 +6,8 @@ import (
"sort"
)
var (
// HeaderPrefix for all headers passed
HeaderPrefix = "Micro-"
)
// HeaderPrefix for all headers passed
var HeaderPrefix = "Micro-"
// Metadata is our way of representing request headers internally.
// They're used at the RPC level and translate back and forth
@@ -20,10 +18,8 @@ type rawMetadata struct {
md Metadata
}
var (
// defaultMetadataSize used when need to init new Metadata
defaultMetadataSize = 2
)
// defaultMetadataSize used when need to init new Metadata
var defaultMetadataSize = 2
// Iterator used to iterate over metadata with order
type Iterator struct {

View File

@@ -76,7 +76,7 @@ func TestIterator(t *testing.T) {
var k, v string
for iter.Next(&k, &v) {
//fmt.Printf("k: %s, v: %s\n", k, v)
// fmt.Printf("k: %s, v: %s\n", k, v)
}
}
@@ -102,7 +102,6 @@ func TestMedataCanonicalKey(t *testing.T) {
} else if v != "12345" {
t.Fatalf("invalid metadata value: %s != %s", "12345", v)
}
}
func TestMetadataSet(t *testing.T) {
@@ -130,7 +129,6 @@ func TestMetadataDelete(t *testing.T) {
if ok {
t.Fatal("key Baz not deleted")
}
}
func TestNilContext(t *testing.T) {

View File

@@ -1,33 +1,64 @@
package pb
package handler
import (
"bytes"
"context"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
"github.com/unistack-org/micro/v3/meter"
)
var (
// guard to fail early
_ MeterServer = &handler{}
)
// guard to fail early
var _ MeterServer = &Handler{}
type Empty struct{}
type handler struct {
meter meter.Meter
opts []meter.Option
type Handler struct {
opts Options
}
func NewHandler(meter meter.Meter, opts ...meter.Option) *handler {
return &handler{meter: meter, opts: opts}
type Option func(*Options)
type Options struct {
Meter meter.Meter
Name string
MeterOptions []meter.Option
}
func (h *handler) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
func Meter(m meter.Meter) Option {
return func(o *Options) {
o.Meter = m
}
}
func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}
func MeterOptions(opts ...meter.Option) Option {
return func(o *Options) {
o.MeterOptions = append(o.MeterOptions, opts...)
}
}
func NewOptions(opts ...Option) Options {
options := Options{Meter: meter.DefaultMeter}
for _, o := range opts {
o(&options)
}
return options
}
func NewHandler(opts ...Option) *Handler {
options := NewOptions(opts...)
return &Handler{opts: options}
}
func (h *Handler) Metrics(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
buf := bytes.NewBuffer(nil)
if err := h.meter.Write(buf, h.opts...); err != nil {
return err
if err := h.opts.Meter.Write(buf, h.opts.MeterOptions...); err != nil {
return errors.InternalServerError(h.opts.Name, "%v", err)
}
rsp.Data = buf.Bytes()

View File

@@ -1,7 +1,7 @@
syntax = "proto3";
package meter;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;pb";
package micro.meter.handler;
option go_package = "github.com/unistack-org/micro/v3/meter/handler;handler";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
@@ -17,7 +17,7 @@ service Meter {
description: "Error response";
schema: {
json_schema: {
ref: "Empty";
ref: "micro.codec.Frame";
}
}
}

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-micro
// source: handler.proto
package pb
package handler
import (
context "context"

View File

@@ -1,6 +1,6 @@
// Code generated by protoc-gen-micro
// source: handler.proto
package pb
package handler
import (
context "context"

View File

@@ -3,8 +3,9 @@ package meter
import (
"io"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
@@ -29,13 +30,13 @@ var (
type Meter interface {
Name() string
Init(opts ...Option) error
Counter(name string, opts ...Option) Counter
FloatCounter(name string, opts ...Option) FloatCounter
Gauge(name string, fn func() float64, opts ...Option) Gauge
Counter(name string, labels ...string) Counter
FloatCounter(name string, labels ...string) FloatCounter
Gauge(name string, fn func() float64, labels ...string) Gauge
Set(opts ...Option) Meter
Histogram(name string, opts ...Option) Histogram
Summary(name string, opts ...Option) Summary
SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary
Histogram(name string, labels ...string) Histogram
Summary(name string, labels ...string) Summary
SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary
Write(w io.Writer, opts ...Option) error
Options() Options
String() string
@@ -77,36 +78,60 @@ type Summary interface {
UpdateDuration(time.Time)
}
// sort labels alphabeticaly by label name
type byKey []string
func (k byKey) Len() int { return len(k) / 2 }
func (k byKey) Less(i, j int) bool { return k[i*2] < k[j*2] }
func (k byKey) Swap(i, j int) {
k[i*2], k[i*2+1], k[j*2], k[j*2+1] = k[j*2], k[j*2+1], k[i*2], k[i*2+1]
k[i*2], k[j*2] = k[j*2], k[i*2]
k[i*2+1], k[j*2+1] = k[j*2+1], k[i*2+1]
}
func Sort(slice *[]string) {
bk := byKey(*slice)
if bk.Len() <= 1 {
return
// BuildLables used to sort labels and delete duplicates.
// Last value wins in case of duplicate label keys.
func BuildLabels(labels ...string) []string {
if len(labels)%2 == 1 {
labels = labels[:len(labels)-1]
}
sort.Sort(bk)
v := reflect.ValueOf(slice).Elem()
cnt := 0
key := 0
val := 1
for key < v.Len() {
if len(bk) > key+2 && bk[key] == bk[key+2] {
key += 2
val += 2
continue
}
v.Index(cnt).Set(v.Index(key))
cnt++
v.Index(cnt).Set(v.Index(val))
cnt++
key += 2
val += 2
}
v.SetLen(cnt)
sort.Sort(byKey(labels))
return labels
}
// BuildName used to combine metric with labels.
// If labels count is odd, drop last element
func BuildName(name string, labels ...string) string {
if len(labels)%2 == 1 {
labels = labels[:len(labels)-1]
}
sort.Sort(byKey(labels))
idx := 0
for {
if labels[idx] == labels[idx+2] {
copy(labels[idx:], labels[idx+2:])
labels = labels[:len(labels)-2]
} else {
idx += 2
}
if idx+2 >= len(labels) {
break
}
}
var b strings.Builder
_, _ = b.WriteString(name)
_, _ = b.WriteRune('{')
for idx := 0; idx < len(labels); idx += 2 {
if idx > 0 {
_, _ = b.WriteRune(',')
}
_, _ = b.WriteString(labels[idx])
_, _ = b.WriteString(`=`)
_, _ = b.WriteString(strconv.Quote(labels[idx+1]))
}
_, _ = b.WriteRune('}')
return b.String()
}

View File

@@ -10,15 +10,57 @@ func TestNoopMeter(t *testing.T) {
t.Fatalf("invalid options parsing: %v", m.Options())
}
cnt := m.Counter("counter", Labels("server", "noop"))
cnt := m.Counter("counter", "server", "noop")
cnt.Inc()
}
func TestLabelsSort(t *testing.T) {
ls := []string{"server", "http", "register", "mdns", "broker", "broker1", "broker", "broker2", "server", "tcp"}
Sort(&ls)
func testEq(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
if ls[0] != "broker" || ls[1] != "broker2" {
t.Fatalf("sort error: %v", ls)
func TestBuildLabels(t *testing.T) {
type testData struct {
src []string
dst []string
}
data := []testData{
testData{
src: []string{"zerolabel", "value3", "firstlabel", "value2"},
dst: []string{"firstlabel", "value2", "zerolabel", "value3"},
},
}
for _, d := range data {
if !testEq(d.dst, BuildLabels(d.src...)) {
t.Fatalf("slices not properly sorted: %v %v", d.dst, d.src)
}
}
}
func TestBuildName(t *testing.T) {
data := map[string][]string{
`my_metric{firstlabel="value2",zerolabel="value3"}`: []string{
"my_metric",
"zerolabel", "value3", "firstlabel", "value2",
},
`my_metric{broker="broker2",register="mdns",server="tcp"}`: []string{
"my_metric",
"broker", "broker1", "broker", "broker2", "server", "http", "server", "tcp", "register", "mdns",
},
}
for e, d := range data {
if x := BuildName(d[0], d[1:]...); x != e {
t.Fatalf("expect: %s, result: %s", e, x)
}
}
}

View File

@@ -28,57 +28,33 @@ func (r *noopMeter) Init(opts ...Option) error {
}
// Counter implements the Meter interface
func (r *noopMeter) Counter(name string, opts ...Option) Counter {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopCounter{labels: options.Labels}
func (r *noopMeter) Counter(name string, labels ...string) Counter {
return &noopCounter{labels: labels}
}
// FloatCounter implements the Meter interface
func (r *noopMeter) FloatCounter(name string, opts ...Option) FloatCounter {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopFloatCounter{labels: options.Labels}
func (r *noopMeter) FloatCounter(name string, labels ...string) FloatCounter {
return &noopFloatCounter{labels: labels}
}
// Gauge implements the Meter interface
func (r *noopMeter) Gauge(name string, f func() float64, opts ...Option) Gauge {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopGauge{labels: options.Labels}
func (r *noopMeter) Gauge(name string, f func() float64, labels ...string) Gauge {
return &noopGauge{labels: labels}
}
// Summary implements the Meter interface
func (r *noopMeter) Summary(name string, opts ...Option) Summary {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
func (r *noopMeter) Summary(name string, labels ...string) Summary {
return &noopSummary{labels: labels}
}
// SummaryExt implements the Meter interface
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, opts ...Option) Summary {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopSummary{labels: options.Labels}
func (r *noopMeter) SummaryExt(name string, window time.Duration, quantiles []float64, labels ...string) Summary {
return &noopSummary{labels: labels}
}
// Histogram implements the Meter interface
func (r *noopMeter) Histogram(name string, opts ...Option) Histogram {
options := Options{}
for _, o := range opts {
o(&options)
}
return &noopHistogram{labels: options.Labels}
func (r *noopMeter) Histogram(name string, labels ...string) Histogram {
return &noopHistogram{labels: labels}
}
// Set implements the Meter interface
@@ -111,11 +87,9 @@ type noopCounter struct {
}
func (r *noopCounter) Add(int) {
}
func (r *noopCounter) Dec() {
}
func (r *noopCounter) Get() uint64 {
@@ -123,11 +97,9 @@ func (r *noopCounter) Get() uint64 {
}
func (r *noopCounter) Inc() {
}
func (r *noopCounter) Set(uint64) {
}
type noopFloatCounter struct {
@@ -135,7 +107,6 @@ type noopFloatCounter struct {
}
func (r *noopFloatCounter) Add(float64) {
}
func (r *noopFloatCounter) Get() float64 {
@@ -143,11 +114,9 @@ func (r *noopFloatCounter) Get() float64 {
}
func (r *noopFloatCounter) Set(float64) {
}
func (r *noopFloatCounter) Sub(float64) {
}
type noopGauge struct {
@@ -163,11 +132,9 @@ type noopSummary struct {
}
func (r *noopSummary) Update(float64) {
}
func (r *noopSummary) UpdateDuration(time.Time) {
}
type noopHistogram struct {
@@ -175,15 +142,12 @@ type noopHistogram struct {
}
func (r *noopHistogram) Reset() {
}
func (r *noopHistogram) Update(float64) {
}
func (r *noopHistogram) UpdateDuration(time.Time) {
}
//func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}
// func (r *noopHistogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {}

View File

@@ -90,7 +90,7 @@ func Logger(l logger.Logger) Option {
func Labels(ls ...string) Option {
return func(o *Options) {
o.Labels = ls
o.Labels = append(o.Labels, ls...)
}
}

View File

@@ -11,18 +11,25 @@ import (
)
var (
ClientRequestDurationSeconds = "client_request_duration_seconds"
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total"
ServerRequestDurationSeconds = "server_request_duration_seconds"
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total"
PublishMessageDurationSeconds = "publish_message_duration_seconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total"
ClientRequestDurationSeconds = "client_request_duration_seconds"
ClientRequestLatencyMicroseconds = "client_request_latency_microseconds"
ClientRequestTotal = "client_request_total"
ClientRequestInflight = "client_request_inflight"
ServerRequestDurationSeconds = "server_request_duration_seconds"
ServerRequestLatencyMicroseconds = "server_request_latency_microseconds"
ServerRequestTotal = "server_request_total"
ServerRequestInflight = "server_request_inflight"
PublishMessageDurationSeconds = "publish_message_duration_seconds"
PublishMessageLatencyMicroseconds = "publish_message_latency_microseconds"
PublishMessageTotal = "publish_message_total"
PublishMessageInflight = "publish_message_inflight"
SubscribeMessageDurationSeconds = "subscribe_message_duration_seconds"
SubscribeMessageLatencyMicroseconds = "subscribe_message_latency_microseconds"
SubscribeMessageTotal = "subscribe_message_total"
SubscribeMessageInflight = "subscribe_message_inflight"
labelSuccess = "success"
labelFailure = "failure"
@@ -116,22 +123,25 @@ func (w *wrapper) CallFunc(ctx context.Context, addr string, req client.Request,
return w.callFunc(ctx, addr, req, rsp, opts)
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.callFunc(ctx, addr, req, rsp, opts)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
@@ -144,22 +154,24 @@ func (w *wrapper) Call(ctx context.Context, req client.Request, rsp interface{},
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Call(ctx, req, rsp, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return err
}
@@ -172,22 +184,24 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Inc()
ts := time.Now()
stream, err := w.Client.Stream(ctx, req, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(ClientRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ClientRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ClientRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ClientRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ClientRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ClientRequestTotal, labels...).Inc()
return stream, err
}
@@ -195,22 +209,24 @@ func (w *wrapper) Stream(ctx context.Context, req client.Request, opts ...client
func (w *wrapper) Publish(ctx context.Context, p client.Message, opts ...client.PublishOption) error {
endpoint := p.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Inc()
ts := time.Now()
err := w.Client.Publish(ctx, p, opts...)
te := time.Since(ts)
w.opts.Meter.Counter(PublishMessageInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(PublishMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(PublishMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(PublishMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(PublishMessageTotal, lopts...).Inc()
w.opts.Meter.Counter(PublishMessageTotal, labels...).Inc()
return err
}
@@ -231,22 +247,24 @@ func (w *wrapper) HandlerFunc(fn server.HandlerFunc) server.HandlerFunc {
}
}
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, req, rsp)
te := time.Since(ts)
w.opts.Meter.Counter(ServerRequestInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(ServerRequestDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(ServerRequestLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(ServerRequestDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(ServerRequestTotal, lopts...).Inc()
w.opts.Meter.Counter(ServerRequestTotal, labels...).Inc()
return err
}
@@ -263,22 +281,24 @@ func (w *wrapper) SubscriberFunc(fn server.SubscriberFunc) server.SubscriberFunc
return func(ctx context.Context, msg server.Message) error {
endpoint := msg.Topic()
labels := make([]string, 0, 4)
labels = append(labels, labelEndpoint, endpoint)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Inc()
ts := time.Now()
err := fn(ctx, msg)
te := time.Since(ts)
w.opts.Meter.Counter(SubscribeMessageInflight, labels...).Dec()
lopts := w.opts.lopts
lopts = append(lopts, meter.Labels(labelEndpoint, endpoint))
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, lopts...).Update(float64(te.Seconds()))
w.opts.Meter.Summary(SubscribeMessageLatencyMicroseconds, labels...).Update(te.Seconds())
w.opts.Meter.Histogram(SubscribeMessageDurationSeconds, labels...).Update(te.Seconds())
if err == nil {
lopts = append(lopts, meter.Labels(labelStatus, labelSuccess))
labels = append(labels, labelStatus, labelSuccess)
} else {
lopts = append(lopts, meter.Labels(labelStatus, labelFailure))
labels = append(labels, labelStatus, labelFailure)
}
w.opts.Meter.Counter(SubscribeMessageTotal, lopts...).Inc()
w.opts.Meter.Counter(SubscribeMessageTotal, labels...).Inc()
return err
}

View File

@@ -31,18 +31,18 @@ type memoryClient struct {
}
type memoryListener struct {
topts Options
ctx context.Context
lopts ListenOptions
ctx context.Context
exit chan bool
conn chan *memorySocket
addr string
topts Options
sync.RWMutex
}
type memoryTransport struct {
opts Options
listeners map[string]*memoryListener
opts Options
sync.RWMutex
}

View File

@@ -27,9 +27,9 @@ func TestMemoryTransport(t *testing.T) {
if len(os.Getenv("INTEGRATION_TESTS")) == 0 {
t.Logf("Server Received %s", string(m.Body))
}
if err := sock.Send(&Message{
if cerr := sock.Send(&Message{
Body: []byte(`pong`),
}); err != nil {
}); cerr != nil {
return
}
}
@@ -60,7 +60,6 @@ func TestMemoryTransport(t *testing.T) {
t.Logf("Client Received %s", string(m.Body))
}
}
}
func TestListener(t *testing.T) {

View File

@@ -12,16 +12,16 @@ import (
)
type tunBroker struct {
opts broker.Options
tunnel tunnel.Tunnel
opts broker.Options
}
type tunSubscriber struct {
opts broker.SubscribeOptions
listener tunnel.Listener
handler broker.Handler
closed chan bool
topic string
opts broker.SubscribeOptions
}
type tunEvent struct {
@@ -30,8 +30,10 @@ type tunEvent struct {
}
// used to access tunnel from options context
type tunnelKey struct{}
type tunnelAddr struct{}
type (
tunnelKey struct{}
tunnelAddr struct{}
)
func (t *tunBroker) Init(opts ...broker.Option) error {
for _, o := range opts {

View File

@@ -34,8 +34,8 @@ type Options struct {
Token string
// Name holds the tunnel name
Name string
// Id holds the tunnel id
Id string
// ID holds the tunnel id
ID string
// Address holds the tunnel address
Address string
// Nodes holds the tunnel nodes
@@ -68,10 +68,10 @@ type ListenOptions struct {
Timeout time.Duration
}
// Id sets the tunnel id
func Id(id string) Option {
// ID sets the tunnel id
func ID(id string) Option {
return func(o *Options) {
o.Id = id
o.ID = id
}
}
@@ -164,7 +164,7 @@ func DialWait(b bool) DialOption {
// NewOptions returns router default options with filled values
func NewOptions(opts ...Option) Options {
options := Options{
Id: uuid.New().String(),
ID: uuid.New().String(),
Address: DefaultAddress,
Token: DefaultToken,
Logger: logger.DefaultLogger,

View File

@@ -10,9 +10,8 @@ import (
)
type tunTransport struct {
tunnel tunnel.Tunnel
options transport.Options
tunnel tunnel.Tunnel
}
type tunnelKey struct{}
@@ -88,7 +87,7 @@ func NewTransport(opts ...transport.Option) transport.Transport {
}
// initialise
//t.Init(opts...)
// t.Init(opts...)
return t
}

View File

@@ -9,10 +9,8 @@ import (
"github.com/unistack-org/micro/v3/network/transport"
)
var (
// DefaultTunnel contains default tunnel implementation
DefaultTunnel Tunnel
)
// DefaultTunnel contains default tunnel implementation
var DefaultTunnel Tunnel
const (
// Unicast send over one link

View File

@@ -17,8 +17,6 @@ import (
"github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro/v3/store"
"github.com/unistack-org/micro/v3/tracer"
// "github.com/unistack-org/micro/v3/profiler"
// "github.com/unistack-org/micro/v3/runtime"
)
// Options for micro service
@@ -78,8 +76,8 @@ func NewOptions(opts ...Option) Options {
Meters: []meter.Meter{meter.DefaultMeter},
Configs: []config.Config{config.DefaultConfig},
Stores: []store.Store{store.DefaultStore},
//Runtime runtime.Runtime
//Profile profile.Profile
// Runtime runtime.Runtime
// Profile profile.Profile
}
for _, o := range opts {

View File

@@ -16,10 +16,8 @@ type httpProfile struct {
running bool
}
var (
// DefaultAddress for http profiler
DefaultAddress = ":6060"
)
// DefaultAddress for http profiler
var DefaultAddress = ":6060"
// Start the profiler
func (h *httpProfile) Start() error {

View File

@@ -11,10 +11,8 @@ type Profiler interface {
String() string
}
var (
// DefaultProfiler holds the default profiler
DefaultProfiler Profiler = NewProfiler()
)
// DefaultProfiler holds the default profiler
var DefaultProfiler Profiler = NewProfiler()
// Options holds the options for profiler
type Options struct {

View File

@@ -7,10 +7,8 @@ import (
"github.com/unistack-org/micro/v3/server"
)
var (
// DefaultEndpoint holds default proxy address
DefaultEndpoint = "localhost:9090"
)
// DefaultEndpoint holds default proxy address
var DefaultEndpoint = "localhost:9090"
// Proxy can be used as a proxy server for micro services
type Proxy interface {

View File

@@ -3,7 +3,6 @@ package register
import (
"fmt"
"reflect"
"strings"
"unicode"
"unicode/utf8"
@@ -11,12 +10,12 @@ import (
)
// ExtractValue from reflect.Type from specified depth
func ExtractValue(v reflect.Type, d int) *Value {
func ExtractValue(v reflect.Type, d int) string {
if d == 3 {
return nil
return ""
}
if v == nil {
return nil
return ""
}
if v.Kind() == reflect.Ptr {
@@ -25,7 +24,7 @@ func ExtractValue(v reflect.Type, d int) *Value {
// slices and maps don't have a defined name
if (v.Kind() == reflect.Slice || v.Kind() == reflect.Map) || len(v.Name()) == 0 {
return nil
return ""
}
// get the rune character
@@ -33,58 +32,10 @@ func ExtractValue(v reflect.Type, d int) *Value {
// crude check for is unexported field
if unicode.IsLower(a) {
return nil
return ""
}
arg := &Value{
Name: v.Name(),
Type: v.Name(),
}
switch v.Kind() {
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
val := ExtractValue(f.Type, d+1)
if val == nil {
continue
}
// if we can find a json tag use it
if tags := f.Tag.Get("json"); len(tags) > 0 {
parts := strings.Split(tags, ",")
if parts[0] == "-" || parts[0] == "omitempty" {
continue
}
val.Name = parts[0]
}
// if there's no name default it
if len(val.Name) == 0 {
val.Name = v.Field(i).Name
}
arg.Values = append(arg.Values, val)
}
case reflect.Slice:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
arg.Type = "[]" + p.Name()
case reflect.Map:
p := v.Elem()
if p.Kind() == reflect.Ptr {
p = p.Elem()
}
key := v.Key()
if key.Kind() == reflect.Ptr {
key = key.Elem()
}
arg.Type = fmt.Sprintf("map[%s]%s", key.Name(), p.Name())
}
return arg
return v.Name()
}
// ExtractEndpoint extract *Endpoint from reflect.Method
@@ -116,7 +67,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
request := ExtractValue(reqType, 0)
response := ExtractValue(rspType, 0)
if request == nil || response == nil {
if request == "" || response == "" {
return nil
}
@@ -135,7 +86,7 @@ func ExtractEndpoint(method reflect.Method) *Endpoint {
}
// ExtractSubValue exctact *Value from reflect.Type
func ExtractSubValue(typ reflect.Type) *Value {
func ExtractSubValue(typ reflect.Type) string {
var reqType reflect.Type
switch typ.NumIn() {
case 1:
@@ -145,7 +96,7 @@ func ExtractSubValue(typ reflect.Type) *Value {
case 3:
reqType = typ.In(2)
default:
return nil
return ""
}
return ExtractValue(reqType, 0)
}

View File

@@ -36,28 +36,21 @@ func TestExtractEndpoint(t *testing.T) {
t.Fatalf("Expected handler Test, got %s", endpoints[0].Name)
}
if endpoints[0].Request == nil {
if endpoints[0].Request == "" {
t.Fatal("Expected non nil Request")
}
if endpoints[0].Response == nil {
if endpoints[0].Response == "" {
t.Fatal("Expected non nil Request")
}
if endpoints[0].Request.Name != "TestRequest" {
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request.Name)
if endpoints[0].Request != "TestRequest" {
t.Fatalf("Expected TestRequest got %s", endpoints[0].Request)
}
if endpoints[0].Response.Name != "TestResponse" {
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response.Name)
}
if endpoints[0].Request.Type != "TestRequest" {
t.Fatalf("Expected TestRequest type got %s", endpoints[0].Request.Type)
}
if endpoints[0].Response.Type != "TestResponse" {
t.Fatalf("Expected TestResponse type got %s", endpoints[0].Response.Type)
if endpoints[0].Response != "TestResponse" {
t.Fatalf("Expected TestResponse got %s", endpoints[0].Response)
}
t.Logf("XXX %#+v\n", endpoints[0])
}

View File

@@ -30,10 +30,9 @@ type record struct {
}
type memory struct {
opts Options
// records is a KV map with domain name as the key and a services map as the value
records map[string]services
watchers map[string]*watcher
opts Options
sync.RWMutex
}
@@ -65,7 +64,7 @@ func (m *memory) ttlPrune() {
for id, n := range record.Nodes {
if n.TTL != 0 && time.Since(n.LastSeen) > n.TTL {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.Id, service)
m.opts.Logger.Debugf(m.opts.Context, "Register TTL expired for node %s of service %s", n.ID, service)
}
delete(m.records[domain][service][version].Nodes, id)
}
@@ -162,7 +161,7 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
for _, n := range s.Nodes {
// check if already exists
if _, ok := srvs[s.Name][s.Version].Nodes[n.Id]; ok {
if _, ok := srvs[s.Name][s.Version].Nodes[n.ID]; ok {
continue
}
@@ -177,9 +176,9 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
metadata["domain"] = options.Domain
// add the node
srvs[s.Name][s.Version].Nodes[n.Id] = &node{
srvs[s.Name][s.Version].Nodes[n.ID] = &node{
Node: &Node{
Id: n.Id,
ID: n.ID,
Address: n.Address,
Metadata: metadata,
},
@@ -201,8 +200,8 @@ func (m *memory) Register(ctx context.Context, s *Service, opts ...RegisterOptio
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Updated registration for service: %s, version: %s", s.Name, s.Version)
}
srvs[s.Name][s.Version].Nodes[n.Id].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.Id].LastSeen = time.Now()
srvs[s.Name][s.Version].Nodes[n.ID].TTL = options.TTL
srvs[s.Name][s.Version].Nodes[n.ID].LastSeen = time.Now()
}
}
@@ -242,11 +241,11 @@ func (m *memory) Deregister(ctx context.Context, s *Service, opts ...DeregisterO
// deregister all of the service nodes from this version
for _, n := range s.Nodes {
if _, ok := version.Nodes[n.Id]; ok {
if _, ok := version.Nodes[n.ID]; ok {
if m.opts.Logger.V(logger.DebugLevel) {
m.opts.Logger.Debugf(m.opts.Context, "Register removed node from service: %s, version: %s", s.Name, s.Version)
}
delete(version.Nodes, n.Id)
delete(version.Nodes, n.ID)
}
}
@@ -458,7 +457,7 @@ func serviceToRecord(s *Service, ttl time.Duration) *record {
nodes := make(map[string]*node, len(s.Nodes))
for _, n := range s.Nodes {
nodes[n.Id] = &node{
nodes[n.ID] = &node{
Node: n,
TTL: ttl,
LastSeen: time.Now(),
@@ -490,40 +489,31 @@ func recordToService(r *record, domain string) *Service {
endpoints := make([]*Endpoint, len(r.Endpoints))
for i, e := range r.Endpoints {
request := new(Value)
if e.Request != nil {
*request = *e.Request
}
response := new(Value)
if e.Response != nil {
*response = *e.Response
}
metadata := make(map[string]string, len(e.Metadata))
md := make(map[string]string, len(e.Metadata))
for k, v := range e.Metadata {
metadata[k] = v
md[k] = v
}
endpoints[i] = &Endpoint{
Name: e.Name,
Request: request,
Response: response,
Metadata: metadata,
Request: e.Request,
Response: e.Response,
Metadata: md,
}
}
nodes := make([]*Node, len(r.Nodes))
i := 0
for _, n := range r.Nodes {
metadata := make(map[string]string, len(n.Metadata))
md := make(map[string]string, len(n.Metadata))
for k, v := range n.Metadata {
metadata[k] = v
md[k] = v
}
nodes[i] = &Node{
Id: n.Id,
ID: n.ID,
Address: n.Address,
Metadata: metadata,
Metadata: md,
}
i++
}

View File

@@ -8,72 +8,70 @@ import (
"time"
)
var (
testData = map[string][]*Service{
"foo": {
{
Name: "foo",
Version: "1.0.0",
Nodes: []*Node{
{
Id: "foo-1.0.0-123",
Address: "localhost:9999",
},
{
Id: "foo-1.0.0-321",
Address: "localhost:9999",
},
var testData = map[string][]*Service{
"foo": {
{
Name: "foo",
Version: "1.0.0",
Nodes: []*Node{
{
ID: "foo-1.0.0-123",
Address: "localhost:9999",
},
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*Node{
{
Id: "foo-1.0.1-321",
Address: "localhost:6666",
},
},
},
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
Id: "foo-1.0.3-345",
Address: "localhost:8888",
},
{
ID: "foo-1.0.0-321",
Address: "localhost:9999",
},
},
},
"bar": {
{
Name: "bar",
Version: "default",
Nodes: []*Node{
{
Id: "bar-1.0.0-123",
Address: "localhost:9999",
},
{
Id: "bar-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
{
Id: "bar-1.0.1-321",
Address: "localhost:6666",
},
{
Name: "foo",
Version: "1.0.1",
Nodes: []*Node{
{
ID: "foo-1.0.1-321",
Address: "localhost:6666",
},
},
},
}
)
{
Name: "foo",
Version: "1.0.3",
Nodes: []*Node{
{
ID: "foo-1.0.3-345",
Address: "localhost:8888",
},
},
},
},
"bar": {
{
Name: "bar",
Version: "default",
Nodes: []*Node{
{
ID: "bar-1.0.0-123",
Address: "localhost:9999",
},
{
ID: "bar-1.0.0-321",
Address: "localhost:9999",
},
},
},
{
Name: "bar",
Version: "latest",
Nodes: []*Node{
{
ID: "bar-1.0.1-321",
Address: "localhost:6666",
},
},
},
},
}
//nolint:gocyclo
func TestMemoryRegistry(t *testing.T) {

View File

@@ -44,6 +44,7 @@ func NewOptions(opts ...Option) Options {
return options
}
// nolint: golint
// RegisterOptions holds options for register method
type RegisterOptions struct {
Context context.Context
@@ -196,6 +197,7 @@ func TLSConfig(t *tls.Config) Option {
}
}
// nolint: golint
// RegisterAttempts specifies register atempts count
func RegisterAttempts(t int) RegisterOption {
return func(o *RegisterOptions) {
@@ -203,6 +205,7 @@ func RegisterAttempts(t int) RegisterOption {
}
}
// nolint: golint
// RegisterTTL specifies register ttl
func RegisterTTL(t time.Duration) RegisterOption {
return func(o *RegisterOptions) {
@@ -210,6 +213,7 @@ func RegisterTTL(t time.Duration) RegisterOption {
}
}
// nolint: golint
// RegisterContext sets the register context
func RegisterContext(ctx context.Context) RegisterOption {
return func(o *RegisterOptions) {
@@ -217,6 +221,7 @@ func RegisterContext(ctx context.Context) RegisterOption {
}
}
// nolint: golint
// RegisterDomain secifies register domain
func RegisterDomain(d string) RegisterOption {
return func(o *RegisterOptions) {

View File

@@ -53,29 +53,23 @@ type Service struct {
// Node holds node register info
type Node struct {
Metadata metadata.Metadata `json:"metadata"`
Id string `json:"id"`
ID string `json:"id"`
Address string `json:"address"`
}
// Endpoint holds endpoint register info
type Endpoint struct {
Request *Value `json:"request"`
Response *Value `json:"response"`
Request string `json:"request"`
Response string `json:"response"`
Metadata metadata.Metadata `json:"metadata"`
Name string `json:"name"`
}
// Value holds additional kv stuff
type Value struct {
Name string `json:"name"`
Type string `json:"type"`
Values []*Value `json:"values"`
}
// Option func signature
type Option func(*Options)
// RegisterOption option is used to register service
// nolint: golint
type RegisterOption func(*RegisterOptions)
// WatchOption option is used to watch service changes

View File

@@ -52,8 +52,8 @@ type Event struct {
Timestamp time.Time
// Service is register service
Service *Service
// Id is register id
Id string
// ID is register id
ID string
// Type defines type of event
Type EventType
}

View File

@@ -5,10 +5,8 @@ import (
"time"
)
var (
// ErrWatcherStopped is returned when routing table watcher has been stopped
ErrWatcherStopped = errors.New("watcher stopped")
)
// ErrWatcherStopped is returned when routing table watcher has been stopped
var ErrWatcherStopped = errors.New("watcher stopped")
// EventType defines routing table event
type EventType int
@@ -38,12 +36,12 @@ func (t EventType) String() string {
// Event is returned by a call to Next on the watcher.
type Event struct {
// Route is table route
Route Route
// Timestamp is event timestamp
Timestamp time.Time
// Id of the event
Id string
// Route is table route
Route Route
// Type defines type of event
Type EventType
}

View File

@@ -8,10 +8,8 @@ import (
"github.com/unistack-org/micro/v3/metadata"
)
var (
// ErrAlreadyExists error
ErrAlreadyExists = errors.New("already exists")
)
// ErrAlreadyExists error
var ErrAlreadyExists = errors.New("already exists")
// Runtime is a service runtime manager
type Runtime interface {

View File

@@ -5,10 +5,8 @@ import (
"errors"
)
var (
// ErrNoneAvailable is returned by select when no routes were provided to select from
ErrNoneAvailable = errors.New("none available")
)
// ErrNoneAvailable is returned by select when no routes were provided to select from
var ErrNoneAvailable = errors.New("none available")
// Selector selects a route from a pool
type Selector interface {

3
server/generate.go Normal file
View File

@@ -0,0 +1,3 @@
package server
//go:generate protoc -I./health -I../ -I/home/vtolstov/.cache/go-path/pkg/mod/github.com/unistack-org/micro-proto@v0.0.1 --micro_out=components=micro|http|server,standalone=false,debug=true,paths=source_relative:./health health/health.proto

View File

@@ -13,7 +13,7 @@ type rpcHandler struct {
endpoints []*register.Endpoint
}
func newRpcHandler(handler interface{}, opts ...HandlerOption) Handler {
func newRPCHandler(handler interface{}, opts ...HandlerOption) Handler {
options := NewHandlerOptions(opts...)
typ := reflect.TypeOf(handler)

82
server/health/health.go Normal file
View File

@@ -0,0 +1,82 @@
package health
import (
"context"
"github.com/unistack-org/micro/v3/codec"
"github.com/unistack-org/micro/v3/errors"
)
var _ HealthServer = &Handler{}
type Handler struct {
opts Options
}
type CheckFunc func(context.Context) error
type Option func(*Options)
type Options struct {
Version string
Name string
LiveChecks []CheckFunc
ReadyChecks []CheckFunc
}
func LiveChecks(fns ...CheckFunc) Option {
return func(o *Options) {
o.LiveChecks = append(o.LiveChecks, fns...)
}
}
func ReadyChecks(fns ...CheckFunc) Option {
return func(o *Options) {
o.ReadyChecks = append(o.ReadyChecks, fns...)
}
}
func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}
func Version(version string) Option {
return func(o *Options) {
o.Version = version
}
}
func NewHandler(opts ...Option) *Handler {
options := Options{}
for _, o := range opts {
o(&options)
}
return &Handler{opts: options}
}
func (h *Handler) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
var err error
for _, fn := range h.opts.LiveChecks {
if err = fn(ctx); err != nil {
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
}
}
return nil
}
func (h *Handler) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
var err error
for _, fn := range h.opts.ReadyChecks {
if err = fn(ctx); err != nil {
return errors.ServiceUnavailable(h.opts.Name, "%v", err)
}
}
return nil
}
func (h *Handler) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
rsp.Data = []byte(h.opts.Version)
return nil
}

View File

@@ -0,0 +1,62 @@
syntax = "proto3";
package micro.server.health;
option go_package = "github.com/unistack-org/micro/v3/server/health;health";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
import "codec/frame.proto";
service Health {
rpc Live(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Live";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/live"; };
};
rpc Ready(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Ready";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/ready"; };
};
rpc Version(micro.codec.Frame) returns (micro.codec.Frame) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "Version";
responses: {
key: "default";
value: {
description: "Error response";
schema: {
json_schema: {
ref: "micro.codec.Frame";
}
}
}
}
};
option (micro.api.http) = { get: "/version"; };
};
};

View File

@@ -0,0 +1,38 @@
// Code generated by protoc-gen-micro
// source: health.proto
package health
import (
context "context"
api "github.com/unistack-org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec"
)
func NewHealthEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "Health.Live",
Path: []string{"/live"},
Method: []string{"GET"},
Handler: "rpc",
},
&api.Endpoint{
Name: "Health.Ready",
Path: []string{"/ready"},
Method: []string{"GET"},
Handler: "rpc",
},
&api.Endpoint{
Name: "Health.Version",
Path: []string{"/version"},
Method: []string{"GET"},
Handler: "rpc",
},
}
}
type HealthServer interface {
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}

View File

@@ -0,0 +1,43 @@
// Code generated by protoc-gen-micro
// source: health.proto
package health
import (
context "context"
api "github.com/unistack-org/micro/v3/api"
codec "github.com/unistack-org/micro/v3/codec"
server "github.com/unistack-org/micro/v3/server"
)
type healthServer struct {
HealthServer
}
func (h *healthServer) Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.HealthServer.Live(ctx, req, rsp)
}
func (h *healthServer) Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.HealthServer.Ready(ctx, req, rsp)
}
func (h *healthServer) Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error {
return h.HealthServer.Version(ctx, req, rsp)
}
func RegisterHealthServer(s server.Server, sh HealthServer, opts ...server.HandlerOption) error {
type health interface {
Live(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Ready(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
Version(ctx context.Context, req *codec.Frame, rsp *codec.Frame) error
}
type Health struct {
health
}
h := &healthServer{sh}
var nopts []server.HandlerOption
for _, endpoint := range NewHealthEndpoints() {
nopts = append(nopts, api.WithEndpoint(endpoint))
}
return s.Handle(s.NewHandler(&Health{h}, append(nopts, opts...)...))
}

View File

@@ -6,9 +6,6 @@ import (
"sync"
"time"
// cjson "github.com/unistack-org/micro-codec-json"
// cjsonrpc "github.com/unistack-org/micro-codec-jsonrpc"
// cproto "github.com/unistack-org/micro-codec-proto"
// cprotorpc "github.com/unistack-org/micro-codec-protorpc"
"github.com/unistack-org/micro/v3/broker"
"github.com/unistack-org/micro/v3/codec"
@@ -16,29 +13,23 @@ import (
"github.com/unistack-org/micro/v3/register"
)
var (
// DefaultCodecs will be used to encode/decode
DefaultCodecs = map[string]codec.Codec{
//"application/json": cjson.NewCodec,
//"application/json-rpc": cjsonrpc.NewCodec,
//"application/protobuf": cproto.NewCodec,
//"application/proto-rpc": cprotorpc.NewCodec,
"application/octet-stream": codec.NewCodec(),
}
)
// DefaultCodecs will be used to encode/decode
var DefaultCodecs = map[string]codec.Codec{
"application/octet-stream": codec.NewCodec(),
}
const (
defaultContentType = "application/json"
)
type noopServer struct {
opts Options
h Handler
wg *sync.WaitGroup
rsvc *register.Service
handlers map[string]Handler
subscribers map[*subscriber][]broker.Subscriber
exit chan chan error
wg *sync.WaitGroup
opts Options
sync.RWMutex
registered bool
started bool
@@ -103,7 +94,7 @@ func (n *noopServer) Subscribe(sb Subscriber) error {
}
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
return newRpcHandler(h, opts...)
return newRPCHandler(h, opts...)
}
func (n *noopServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
@@ -158,23 +149,16 @@ func (n *noopServer) Register() error {
}
n.RLock()
// Maps are ordered randomly, sort the keys for consistency
var handlerList []string
for n, e := range n.handlers {
// Only advertise non internal handlers
if !e.Options().Internal {
handlerList = append(handlerList, n)
}
handlerList := make([]string, 0, len(n.handlers))
for n := range n.handlers {
handlerList = append(handlerList, n)
}
sort.Strings(handlerList)
var subscriberList []*subscriber
subscriberList := make([]*subscriber, 0, len(n.subscribers))
for e := range n.subscribers {
// Only advertise non internal subscribers
if !e.Options().Internal {
subscriberList = append(subscriberList, e)
}
subscriberList = append(subscriberList, e)
}
sort.Slice(subscriberList, func(i, j int) bool {
return subscriberList[i].topic > subscriberList[j].topic
@@ -190,7 +174,7 @@ func (n *noopServer) Register() error {
n.RUnlock()
service.Nodes[0].Metadata["protocol"] = "noop"
service.Nodes[0].Metadata["transport"] = "noop"
service.Nodes[0].Metadata["transport"] = service.Nodes[0].Metadata["protocol"]
service.Endpoints = endpoints
n.RLock()
@@ -199,7 +183,7 @@ func (n *noopServer) Register() error {
if !registered {
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].ID)
}
}
@@ -262,7 +246,7 @@ func (n *noopServer) Deregister() error {
}
if config.Logger.V(logger.InfoLevel) {
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].Id)
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].ID)
}
if err := DefaultDeregisterFunc(service, config); err != nil {
@@ -344,9 +328,10 @@ func (n *noopServer) Start() error {
}
// use RegisterCheck func before register
// nolint: nestif
if err := config.RegisterCheck(config.Context); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, err)
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, err)
}
} else {
// announce self to the world
@@ -378,25 +363,26 @@ func (n *noopServer) Start() error {
registered := n.registered
n.RUnlock()
rerr := config.RegisterCheck(config.Context)
// nolint: nestif
if rerr != nil && registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.ID, rerr)
}
// deregister self in case of error
if err := n.Deregister(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.Id, err)
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.ID, err)
}
}
} else if rerr != nil && !registered {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, rerr)
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.ID, rerr)
}
continue
}
if err := n.Register(); err != nil {
if config.Logger.V(logger.ErrorLevel) {
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.Id, err)
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.ID, err)
}
}
// wait for exit

View File

@@ -57,8 +57,8 @@ type Options struct {
RegisterCheck func(context.Context) error
// Codecs map to handle content-type
Codecs map[string]codec.Codec
// Id holds the id of the server
Id string
// ID holds the id of the server
ID string
// Namespace for te server
Namespace string
// Name holds the server name
@@ -104,7 +104,7 @@ func NewOptions(opts ...Option) Options {
Address: DefaultAddress,
Name: DefaultName,
Version: DefaultVersion,
Id: DefaultId,
ID: DefaultID,
Namespace: DefaultNamespace,
}
@@ -143,10 +143,10 @@ func Meter(m meter.Meter) Option {
}
}
// Id unique server id
func Id(id string) Option {
// ID unique server id
func ID(id string) Option {
return func(o *Options) {
o.Id = id
o.ID = id
}
}
@@ -325,8 +325,6 @@ type HandlerOptions struct {
Context context.Context
// Metadata for hondler
Metadata map[string]metadata.Metadata
// Internal flag limits exporting to other nodes via register
Internal bool
}
// NewHandlerOptions creates new HandlerOptions
@@ -354,8 +352,6 @@ type SubscriberOptions struct {
Queue string
// AutoAck flag for auto ack messages after processing
AutoAck bool
// Internal flag limit exporting info via register
Internal bool
// BodyOnly flag specifies that message without headers
BodyOnly bool
}
@@ -382,23 +378,6 @@ func EndpointMetadata(name string, md metadata.Metadata) HandlerOption {
}
}
// InternalHandler options specifies that a handler is not advertised
// to the discovery system. In the future this may also limit request
// to the internal network or authorised user.
func InternalHandler(b bool) HandlerOption {
return func(o *HandlerOptions) {
o.Internal = b
}
}
// InternalSubscriber options specifies that a subscriber is not advertised
// to the discovery system.
func InternalSubscriber(b bool) SubscriberOption {
return func(o *SubscriberOptions) {
o.Internal = b
}
}
// DisableAutoAck will disable auto acking of messages
// after they have been handled.
func DisableAutoAck() SubscriberOption {

View File

@@ -72,7 +72,7 @@ func NewRegisterService(s Server) (*register.Service, error) {
}
node := &register.Node{
Id: opts.Name + "-" + opts.Id,
ID: opts.Name + "-" + opts.ID,
Address: net.JoinHostPort(addr, port),
}
node.Metadata = metadata.Copy(opts.Metadata)

View File

@@ -11,20 +11,18 @@ import (
"github.com/unistack-org/micro/v3/register"
)
var (
// DefaultServer default server
DefaultServer Server = NewServer()
)
// DefaultServer default server
var DefaultServer Server = NewServer()
var (
// DefaultAddress will be used if no address passed
DefaultAddress = ":0"
// DefaultAddress will be used if no address passed, use secure localhost
DefaultAddress = "127.0.0.1:0"
// DefaultName will be used if no name passed
DefaultName = "server"
// DefaultVersion will be used if no version passed
DefaultVersion = "latest"
// DefaultId will be used if no id passed
DefaultId = uuid.New().String()
// DefaultID will be used if no id passed
DefaultID = uuid.New().String()
// DefaultRegisterCheck holds func that run before register server
DefaultRegisterCheck = func(context.Context) error { return nil }
// DefaultRegisterInterval holds interval for register

View File

@@ -21,11 +21,9 @@ const (
subSig = "func(context.Context, interface{}) error"
)
var (
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
)
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
type handler struct {
reqType reflect.Type
@@ -64,7 +62,8 @@ func ValidateSubscriber(sub Subscriber) error {
typ := reflect.TypeOf(sub.Subscriber())
var argType reflect.Type
if typ.Kind() == reflect.Func {
switch typ.Kind() {
case reflect.Func:
name := "Func"
switch typ.NumIn() {
case 2:
@@ -82,7 +81,7 @@ func ValidateSubscriber(sub Subscriber) error {
if returnType := typ.Out(0); returnType != typeOfError {
return fmt.Errorf("subscriber %v returns %v not error", name, returnType.String())
}
} else {
default:
hdlr := reflect.ValueOf(sub.Subscriber())
name := reflect.Indirect(hdlr).Type().Name()
@@ -276,14 +275,14 @@ func (n *noopServer) createSubHandler(sb *subscriber, opts Options) broker.Handl
if n.wg != nil {
defer n.wg.Done()
}
err := fn(ctx, &rpcMessage{
cerr := fn(ctx, &rpcMessage{
topic: sb.topic,
contentType: ct,
payload: req.Interface(),
header: msg.Header,
body: msg.Body,
})
results <- err
results <- cerr
}()
}
var errors []string

View File

@@ -103,48 +103,48 @@ func (s *service) Init(opts ...Option) error {
// skip config as the struct not passed
continue
}
if err = cfg.Init(config.Context(s.opts.Context)); err != nil {
if err = cfg.Init(config.Context(cfg.Options().Context)); err != nil {
return err
}
if err = cfg.Load(s.opts.Context); err != nil {
if err = cfg.Load(cfg.Options().Context); err != nil {
return err
}
}
for _, log := range s.opts.Loggers {
if err = log.Init(logger.WithContext(s.opts.Context)); err != nil {
if err = log.Init(logger.WithContext(log.Options().Context)); err != nil {
return err
}
}
for _, reg := range s.opts.Registers {
if err = reg.Init(register.Context(s.opts.Context)); err != nil {
if err = reg.Init(register.Context(reg.Options().Context)); err != nil {
return err
}
}
for _, brk := range s.opts.Brokers {
if err = brk.Init(broker.Context(s.opts.Context)); err != nil {
if err = brk.Init(broker.Context(brk.Options().Context)); err != nil {
return err
}
}
for _, str := range s.opts.Stores {
if err = str.Init(store.Context(s.opts.Context)); err != nil {
if err = str.Init(store.Context(str.Options().Context)); err != nil {
return err
}
}
for _, srv := range s.opts.Servers {
if err = srv.Init(server.Context(s.opts.Context)); err != nil {
if err = srv.Init(server.Context(srv.Options().Context)); err != nil {
return err
}
}
for _, cli := range s.opts.Clients {
if err = cli.Init(client.Context(s.opts.Context)); err != nil {
if err = cli.Init(client.Context(cli.Options().Context)); err != nil {
return err
}
}
@@ -162,7 +162,6 @@ func (s *service) Broker(names ...string) broker.Broker {
idx = getNameIndex(names[0], s.opts.Brokers)
}
return s.opts.Brokers[idx]
}
func (s *service) Tracer(names ...string) tracer.Tracer {

View File

@@ -28,24 +28,14 @@ func (m *memoryStore) Disconnect(ctx context.Context) error {
}
type memoryStore struct {
opts Options
store *cache.Cache
opts Options
}
func (m *memoryStore) key(prefix, key string) string {
return filepath.Join(prefix, key)
}
func (m *memoryStore) prefix(database, table string) string {
if len(database) == 0 {
database = m.opts.Database
}
if len(table) == 0 {
table = m.opts.Table
}
return filepath.Join(database, table)
}
func (m *memoryStore) exists(prefix, key string) error {
key = m.key(prefix, key)
@@ -80,15 +70,17 @@ func (m *memoryStore) delete(prefix, key string) {
func (m *memoryStore) list(prefix string, limit, offset uint) []string {
allItems := m.store.Items()
allKeys := make([]string, len(allItems))
i := 0
allKeys := make([]string, 0, len(allItems))
for k := range allItems {
if !strings.HasPrefix(k, prefix+"/") {
if !strings.HasPrefix(k, prefix) {
continue
}
allKeys[i] = strings.TrimPrefix(k, prefix+"/")
i++
k = strings.TrimPrefix(k, prefix)
if k[0] == '/' {
k = k[1:]
}
allKeys = append(allKeys, k)
}
if limit != 0 || offset != 0 {
@@ -107,7 +99,6 @@ func (m *memoryStore) list(prefix string, limit, offset uint) []string {
}
return allKeys[offset:end]
}
return allKeys
}
@@ -127,37 +118,48 @@ func (m *memoryStore) Name() string {
}
func (m *memoryStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
prefix := m.prefix(m.opts.Database, m.opts.Table)
return m.exists(prefix, key)
options := NewExistsOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.exists(options.Namespace, key)
}
func (m *memoryStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
readOpts := NewReadOptions(opts...)
prefix := m.prefix(readOpts.Database, readOpts.Table)
return m.get(prefix, key, val)
options := NewReadOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
return m.get(options.Namespace, key, val)
}
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
writeOpts := NewWriteOptions(opts...)
options := NewWriteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
if options.TTL == 0 {
options.TTL = cache.NoExpiration
}
prefix := m.prefix(writeOpts.Database, writeOpts.Table)
key = m.key(prefix, key)
key = m.key(options.Namespace, key)
buf, err := m.opts.Codec.Marshal(val)
if err != nil {
return err
}
m.store.Set(key, buf, writeOpts.TTL)
m.store.Set(key, buf, options.TTL)
return nil
}
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
deleteOptions := NewDeleteOptions(opts...)
options := NewDeleteOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
prefix := m.prefix(deleteOptions.Database, deleteOptions.Table)
m.delete(prefix, key)
m.delete(options.Namespace, key)
return nil
}
@@ -166,25 +168,27 @@ func (m *memoryStore) Options() Options {
}
func (m *memoryStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
listOptions := NewListOptions(opts...)
options := NewListOptions(opts...)
if options.Namespace == "" {
options.Namespace = m.opts.Namespace
}
prefix := m.prefix(listOptions.Database, listOptions.Table)
keys := m.list(prefix, listOptions.Limit, listOptions.Offset)
keys := m.list(options.Namespace, options.Limit, options.Offset)
if len(listOptions.Prefix) > 0 {
if len(options.Prefix) > 0 {
var prefixKeys []string
for _, k := range keys {
if strings.HasPrefix(k, listOptions.Prefix) {
if strings.HasPrefix(k, options.Prefix) {
prefixKeys = append(prefixKeys, k)
}
}
keys = prefixKeys
}
if len(listOptions.Suffix) > 0 {
if len(options.Suffix) > 0 {
var suffixKeys []string
for _, k := range keys {
if strings.HasSuffix(k, listOptions.Suffix) {
if strings.HasSuffix(k, options.Suffix) {
suffixKeys = append(suffixKeys, k)
}
}

View File

@@ -9,11 +9,11 @@ import (
)
func TestMemoryReInit(t *testing.T) {
s := store.NewStore(store.Table("aaa"))
if err := s.Init(store.Table("")); err != nil {
s := store.NewStore(store.Namespace("aaa"))
if err := s.Init(store.Namespace("")); err != nil {
t.Fatal(err)
}
if len(s.Options().Table) > 0 {
if len(s.Options().Namespace) > 0 {
t.Error("Init didn't reinitialise the store")
}
}
@@ -28,7 +28,7 @@ func TestMemoryBasic(t *testing.T) {
func TestMemoryPrefix(t *testing.T) {
s := store.NewStore()
if err := s.Init(store.Table("some-prefix")); err != nil {
if err := s.Init(store.Namespace("some-prefix")); err != nil {
t.Fatal(err)
}
basictest(s, t)
@@ -36,7 +36,7 @@ func TestMemoryPrefix(t *testing.T) {
func TestMemoryNamespace(t *testing.T) {
s := store.NewStore()
if err := s.Init(store.Database("some-namespace")); err != nil {
if err := s.Init(store.Namespace("some-namespace")); err != nil {
t.Fatal(err)
}
basictest(s, t)
@@ -44,7 +44,7 @@ func TestMemoryNamespace(t *testing.T) {
func TestMemoryNamespacePrefix(t *testing.T) {
s := store.NewStore()
if err := s.Init(store.Table("some-prefix"), store.Database("some-namespace")); err != nil {
if err := s.Init(store.Namespace("some-namespace")); err != nil {
t.Fatal(err)
}
basictest(s, t)

View File

@@ -28,13 +28,12 @@ type Options struct {
TLSConfig *tls.Config
// Name specifies store name
Name string
// Database specifies store database
Database string
// Table specifies store table
Table string
// Nodes contains store address
// TODO: replace with Addrs
Nodes []string
// Namespace of the records
Namespace string
// Addrs contains store address
Addrs []string
//Wrappers store wrapper that called before actual functions
//Wrappers []Wrapper
}
// NewOptions creates options struct
@@ -90,13 +89,20 @@ func Meter(m meter.Meter) Option {
}
}
// Name the name
// Name the name of the store
func Name(n string) Option {
return func(o *Options) {
o.Name = n
}
}
// Namespace sets namespace of the store
func Namespace(ns string) Option {
return func(o *Options) {
o.Namespace = ns
}
}
// Tracer sets the tracer
func Tracer(t tracer.Tracer) Option {
return func(o *Options) {
@@ -104,27 +110,21 @@ func Tracer(t tracer.Tracer) Option {
}
}
// Nodes contains the addresses or other connection information of the backing storage.
// Addrs contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings.
func Nodes(a ...string) Option {
func Addrs(addrs ...string) Option {
return func(o *Options) {
o.Nodes = a
o.Addrs = addrs
}
}
// Database allows multiple isolated stores to be kept in one backend, if supported.
func Database(db string) Option {
return func(o *Options) {
o.Database = db
}
}
// Table is analag for a table in database backends or a key prefix in KV backends
func Table(t string) Option {
return func(o *Options) {
o.Table = t
}
// ReadOptions configures an individual Read operation
type ReadOptions struct {
// Context holds external options
Context context.Context
// Namespace holds namespace
Namespace string
}
// NewReadOptions fills ReadOptions struct with opts slice
@@ -136,29 +136,35 @@ func NewReadOptions(opts ...ReadOption) ReadOptions {
return options
}
// ReadOptions configures an individual Read operation
type ReadOptions struct {
// Context holds external options
Context context.Context
// Database holds the database name
Database string
// Table holds table name
Table string
// Namespace holds namespace
Namespace string
}
// ReadOption sets values in ReadOptions
type ReadOption func(r *ReadOptions)
// ReadFrom the database and table
func ReadFrom(database, table string) ReadOption {
return func(r *ReadOptions) {
r.Database = database
r.Table = table
// ReadContext pass context.Context to ReadOptions
func ReadContext(ctx context.Context) ReadOption {
return func(o *ReadOptions) {
o.Context = ctx
}
}
// ReadNamespace pass namespace to ReadOptions
func ReadNamespace(ns string) ReadOption {
return func(o *ReadOptions) {
o.Namespace = ns
}
}
// WriteOptions configures an individual Write operation
type WriteOptions struct {
// Context holds external options
Context context.Context
// Metadata contains additional metadata
Metadata metadata.Metadata
// Namespace holds namespace
Namespace string
// TTL specifies key TTL
TTL time.Duration
}
// NewWriteOptions fills WriteOptions struct with opts slice
func NewWriteOptions(opts ...WriteOption) WriteOptions {
options := WriteOptions{}
@@ -168,47 +174,45 @@ func NewWriteOptions(opts ...WriteOption) WriteOptions {
return options
}
// WriteOptions configures an individual Write operation
type WriteOptions struct {
// Context holds external options
Context context.Context
// Metadata contains additional metadata
Metadata metadata.Metadata
// Database holds database name
Database string
// Table holds table name
Table string
// Namespace holds namespace
Namespace string
// TTL specifies key TTL
TTL time.Duration
}
// WriteOption sets values in WriteOptions
type WriteOption func(w *WriteOptions)
// WriteTo the database and table
func WriteTo(database, table string) WriteOption {
return func(w *WriteOptions) {
w.Database = database
w.Table = table
}
}
// WriteTTL is the time the record expires
func WriteTTL(d time.Duration) WriteOption {
return func(w *WriteOptions) {
w.TTL = d
// WriteContext pass context.Context to wirte options
func WriteContext(ctx context.Context) WriteOption {
return func(o *WriteOptions) {
o.Context = ctx
}
}
// WriteMetadata add metadata.Metadata
func WriteMetadata(md metadata.Metadata) WriteOption {
return func(w *WriteOptions) {
w.Metadata = metadata.Copy(md)
return func(o *WriteOptions) {
o.Metadata = metadata.Copy(md)
}
}
// WriteTTL is the time the record expires
func WriteTTL(d time.Duration) WriteOption {
return func(o *WriteOptions) {
o.TTL = d
}
}
// WriteNamespace pass namespace to write options
func WriteNamespace(ns string) WriteOption {
return func(o *WriteOptions) {
o.Namespace = ns
}
}
// DeleteOptions configures an individual Delete operation
type DeleteOptions struct {
// Context holds external options
Context context.Context
// Namespace holds namespace
Namespace string
}
// NewDeleteOptions fills DeleteOptions struct with opts slice
func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
options := DeleteOptions{}
@@ -218,29 +222,33 @@ func NewDeleteOptions(opts ...DeleteOption) DeleteOptions {
return options
}
// DeleteOptions configures an individual Delete operation
type DeleteOptions struct {
// Context holds external options
Context context.Context
// Database holds database name
Database string
// Table holds table name
Table string
// Namespace holds namespace
Namespace string
}
// DeleteOption sets values in DeleteOptions
type DeleteOption func(d *DeleteOptions)
// DeleteFrom the database and table
func DeleteFrom(database, table string) DeleteOption {
return func(d *DeleteOptions) {
d.Database = database
d.Table = table
// DeleteContext pass context.Context to delete options
func DeleteContext(ctx context.Context) DeleteOption {
return func(o *DeleteOptions) {
o.Context = ctx
}
}
// DeleteNamespace pass namespace to delete options
func DeleteNamespace(ns string) DeleteOption {
return func(o *DeleteOptions) {
o.Namespace = ns
}
}
// ListOptions configures an individual List operation
type ListOptions struct {
Context context.Context
Prefix string
Suffix string
Namespace string
Limit uint
Offset uint
}
// NewListOptions fills ListOptions struct with opts slice
func NewListOptions(opts ...ListOption) ListOptions {
options := ListOptions{}
@@ -250,59 +258,50 @@ func NewListOptions(opts ...ListOption) ListOptions {
return options
}
// ListOptions configures an individual List operation
type ListOptions struct {
Context context.Context
Database string
Prefix string
Suffix string
Namespace string
Table string
Limit uint
Offset uint
}
// ListOption sets values in ListOptions
type ListOption func(l *ListOptions)
// ListFrom the database and table
func ListFrom(database, table string) ListOption {
return func(l *ListOptions) {
l.Database = database
l.Table = table
// ListContext pass context.Context to list options
func ListContext(ctx context.Context) ListOption {
return func(o *ListOptions) {
o.Context = ctx
}
}
// ListPrefix returns all keys that are prefixed with key
func ListPrefix(p string) ListOption {
return func(l *ListOptions) {
l.Prefix = p
func ListPrefix(s string) ListOption {
return func(o *ListOptions) {
o.Prefix = s
}
}
// ListSuffix returns all keys that end with key
func ListSuffix(s string) ListOption {
return func(l *ListOptions) {
l.Suffix = s
return func(o *ListOptions) {
o.Suffix = s
}
}
// ListLimit limits the number of returned keys to l
func ListLimit(l uint) ListOption {
return func(lo *ListOptions) {
lo.Limit = l
// ListLimit limits the number of returned keys
func ListLimit(n uint) ListOption {
return func(o *ListOptions) {
o.Limit = n
}
}
// ListOffset starts returning responses from o. Use in conjunction with Limit for pagination.
func ListOffset(o uint) ListOption {
return func(l *ListOptions) {
l.Offset = o
// ListOffset use with Limit for pagination
func ListOffset(n uint) ListOption {
return func(o *ListOptions) {
o.Offset = n
}
}
// ExistsOption specifies Exists call options
type ExistsOption func(*ExistsOptions)
// ListNamespace pass namespace to list options
func ListNamespace(ns string) ListOption {
return func(o *ListOptions) {
o.Namespace = ns
}
}
// ExistsOptions holds options for Exists method
type ExistsOptions struct {
@@ -312,6 +311,9 @@ type ExistsOptions struct {
Namespace string
}
// ExistsOption specifies Exists call options
type ExistsOption func(*ExistsOptions)
// NewExistsOptions helper for Exists method
func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
options := ExistsOptions{
@@ -322,3 +324,24 @@ func NewExistsOptions(opts ...ExistsOption) ExistsOptions {
}
return options
}
// ExistsContext pass context.Context to exist options
func ExistsContext(ctx context.Context) ExistsOption {
return func(o *ExistsOptions) {
o.Context = ctx
}
}
// ExistsNamespace pass namespace to exist options
func ExistsNamespace(ns string) ExistsOption {
return func(o *ExistsOptions) {
o.Namespace = ns
}
}
// WrapStore adds a store Wrapper to a list of options passed into the store
//func WrapStore(w Wrapper) Option {
// return func(o *Options) {
// o.Wrappers = append(o.Wrappers, w)
// }
//}

View File

@@ -5,8 +5,6 @@ package store
import (
"context"
"errors"
"github.com/unistack-org/micro/v3/metadata"
)
var (
@@ -42,12 +40,3 @@ type Store interface {
// String returns the name of the implementation.
String() string
}
// Value is an item stored or retrieved from a Store
// may be used in store implementations to provide metadata
type Value struct {
// Data holds underline struct
Data interface{} `json:"data"`
// Metadata associated with data for indexing
Metadata metadata.Metadata `json:"metadata"`
}

84
store/wrapper.go Normal file
View File

@@ -0,0 +1,84 @@
package store
import (
"context"
)
// LogfFunc function used for Logf method
//type LogfFunc func(ctx context.Context, level Level, msg string, args ...interface{})
//type Wrapper interface {
// Logf logs message with needed level
//Logf(LogfFunc) LogfFunc
//}
type NamespaceStore struct {
s Store
ns string
}
var (
_ Store = &NamespaceStore{}
)
func NewNamespaceStore(s Store, ns string) Store {
return &NamespaceStore{s: s, ns: ns}
}
func (w *NamespaceStore) Init(opts ...Option) error {
return w.s.Init(opts...)
}
func (w *NamespaceStore) Connect(ctx context.Context) error {
return w.s.Connect(ctx)
}
func (w *NamespaceStore) Disconnect(ctx context.Context) error {
return w.s.Disconnect(ctx)
}
func (w *NamespaceStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
return w.s.Read(ctx, key, val, append(opts, ReadNamespace(w.ns))...)
}
func (w *NamespaceStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
return w.s.Write(ctx, key, val, append(opts, WriteNamespace(w.ns))...)
}
func (w *NamespaceStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
return w.s.Delete(ctx, key, append(opts, DeleteNamespace(w.ns))...)
}
func (w *NamespaceStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
return w.s.Exists(ctx, key, append(opts, ExistsNamespace(w.ns))...)
}
func (w *NamespaceStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
return w.s.List(ctx, append(opts, ListNamespace(w.ns))...)
}
func (w *NamespaceStore) Options() Options {
return w.s.Options()
}
func (w *NamespaceStore) Name() string {
return w.s.Name()
}
func (w *NamespaceStore) String() string {
return w.s.String()
}
//type NamespaceWrapper struct{}
//func NewNamespaceWrapper() Wrapper {
// return &NamespaceWrapper{}
//}
/*
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
}
*/

View File

@@ -6,8 +6,8 @@ import (
)
type memorySync struct {
options Options
locks map[string]*memoryLock
options Options
mtx gosync.RWMutex
}

View File

@@ -5,10 +5,8 @@ import (
"errors"
)
var (
// ErrLockTimeout error
ErrLockTimeout = errors.New("lock timeout")
)
// ErrLockTimeout error
var ErrLockTimeout = errors.New("lock timeout")
// Sync is an interface for distributed synchronization
type Sync interface {

View File

@@ -38,7 +38,6 @@ type noopSpan struct {
}
func (s *noopSpan) Finish(opts ...SpanOption) {
}
func (s *noopSpan) Context() context.Context {
@@ -50,7 +49,6 @@ func (s *noopSpan) Tracer() Tracer {
}
func (s *noopSpan) AddEvent(name string, opts ...EventOption) {
}
func (s *noopSpan) SetName(name string) {
@@ -58,7 +56,6 @@ func (s *noopSpan) SetName(name string) {
}
func (s *noopSpan) SetLabels(labels ...Label) {
}
// NewTracer returns new memory tracer

View File

@@ -2,13 +2,11 @@ package tracer
import "github.com/unistack-org/micro/v3/logger"
type SpanOptions struct {
}
type SpanOptions struct{}
type SpanOption func(o *SpanOptions)
type EventOptions struct {
}
type EventOptions struct{}
type EventOption func(o *EventOptions)

View File

@@ -5,10 +5,8 @@ import (
"context"
)
var (
// DefaultTracer is the global default tracer
DefaultTracer Tracer = NewTracer()
)
// DefaultTracer is the global default tracer
var DefaultTracer Tracer = NewTracer()
// Tracer is an interface for distributed tracing
type Tracer interface {

View File

@@ -113,12 +113,14 @@ type tWrapper struct {
opts Options
}
type ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, tracer.Span, error)
type ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, tracer.Span, error)
type ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, tracer.Span, error)
type ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, tracer.Span, error)
type ServerHandlerObserver func(context.Context, server.Request, interface{}, tracer.Span, error)
type ServerSubscriberObserver func(context.Context, server.Message, tracer.Span, error)
type (
ClientCallObserver func(context.Context, client.Request, interface{}, []client.CallOption, tracer.Span, error)
ClientStreamObserver func(context.Context, client.Request, []client.CallOption, client.Stream, tracer.Span, error)
ClientPublishObserver func(context.Context, client.Message, []client.PublishOption, tracer.Span, error)
ClientCallFuncObserver func(context.Context, string, client.Request, interface{}, client.CallOptions, tracer.Span, error)
ServerHandlerObserver func(context.Context, server.Request, interface{}, tracer.Span, error)
ServerSubscriberObserver func(context.Context, server.Message, tracer.Span, error)
)
// Options struct
type Options struct {

View File

@@ -5,9 +5,7 @@ import (
"net"
)
var (
privateBlocks []*net.IPNet
)
var privateBlocks []*net.IPNet
func init() {
for _, b := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10", "fd00::/8"} {

View File

@@ -54,7 +54,6 @@ func TestExtractor(t *testing.T) {
t.Errorf("Expected %s got %s", d.expect, addr)
}
}
}
func TestAppendPrivateBlocks(t *testing.T) {

View File

@@ -4,20 +4,20 @@ import (
"bytes"
)
type buffer struct {
type Buffer struct {
*bytes.Buffer
}
// Close reset buffer contents
func (b *buffer) Close() error {
func (b *Buffer) Close() error {
b.Buffer.Reset()
return nil
}
// New creates new buffer that satisfies Closer interface
func New(b *bytes.Buffer) *buffer {
func New(b *bytes.Buffer) *Buffer {
if b == nil {
b = bytes.NewBuffer(nil)
}
return &buffer{b}
return &Buffer{b}
}

15
util/fn/fn.go Normal file
View File

@@ -0,0 +1,15 @@
package fn
type Initer interface {
Init(opts ...interface{}) error
}
func Init(ifaces ...Initer) error {
var err error
for _, iface := range ifaces {
if err = iface.Init(); err != nil {
return err
}
}
return nil
}

View File

@@ -27,7 +27,6 @@ func HostPort(addr string, port interface{}) string {
// Listen takes addr:portmin-portmax and binds to the first available port
// Example: Listen("localhost:5000-6000", fn)
func Listen(addr string, fn func(string) (net.Listener, error)) (net.Listener, error) {
if strings.Count(addr, ":") == 1 && strings.Count(addr, "-") == 0 {
return fn(addr)
}

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