diff --git a/broker/broker.go b/broker/broker.go index a67a0be8..adf4ed4a 100644 --- a/broker/broker.go +++ b/broker/broker.go @@ -46,6 +46,12 @@ type Broker interface { BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error) // String type of broker String() string + // Live returns broker liveness + Live() bool + // Ready returns broker readiness + Ready() bool + // Health returns broker health + Health() bool } type ( diff --git a/broker/memory/memory.go b/broker/memory/memory.go index de8f1c73..56c08eaa 100644 --- a/broker/memory/memory.go +++ b/broker/memory/memory.go @@ -339,6 +339,18 @@ func (m *memoryBroker) Name() string { return m.opts.Name } +func (m *memoryBroker) Live() bool { + return true +} + +func (m *memoryBroker) Ready() bool { + return true +} + +func (m *memoryBroker) Health() bool { + return true +} + func (m *memoryEvent) Topic() string { return m.topic } diff --git a/broker/noop.go b/broker/noop.go index 43f6e7dc..de98421e 100644 --- a/broker/noop.go +++ b/broker/noop.go @@ -25,6 +25,18 @@ func NewBroker(opts ...Option) *NoopBroker { return b } +func (b *NoopBroker) Health() bool { + return true +} + +func (b *NoopBroker) Live() bool { + return true +} + +func (b *NoopBroker) Ready() bool { + return true +} + func (b *NoopBroker) Name() string { return b.opts.Name } diff --git a/client/options.go b/client/options.go index 6c8d0637..84b86672 100644 --- a/client/options.go +++ b/client/options.go @@ -11,7 +11,6 @@ import ( "go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/meter" - "go.unistack.org/micro/v3/network/transport" "go.unistack.org/micro/v3/options" "go.unistack.org/micro/v3/register" "go.unistack.org/micro/v3/router" @@ -22,8 +21,6 @@ import ( // Options holds client options type Options struct { - // Transport used for transfer messages - Transport transport.Transport // Selector used to select needed address Selector selector.Selector // Logger used to log messages @@ -194,18 +191,16 @@ func NewOptions(opts ...Option) Options { Retry: DefaultRetry, Retries: DefaultRetries, RequestTimeout: DefaultRequestTimeout, - DialTimeout: transport.DefaultDialTimeout, }, - Lookup: LookupRoute, - PoolSize: DefaultPoolSize, - PoolTTL: DefaultPoolTTL, - Selector: random.NewSelector(), - Logger: logger.DefaultLogger, - Broker: broker.DefaultBroker, - Meter: meter.DefaultMeter, - Tracer: tracer.DefaultTracer, - Router: router.DefaultRouter, - Transport: transport.DefaultTransport, + Lookup: LookupRoute, + PoolSize: DefaultPoolSize, + PoolTTL: DefaultPoolTTL, + Selector: random.NewSelector(), + Logger: logger.DefaultLogger, + Broker: broker.DefaultBroker, + Meter: meter.DefaultMeter, + Tracer: tracer.DefaultTracer, + Router: router.DefaultRouter, } for _, o := range opts { @@ -278,13 +273,6 @@ func PoolTTL(d time.Duration) Option { } } -// Transport to use for communication e.g http, rabbitmq, etc -func Transport(t transport.Transport) Option { - return func(o *Options) { - o.Transport = t - } -} - // Register sets the routers register func Register(r register.Register) Option { return func(o *Options) { @@ -334,14 +322,6 @@ func TLSConfig(t *tls.Config) Option { return func(o *Options) { // set the internal tls o.TLSConfig = t - - // set the default transport if one is not - // already set. Required for Init call below. - - // set the transport tls - _ = o.Transport.Init( - transport.TLSConfig(t), - ) } } @@ -507,13 +487,6 @@ func WithAuthToken(t string) CallOption { } } -// WithNetwork is a CallOption which sets the network attribute -func WithNetwork(n string) CallOption { - return func(o *CallOptions) { - o.Network = n - } -} - // WithRouter sets the router to use for this call func WithRouter(r router.Router) CallOption { return func(o *CallOptions) { diff --git a/cluster/cluster.go b/cluster/cluster.go index 26549e62..01544eb8 100644 --- a/cluster/cluster.go +++ b/cluster/cluster.go @@ -38,4 +38,10 @@ type Cluster interface { Broadcast(ctx context.Context, msg Message, filter ...string) error // Unicast send message to single member in cluster Unicast(ctx context.Context, node Node, msg Message) error + // Live returns cluster liveness + Live() bool + // Ready returns cluster readiness + Ready() bool + // Health returns cluster health + Health() bool } diff --git a/config/config.go b/config/config.go index 0ca629e4..a1831826 100644 --- a/config/config.go +++ b/config/config.go @@ -4,7 +4,6 @@ package config import ( "context" "errors" - "fmt" "reflect" "time" ) @@ -139,7 +138,7 @@ var ( return nil } if err := fn(ctx, c); err != nil { - c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeLoad error", c.String()), err) + c.Options().Logger.Error(ctx, c.String()+" BeforeLoad error", err) if !c.Options().AllowFail { return err } @@ -154,7 +153,7 @@ var ( return nil } if err := fn(ctx, c); err != nil { - c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterLoad error", c.String()), err) + c.Options().Logger.Error(ctx, c.String()+" AfterLoad error", err) if !c.Options().AllowFail { return err } @@ -169,7 +168,7 @@ var ( return nil } if err := fn(ctx, c); err != nil { - c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeSave error", c.String()), err) + c.Options().Logger.Error(ctx, c.String()+" BeforeSave error", err) if !c.Options().AllowFail { return err } @@ -184,7 +183,7 @@ var ( return nil } if err := fn(ctx, c); err != nil { - c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterSave error", c.String()), err) + c.Options().Logger.Error(ctx, c.String()+" AfterSave error", err) if !c.Options().AllowFail { return err } @@ -199,7 +198,7 @@ var ( return nil } if err := fn(ctx, c); err != nil { - c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeInit error", c.String()), err) + c.Options().Logger.Error(ctx, c.String()+" BeforeInit error", err) if !c.Options().AllowFail { return err } @@ -214,7 +213,7 @@ var ( return nil } if err := fn(ctx, c); err != nil { - c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterInit error", c.String(), err), err) + c.Options().Logger.Error(ctx, c.String()+" AfterInit error", err) if !c.Options().AllowFail { return err } diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go index a5381ede..5aa41133 100644 --- a/metadata/metadata_test.go +++ b/metadata/metadata_test.go @@ -5,6 +5,28 @@ import ( "testing" ) +func TestMultipleUsage(t *testing.T) { + ctx := context.TODO() + md := New(0) + md.Set("key1_1", "val1_1", "key1_2", "val1_2", "key1_3", "val1_3") + ctx = NewIncomingContext(ctx, Copy(md)) + ctx = NewOutgoingContext(ctx, Copy(md)) + imd, _ := FromIncomingContext(ctx) + omd, _ := FromOutgoingContext(ctx) + _ = func(x context.Context) context.Context { + m, _ := FromIncomingContext(x) + m.Del("key1_2") + return ctx + }(ctx) + _ = func(x context.Context) context.Context { + m, _ := FromIncomingContext(x) + m.Del("key1_3") + return ctx + }(ctx) + t.Logf("imd %#+v", imd) + t.Logf("omd %#+v", omd) +} + func TestMetadataSetMultiple(t *testing.T) { md := New(4) md.Set("key1", "val1", "key2", "val2", "key3") diff --git a/micro_test.go b/micro_test.go index d2a20121..20272e8c 100644 --- a/micro_test.go +++ b/micro_test.go @@ -66,6 +66,12 @@ type bro struct { func (p *bro) Name() string { return p.name } +func (p *bro) Live() bool { return true } + +func (p *bro) Ready() bool { return true } + +func (p *bro) Health() bool { return true } + func (p *bro) Init(opts ...broker.Option) error { return nil } // Options returns broker options diff --git a/network/tunnel/broker/broker.go b/network/tunnel/broker/broker.go index 921d1853..a05e0ecb 100644 --- a/network/tunnel/broker/broker.go +++ b/network/tunnel/broker/broker.go @@ -45,6 +45,18 @@ type ( tunnelAddr struct{} ) +func (t *tunBroker) Live() bool { + return true +} + +func (t *tunBroker) Ready() bool { + return true +} + +func (t *tunBroker) Health() bool { + return true +} + func (t *tunBroker) Init(opts ...broker.Option) error { for _, o := range opts { o(&t.opts) diff --git a/register/register.go b/register/register.go index 5f2bdab3..a5713cc4 100644 --- a/register/register.go +++ b/register/register.go @@ -29,17 +29,32 @@ var ( // and an abstraction over varying implementations // {consul, etcd, zookeeper, ...} type Register interface { + // Name returns register name Name() string + // Init initialize register Init(...Option) error + // Options returns options for register Options() Options + // Connect initialize connect to register Connect(context.Context) error + // Disconnect initialize discconection from register Disconnect(context.Context) error + // Register service in registry Register(context.Context, *Service, ...RegisterOption) error + // Deregister service from registry Deregister(context.Context, *Service, ...DeregisterOption) error + // LookupService in registry LookupService(context.Context, string, ...LookupOption) ([]*Service, error) + // ListServices in registry ListServices(context.Context, ...ListOption) ([]*Service, error) + // Watch registry events Watch(context.Context, ...WatchOption) (Watcher, error) + // String returns registry string representation String() string + // Live returns register liveness + // Live() bool + // Ready returns register readiness + // Ready() bool } // Service holds service register info diff --git a/server/noop.go b/server/noop.go index c4acf493..86fcfeb9 100644 --- a/server/noop.go +++ b/server/noop.go @@ -121,6 +121,18 @@ func (n *noopServer) newCodec(contentType string) (codec.Codec, error) { return nil, codec.ErrUnknownContentType } +func (n *noopServer) Live() bool { + return true +} + +func (n *noopServer) Ready() bool { + return true +} + +func (n *noopServer) Health() bool { + return true +} + func (n *noopServer) Handle(handler Handler) error { n.h = handler return nil diff --git a/server/options.go b/server/options.go index ffced1a4..4670cf63 100644 --- a/server/options.go +++ b/server/options.go @@ -12,7 +12,6 @@ import ( "go.unistack.org/micro/v3/logger" "go.unistack.org/micro/v3/metadata" "go.unistack.org/micro/v3/meter" - "go.unistack.org/micro/v3/network/transport" "go.unistack.org/micro/v3/options" "go.unistack.org/micro/v3/register" msync "go.unistack.org/micro/v3/sync" @@ -37,8 +36,6 @@ type Options struct { Logger logger.Logger // Meter holds the meter Meter meter.Meter - // Transport holds the transport - Transport transport.Transport /* // Router for requests @@ -100,7 +97,6 @@ func NewOptions(opts ...Option) Options { Tracer: tracer.DefaultTracer, Broker: broker.DefaultBroker, Register: register.DefaultRegister, - Transport: transport.DefaultTransport, Address: DefaultAddress, Name: DefaultName, Version: DefaultVersion, @@ -209,13 +205,6 @@ func Tracer(t tracer.Tracer) Option { } } -// Transport mechanism for communication e.g http, rabbitmq, etc -func Transport(t transport.Transport) Option { - return func(o *Options) { - o.Transport = t - } -} - // Metadata associated with the server func Metadata(md metadata.Metadata) Option { return func(o *Options) { @@ -249,14 +238,6 @@ func TLSConfig(t *tls.Config) Option { return func(o *Options) { // set the internal tls o.TLSConfig = t - - // set the default transport if one is not - // already set. Required for Init call below. - - // set the transport tls - _ = o.Transport.Init( - transport.TLSConfig(t), - ) } } diff --git a/server/server.go b/server/server.go index 49461e3a..8f9e1579 100644 --- a/server/server.go +++ b/server/server.go @@ -62,6 +62,12 @@ type Server interface { Stop() error // Server implementation String() string + // Live returns server liveness + Live() bool + // Ready returns server readiness + Ready() bool + // Health returns server health + Health() bool } type ( diff --git a/service.go b/service.go index cc827492..295e8c82 100644 --- a/service.go +++ b/service.go @@ -1,5 +1,5 @@ // Package micro is a pluggable framework for microservices -package micro // import "go.unistack.org/micro/v3" +package micro import ( "fmt" @@ -72,8 +72,14 @@ type Service interface { Start() error // Stop the service Stop() error - // The service implementation + // String service representation String() string + // Live returns service liveness + Live() bool + // Ready returns service readiness + Ready() bool + // Health returns service health + Health() bool } // RegisterHandler is syntactic sugar for registering a handler @@ -101,9 +107,7 @@ func (s *service) Name() string { return s.opts.Name } -// Init initialises options. Additionally it calls cmd.Init -// which parses command line flags. cmd.Init is only called -// on first Init. +// Init initialises options. // //nolint:gocyclo func (s *service) Init(opts ...Option) error { @@ -252,6 +256,63 @@ func (s *service) String() string { return s.opts.Name } +func (s *service) Live() bool { + for _, v := range s.opts.Brokers { + if !v.Live() { + return false + } + } + for _, v := range s.opts.Servers { + if !v.Live() { + return false + } + } + for _, v := range s.opts.Stores { + if !v.Live() { + return false + } + } + return true +} + +func (s *service) Ready() bool { + for _, v := range s.opts.Brokers { + if !v.Ready() { + return false + } + } + for _, v := range s.opts.Servers { + if !v.Ready() { + return false + } + } + for _, v := range s.opts.Stores { + if !v.Ready() { + return false + } + } + return true +} + +func (s *service) Health() bool { + for _, v := range s.opts.Brokers { + if !v.Health() { + return false + } + } + for _, v := range s.opts.Servers { + if !v.Health() { + return false + } + } + for _, v := range s.opts.Stores { + if !v.Health() { + return false + } + } + return true +} + //nolint:gocyclo func (s *service) Start() error { var err error @@ -281,10 +342,6 @@ func (s *service) Start() error { config.Loggers[0].Info(s.opts.Context, fmt.Sprintf("starting [service] %s version %s", s.Options().Name, s.Options().Version)) } - if len(s.opts.Servers) == 0 { - return fmt.Errorf("cant start nil server") - } - for _, reg := range s.opts.Registers { if err = reg.Connect(s.opts.Context); err != nil { return err diff --git a/store/memory/memory.go b/store/memory/memory.go index 11b41046..c0d5193a 100644 --- a/store/memory/memory.go +++ b/store/memory/memory.go @@ -149,6 +149,18 @@ func (m *memoryStore) Name() string { return m.opts.Name } +func (m *memoryStore) Live() bool { + return true +} + +func (m *memoryStore) Ready() bool { + return true +} + +func (m *memoryStore) Health() bool { + return true +} + func (m *memoryStore) Exists(ctx context.Context, key string, opts ...store.ExistsOption) error { if m.opts.LazyConnect { if err := m.connect(ctx); err != nil { @@ -279,3 +291,16 @@ func (m *memoryStore) connect(ctx context.Context) error { m.isConnected.CompareAndSwap(0, 1) return nil } + +func (m *memoryStore) Watch(ctx context.Context, opts ...store.WatchOption) (store.Watcher, error) { + return &watcher{}, nil +} + +type watcher struct{} + +func (w *watcher) Next() (store.Event, error) { + return nil, nil +} + +func (w *watcher) Stop() { +} diff --git a/store/noop.go b/store/noop.go index 83524901..24edafdd 100644 --- a/store/noop.go +++ b/store/noop.go @@ -23,6 +23,18 @@ type noopStore struct { isConnected atomic.Int32 } +func (n *noopStore) Live() bool { + return true +} + +func (n *noopStore) Ready() bool { + return true +} + +func (n *noopStore) Health() bool { + return true +} + func NewStore(opts ...Option) *noopStore { options := NewOptions(opts...) return &noopStore{opts: options} diff --git a/store/store.go b/store/store.go index 8235be5d..c5b8ac3e 100644 --- a/store/store.go +++ b/store/store.go @@ -45,7 +45,14 @@ type Store interface { Disconnect(ctx context.Context) error // String returns the name of the implementation. String() string + // Watch returns events watcher Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) + // Live returns store liveness + Live() bool + // Ready returns store readiness + Ready() bool + // Health returns store health + Health() bool } type ( diff --git a/store/wrapper.go b/store/wrapper.go index a9221bae..7b62db40 100644 --- a/store/wrapper.go +++ b/store/wrapper.go @@ -70,3 +70,15 @@ func (w *NamespaceStore) String() string { func (w *NamespaceStore) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) { return w.s.Watch(ctx, opts...) } + +func (w *NamespaceStore) Live() bool { + return w.s.Live() +} + +func (w *NamespaceStore) Ready() bool { + return w.s.Ready() +} + +func (w *NamespaceStore) Health() bool { + return w.s.Health() +} diff --git a/util/io/io.go b/util/io/io.go deleted file mode 100644 index ed4d8fc0..00000000 --- a/util/io/io.go +++ /dev/null @@ -1,40 +0,0 @@ -// Package io is for io management -package io - -import ( - "io" - - "go.unistack.org/micro/v3/network/transport" -) - -type rwc struct { - socket transport.Socket -} - -func (r *rwc) Read(p []byte) (n int, err error) { - m := new(transport.Message) - if err := r.socket.Recv(m); err != nil { - return 0, err - } - copy(p, m.Body) - return len(m.Body), nil -} - -func (r *rwc) Write(p []byte) (n int, err error) { - err = r.socket.Send(&transport.Message{ - Body: p, - }) - if err != nil { - return 0, err - } - return len(p), nil -} - -func (r *rwc) Close() error { - return r.socket.Close() -} - -// NewRWC returns a new ReadWriteCloser -func NewRWC(sock transport.Socket) io.ReadWriteCloser { - return &rwc{sock} -} diff --git a/util/pool/default.go b/util/pool/default.go deleted file mode 100644 index 7f61c281..00000000 --- a/util/pool/default.go +++ /dev/null @@ -1,118 +0,0 @@ -package pool - -import ( - "context" - "sync" - "time" - - "go.unistack.org/micro/v3/network/transport" - "go.unistack.org/micro/v3/util/id" -) - -type pool struct { - tr transport.Transport - conns map[string][]*poolConn - size int - ttl time.Duration - sync.Mutex -} - -type poolConn struct { - created time.Time - transport.Client - id string -} - -func newPool(options Options) *pool { - return &pool{ - size: options.Size, - tr: options.Transport, - ttl: options.TTL, - conns: make(map[string][]*poolConn), - } -} - -func (p *pool) Close() error { - p.Lock() - for k, c := range p.conns { - for _, conn := range c { - conn.Client.Close() - } - delete(p.conns, k) - } - p.Unlock() - return nil -} - -// NoOp the Close since we manage it -func (p *poolConn) Close() error { - return nil -} - -func (p *poolConn) ID() string { - return p.id -} - -func (p *poolConn) Created() time.Time { - return p.created -} - -func (p *pool) Get(ctx context.Context, addr string, opts ...transport.DialOption) (Conn, error) { - p.Lock() - conns := p.conns[addr] - - // while we have conns check age and then return one - // otherwise we'll create a new conn - for len(conns) > 0 { - conn := conns[len(conns)-1] - conns = conns[:len(conns)-1] - p.conns[addr] = conns - - // if conn is old kill it and move on - if d := time.Since(conn.Created()); d > p.ttl { - conn.Client.Close() - continue - } - - // we got a good conn, lets unlock and return it - p.Unlock() - - return conn, nil - } - - p.Unlock() - - // create new conn - c, err := p.tr.Dial(ctx, addr, opts...) - if err != nil { - return nil, err - } - id, err := id.New() - if err != nil { - return nil, err - } - return &poolConn{ - Client: c, - id: id, - created: time.Now(), - }, nil -} - -func (p *pool) Release(conn Conn, err error) error { - // don't store the conn if it has errored - if err != nil { - return conn.(*poolConn).Client.Close() - } - - // otherwise put it back for reuse - p.Lock() - conns := p.conns[conn.Remote()] - if len(conns) >= p.size { - p.Unlock() - return conn.(*poolConn).Client.Close() - } - p.conns[conn.Remote()] = append(conns, conn.(*poolConn)) - p.Unlock() - - return nil -} diff --git a/util/pool/default_test.go b/util/pool/default_test.go deleted file mode 100644 index 7fab0f6b..00000000 --- a/util/pool/default_test.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build ignore -// +build ignore - -package pool - -import ( - "testing" - "time" - - "go.unistack.org/micro/v3/network/transport" - "go.unistack.org/micro/v3/network/transport/memory" -) - -func testPool(t *testing.T, size int, ttl time.Duration) { - // mock transport - tr := memory.NewTransport() - - options := Options{ - TTL: ttl, - Size: size, - Transport: tr, - } - // zero pool - p := newPool(options) - - // listen - l, err := tr.Listen(":0") - if err != nil { - t.Fatal(err) - } - defer l.Close() - - // accept loop - go func() { - for { - if err := l.Accept(func(s transport.Socket) { - for { - var msg transport.Message - if err := s.Recv(&msg); err != nil { - return - } - if err := s.Send(&msg); err != nil { - return - } - } - }); err != nil { - return - } - } - }() - - for i := 0; i < 10; i++ { - // get a conn - c, err := p.Get(l.Addr()) - if err != nil { - t.Fatal(err) - } - - msg := &transport.Message{ - Body: []byte(`hello world`), - } - - if err := c.Send(msg); err != nil { - t.Fatal(err) - } - - var rcv transport.Message - - if err := c.Recv(&rcv); err != nil { - t.Fatal(err) - } - - if string(rcv.Body) != string(msg.Body) { - t.Fatalf("got %v, expected %v", rcv.Body, msg.Body) - } - - // release the conn - p.Release(c, nil) - - p.Lock() - if i := len(p.conns[l.Addr()]); i > size { - p.Unlock() - t.Fatalf("pool size %d is greater than expected %d", i, size) - } - p.Unlock() - } -} - -func TestClientPool(t *testing.T) { - testPool(t, 0, time.Minute) - testPool(t, 2, time.Minute) -} diff --git a/util/pool/options.go b/util/pool/options.go deleted file mode 100644 index 3edfc310..00000000 --- a/util/pool/options.go +++ /dev/null @@ -1,38 +0,0 @@ -package pool - -import ( - "time" - - "go.unistack.org/micro/v3/network/transport" -) - -// Options struct -type Options struct { - Transport transport.Transport - TTL time.Duration - Size int -} - -// Option func signature -type Option func(*Options) - -// Size sets the size -func Size(i int) Option { - return func(o *Options) { - o.Size = i - } -} - -// Transport sets the transport -func Transport(t transport.Transport) Option { - return func(o *Options) { - o.Transport = t - } -} - -// TTL specifies ttl -func TTL(t time.Duration) Option { - return func(o *Options) { - o.TTL = t - } -} diff --git a/util/pool/pool.go b/util/pool/pool.go deleted file mode 100644 index 2433cd59..00000000 --- a/util/pool/pool.go +++ /dev/null @@ -1,38 +0,0 @@ -// Package pool is a connection pool -package pool - -import ( - "context" - "time" - - "go.unistack.org/micro/v3/network/transport" -) - -// Pool is an interface for connection pooling -type Pool interface { - // Close the pool - Close() error - // Get a connection - Get(ctx context.Context, addr string, opts ...transport.DialOption) (Conn, error) - // Release the connection - Release(c Conn, status error) error -} - -// Conn conn pool interface -type Conn interface { - // unique id of connection - ID() string - // time it was created - Created() time.Time - // embedded connection - transport.Client -} - -// NewPool creates new connection pool -func NewPool(opts ...Option) Pool { - options := Options{} - for _, o := range opts { - o(&options) - } - return newPool(options) -} diff --git a/util/time/duration_test.go b/util/time/duration_test.go index 0324e68a..af5f1434 100644 --- a/util/time/duration_test.go +++ b/util/time/duration_test.go @@ -23,7 +23,7 @@ func TestMarshalYAML(t *testing.T) { func TestUnmarshalYAML(t *testing.T) { type str struct { - TTL Duration `yaml:"ttl"` + TTL *Duration `yaml:"ttl"` } v := &str{} var err error @@ -31,14 +31,14 @@ func TestUnmarshalYAML(t *testing.T) { err = yaml.Unmarshal([]byte(`{"ttl":"10ms"}`), v) if err != nil { t.Fatal(err) - } else if v.TTL != 10000000 { + } else if *(v.TTL) != 10000000 { t.Fatalf("invalid duration %v != 10000000", v.TTL) } err = yaml.Unmarshal([]byte(`{"ttl":"1y"}`), v) if err != nil { t.Fatal(err) - } else if v.TTL != 31622400000000000 { + } else if *(v.TTL) != 31622400000000000 { t.Fatalf("invalid duration %v != 31622400000000000", v.TTL) } }