mirror of https://github.com/status-im/consul.git
cache-types: Update Streaming health cache-type
To use latest protobuf types
This commit is contained in:
parent
132b76acef
commit
b576a2d3c7
|
@ -0,0 +1,174 @@
|
||||||
|
package cachetype
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/proto/pbcommon"
|
||||||
|
"github.com/hashicorp/consul/proto/pbservice"
|
||||||
|
"github.com/hashicorp/consul/proto/pbsubscribe"
|
||||||
|
"github.com/hashicorp/consul/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEndOfSnapshotEvent(topic pbsubscribe.Topic, index uint64) *pbsubscribe.Event {
|
||||||
|
return &pbsubscribe.Event{
|
||||||
|
Topic: topic,
|
||||||
|
Index: index,
|
||||||
|
Payload: &pbsubscribe.Event_EndOfSnapshot{EndOfSnapshot: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEndOfEmptySnapshotEvent(topic pbsubscribe.Topic, index uint64) *pbsubscribe.Event {
|
||||||
|
return &pbsubscribe.Event{
|
||||||
|
Topic: topic,
|
||||||
|
Index: index,
|
||||||
|
Payload: &pbsubscribe.Event_EndOfEmptySnapshot{EndOfEmptySnapshot: true},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEventServiceHealthRegister returns a realistically populated service
|
||||||
|
// health registration event for tests. The nodeNum is a
|
||||||
|
// logical node and is used to create the node name ("node%d") but also change
|
||||||
|
// the node ID and IP address to make it a little more realistic for cases that
|
||||||
|
// need that. nodeNum should be less than 64k to make the IP address look
|
||||||
|
// realistic. Any other changes can be made on the returned event to avoid
|
||||||
|
// adding too many options to callers.
|
||||||
|
func newEventServiceHealthRegister(index uint64, nodeNum int, svc string) pbsubscribe.Event {
|
||||||
|
node := fmt.Sprintf("node%d", nodeNum)
|
||||||
|
nodeID := types.NodeID(fmt.Sprintf("11111111-2222-3333-4444-%012d", nodeNum))
|
||||||
|
addr := fmt.Sprintf("10.10.%d.%d", nodeNum/256, nodeNum%256)
|
||||||
|
|
||||||
|
return pbsubscribe.Event{
|
||||||
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
||||||
|
Key: svc,
|
||||||
|
Index: index,
|
||||||
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
||||||
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
||||||
|
Op: pbsubscribe.CatalogOp_Register,
|
||||||
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
||||||
|
Node: &pbservice.Node{
|
||||||
|
ID: nodeID,
|
||||||
|
Node: node,
|
||||||
|
Address: addr,
|
||||||
|
Datacenter: "dc1",
|
||||||
|
RaftIndex: pbcommon.RaftIndex{
|
||||||
|
CreateIndex: index,
|
||||||
|
ModifyIndex: index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Service: &pbservice.NodeService{
|
||||||
|
ID: svc,
|
||||||
|
Service: svc,
|
||||||
|
Port: 8080,
|
||||||
|
Weights: &pbservice.Weights{
|
||||||
|
Passing: 1,
|
||||||
|
Warning: 1,
|
||||||
|
},
|
||||||
|
// Empty sadness
|
||||||
|
Proxy: pbservice.ConnectProxyConfig{
|
||||||
|
MeshGateway: pbservice.MeshGatewayConfig{},
|
||||||
|
Expose: pbservice.ExposeConfig{},
|
||||||
|
},
|
||||||
|
EnterpriseMeta: pbcommon.EnterpriseMeta{},
|
||||||
|
RaftIndex: pbcommon.RaftIndex{
|
||||||
|
CreateIndex: index,
|
||||||
|
ModifyIndex: index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Checks: []*pbservice.HealthCheck{
|
||||||
|
{
|
||||||
|
Node: node,
|
||||||
|
CheckID: "serf-health",
|
||||||
|
Name: "serf-health",
|
||||||
|
Status: "passing",
|
||||||
|
EnterpriseMeta: pbcommon.EnterpriseMeta{},
|
||||||
|
RaftIndex: pbcommon.RaftIndex{
|
||||||
|
CreateIndex: index,
|
||||||
|
ModifyIndex: index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Node: node,
|
||||||
|
CheckID: types.CheckID("service:" + svc),
|
||||||
|
Name: "service:" + svc,
|
||||||
|
ServiceID: svc,
|
||||||
|
ServiceName: svc,
|
||||||
|
Type: "ttl",
|
||||||
|
Status: "passing",
|
||||||
|
EnterpriseMeta: pbcommon.EnterpriseMeta{},
|
||||||
|
RaftIndex: pbcommon.RaftIndex{
|
||||||
|
CreateIndex: index,
|
||||||
|
ModifyIndex: index,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEventServiceHealthDeregister returns a realistically populated service
|
||||||
|
// health deregistration event for tests. The nodeNum is a
|
||||||
|
// logical node and is used to create the node name ("node%d") but also change
|
||||||
|
// the node ID and IP address to make it a little more realistic for cases that
|
||||||
|
// need that. nodeNum should be less than 64k to make the IP address look
|
||||||
|
// realistic. Any other changes can be made on the returned event to avoid
|
||||||
|
// adding too many options to callers.
|
||||||
|
func newEventServiceHealthDeregister(index uint64, nodeNum int, svc string) pbsubscribe.Event {
|
||||||
|
node := fmt.Sprintf("node%d", nodeNum)
|
||||||
|
|
||||||
|
return pbsubscribe.Event{
|
||||||
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
||||||
|
Key: svc,
|
||||||
|
Index: index,
|
||||||
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
||||||
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
||||||
|
Op: pbsubscribe.CatalogOp_Deregister,
|
||||||
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
||||||
|
Node: &pbservice.Node{
|
||||||
|
Node: node,
|
||||||
|
},
|
||||||
|
Service: &pbservice.NodeService{
|
||||||
|
ID: svc,
|
||||||
|
Service: svc,
|
||||||
|
Port: 8080,
|
||||||
|
Weights: &pbservice.Weights{
|
||||||
|
Passing: 1,
|
||||||
|
Warning: 1,
|
||||||
|
},
|
||||||
|
// Empty sadness
|
||||||
|
Proxy: pbservice.ConnectProxyConfig{
|
||||||
|
MeshGateway: pbservice.MeshGatewayConfig{},
|
||||||
|
Expose: pbservice.ExposeConfig{},
|
||||||
|
},
|
||||||
|
EnterpriseMeta: pbcommon.EnterpriseMeta{},
|
||||||
|
RaftIndex: pbcommon.RaftIndex{
|
||||||
|
// The original insertion index since a delete doesn't update
|
||||||
|
// this. This magic value came from state store tests where we
|
||||||
|
// setup at index 10 and then mutate at index 100. It can be
|
||||||
|
// modified by the caller later and makes it easier than having
|
||||||
|
// yet another argument in the common case.
|
||||||
|
CreateIndex: 10,
|
||||||
|
ModifyIndex: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEventBatchWithEvents(first pbsubscribe.Event, evs ...pbsubscribe.Event) pbsubscribe.Event {
|
||||||
|
events := make([]*pbsubscribe.Event, len(evs)+1)
|
||||||
|
events[0] = &first
|
||||||
|
for i := range evs {
|
||||||
|
events[i+1] = &evs[i]
|
||||||
|
}
|
||||||
|
return pbsubscribe.Event{
|
||||||
|
Topic: first.Topic,
|
||||||
|
Index: first.Index,
|
||||||
|
Payload: &pbsubscribe.Event_EventBatch{
|
||||||
|
EventBatch: &pbsubscribe.EventBatch{Events: events},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,11 +3,13 @@ package cachetype
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/agentpb"
|
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
|
||||||
"github.com/hashicorp/go-bexpr"
|
"github.com/hashicorp/go-bexpr"
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/proto/pbservice"
|
||||||
|
"github.com/hashicorp/consul/proto/pbsubscribe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -40,18 +42,20 @@ func (c *StreamingHealthServices) Fetch(opts cache.FetchOptions, req cache.Reque
|
||||||
"Internal cache failure: request wrong type: %T", req)
|
"Internal cache failure: request wrong type: %T", req)
|
||||||
}
|
}
|
||||||
|
|
||||||
r := agentpb.SubscribeRequest{
|
r := Request{
|
||||||
Topic: agentpb.Topic_ServiceHealth,
|
SubscribeRequest: pbsubscribe.SubscribeRequest{
|
||||||
Key: reqReal.ServiceName,
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
||||||
Token: reqReal.Token,
|
Key: reqReal.ServiceName,
|
||||||
Index: reqReal.MinQueryIndex,
|
Token: reqReal.Token,
|
||||||
Filter: reqReal.Filter,
|
Index: reqReal.MinQueryIndex,
|
||||||
Datacenter: reqReal.Datacenter,
|
Datacenter: reqReal.Datacenter,
|
||||||
|
},
|
||||||
|
Filter: reqReal.Filter,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect requests need a different topic
|
// Connect requests need a different topic
|
||||||
if reqReal.Connect {
|
if reqReal.Connect {
|
||||||
r.Topic = agentpb.Topic_ServiceHealthConnect
|
r.Topic = pbsubscribe.Topic_ServiceHealthConnect
|
||||||
}
|
}
|
||||||
|
|
||||||
view := MaterializedViewFromFetch(c, opts, r)
|
view := MaterializedViewFromFetch(c, opts, r)
|
||||||
|
@ -105,7 +109,7 @@ func (s *healthViewState) InitFilter(expression string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update implements MaterializedViewState
|
// Update implements MaterializedViewState
|
||||||
func (s *healthViewState) Update(events []*agentpb.Event) error {
|
func (s *healthViewState) Update(events []*pbsubscribe.Event) error {
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
serviceHealth := event.GetServiceHealth()
|
serviceHealth := event.GetServiceHealth()
|
||||||
if serviceHealth == nil {
|
if serviceHealth == nil {
|
||||||
|
@ -116,13 +120,10 @@ func (s *healthViewState) Update(events []*agentpb.Event) error {
|
||||||
id := fmt.Sprintf("%s/%s", node.Node.Node, node.Service.ID)
|
id := fmt.Sprintf("%s/%s", node.Node.Node, node.Service.ID)
|
||||||
|
|
||||||
switch serviceHealth.Op {
|
switch serviceHealth.Op {
|
||||||
case agentpb.CatalogOp_Register:
|
case pbsubscribe.CatalogOp_Register:
|
||||||
checkServiceNode, err := serviceHealth.CheckServiceNode.ToStructs()
|
checkServiceNode := pbservice.CheckServiceNodeToStructs(serviceHealth.CheckServiceNode)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.state[id] = *checkServiceNode
|
s.state[id] = *checkServiceNode
|
||||||
case agentpb.CatalogOp_Deregister:
|
case pbsubscribe.CatalogOp_Deregister:
|
||||||
delete(s.state, id)
|
delete(s.state, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,14 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/agentpb"
|
"github.com/hashicorp/go-hclog"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/consul/proto/pbsubscribe"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
@ -23,8 +25,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
// Initially there are no services registered. Server should send an
|
// Initially there are no services registered. Server should send an
|
||||||
// EndOfSnapshot message immediately with index of 1.
|
// EndOfSnapshot message immediately with index of 1.
|
||||||
eosEv := agentpb.TestEventEndOfSnapshot(t, agentpb.Topic_ServiceHealth, 1)
|
client.QueueEvents(newEndOfSnapshotEvent(pbsubscribe.Topic_ServiceHealth, 1))
|
||||||
client.QueueEvents(&eosEv)
|
|
||||||
|
|
||||||
// This contains the view state so important we share it between calls.
|
// This contains the view state so important we share it between calls.
|
||||||
opts := cache.FetchOptions{
|
opts := cache.FetchOptions{
|
||||||
|
@ -42,7 +43,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
require.True(t, t.Run("empty snapshot returned", func(t *testing.T) {
|
runStep(t, "empty snapshot returned", func(t *testing.T) {
|
||||||
// Fetch should return an empty
|
// Fetch should return an empty
|
||||||
// result of the right type with a non-zero index, and in the background begin
|
// result of the right type with a non-zero index, and in the background begin
|
||||||
// streaming updates.
|
// streaming updates.
|
||||||
|
@ -54,9 +55,9 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("blocks for timeout", func(t *testing.T) {
|
runStep(t, "blocks for timeout", func(t *testing.T) {
|
||||||
// Subsequent fetch should block for the timeout
|
// Subsequent fetch should block for the timeout
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
opts.Timeout = 200 * time.Millisecond
|
opts.Timeout = 200 * time.Millisecond
|
||||||
|
@ -71,9 +72,9 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("blocks until update", func(t *testing.T) {
|
runStep(t, "blocks until update", func(t *testing.T) {
|
||||||
// Make another blocking query with a longer timeout and trigger an update
|
// Make another blocking query with a longer timeout and trigger an update
|
||||||
// event part way through.
|
// event part way through.
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -81,7 +82,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// Then a service registers
|
// Then a service registers
|
||||||
healthEv := agentpb.TestEventServiceHealthRegister(t, 4, 1, "web")
|
healthEv := newEventServiceHealthRegister(4, 1, "web")
|
||||||
client.QueueEvents(&healthEv)
|
client.QueueEvents(&healthEv)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -100,17 +101,16 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("reconnects and resumes after transient stream error", func(t *testing.T) {
|
runStep(t, "reconnects and resumes after transient stream error", func(t *testing.T) {
|
||||||
// Use resetErr just because it's "temporary" this is a stand in for any
|
// Use resetErr just because it's "temporary" this is a stand in for any
|
||||||
// network error that uses that same interface though.
|
// network error that uses that same interface though.
|
||||||
client.QueueErr(resetErr("broken pipe"))
|
client.QueueErr(resetErr("broken pipe"))
|
||||||
|
|
||||||
// After the error the view should re-subscribe with same index so will get
|
// After the error the view should re-subscribe with same index so will get
|
||||||
// a "resume stream".
|
// a "resume stream".
|
||||||
resumeEv := agentpb.TestEventResumeStream(t, agentpb.Topic_ServiceHealth, opts.MinIndex)
|
client.QueueEvents(newEndOfEmptySnapshotEvent(pbsubscribe.Topic_ServiceHealth, opts.MinIndex))
|
||||||
client.QueueEvents(&resumeEv)
|
|
||||||
|
|
||||||
// Next fetch will continue to block until timeout and receive the same
|
// Next fetch will continue to block until timeout and receive the same
|
||||||
// result.
|
// result.
|
||||||
|
@ -129,7 +129,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
|
|
||||||
// But an update should still be noticed due to reconnection
|
// But an update should still be noticed due to reconnection
|
||||||
healthEv := agentpb.TestEventServiceHealthRegister(t, 10, 2, "web")
|
healthEv := newEventServiceHealthRegister(10, 2, "web")
|
||||||
client.QueueEvents(&healthEv)
|
client.QueueEvents(&healthEv)
|
||||||
|
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
|
@ -146,9 +146,9 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("returns non-temporary error to watchers", func(t *testing.T) {
|
runStep(t, "returns non-temporary error to watchers", func(t *testing.T) {
|
||||||
// Wait and send the error while fetcher is waiting
|
// Wait and send the error while fetcher is waiting
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
@ -156,8 +156,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
// After the error the view should re-subscribe with same index so will get
|
// After the error the view should re-subscribe with same index so will get
|
||||||
// a "resume stream".
|
// a "resume stream".
|
||||||
resumeEv := agentpb.TestEventResumeStream(t, agentpb.Topic_ServiceHealth, opts.MinIndex)
|
client.QueueEvents(newEndOfEmptySnapshotEvent(pbsubscribe.Topic_ServiceHealth, opts.MinIndex))
|
||||||
client.QueueEvents(&resumeEv)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Next fetch should return the error
|
// Next fetch should return the error
|
||||||
|
@ -183,7 +182,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
|
|
||||||
// But an update should still be noticed due to reconnection
|
// But an update should still be noticed due to reconnection
|
||||||
healthEv := agentpb.TestEventServiceHealthRegister(t, opts.MinIndex+5, 3, "web")
|
healthEv := newEventServiceHealthRegister(opts.MinIndex+5, 3, "web")
|
||||||
client.QueueEvents(&healthEv)
|
client.QueueEvents(&healthEv)
|
||||||
|
|
||||||
opts.Timeout = time.Second
|
opts.Timeout = time.Second
|
||||||
|
@ -199,7 +198,7 @@ func TestStreamingHealthServices_EmptySnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireResultsSame compares two IndexedCheckServiceNodes without requiring
|
// requireResultsSame compares two IndexedCheckServiceNodes without requiring
|
||||||
|
@ -229,17 +228,15 @@ func TestStreamingHealthServices_FullSnapshot(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an initial snapshot of 3 instances on different nodes
|
// Create an initial snapshot of 3 instances on different nodes
|
||||||
makeReg := func(index uint64, nodeNum int) *agentpb.Event {
|
makeReg := func(index uint64, nodeNum int) *pbsubscribe.Event {
|
||||||
e := agentpb.TestEventServiceHealthRegister(t, index, nodeNum, "web")
|
e := newEventServiceHealthRegister(index, nodeNum, "web")
|
||||||
return &e
|
return &e
|
||||||
}
|
}
|
||||||
eosEv := agentpb.TestEventEndOfSnapshot(t, agentpb.Topic_ServiceHealth, 5)
|
|
||||||
client.QueueEvents(
|
client.QueueEvents(
|
||||||
makeReg(5, 1),
|
makeReg(5, 1),
|
||||||
makeReg(5, 2),
|
makeReg(5, 2),
|
||||||
makeReg(5, 3),
|
makeReg(5, 3),
|
||||||
&eosEv,
|
newEndOfSnapshotEvent(pbsubscribe.Topic_ServiceHealth, 5))
|
||||||
)
|
|
||||||
|
|
||||||
// This contains the view state so important we share it between calls.
|
// This contains the view state so important we share it between calls.
|
||||||
opts := cache.FetchOptions{
|
opts := cache.FetchOptions{
|
||||||
|
@ -260,7 +257,7 @@ func TestStreamingHealthServices_FullSnapshot(t *testing.T) {
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
require.True(t, t.Run("full snapshot returned", func(t *testing.T) {
|
runStep(t, "full snapshot returned", func(t *testing.T) {
|
||||||
result, err := typ.Fetch(opts, req)
|
result, err := typ.Fetch(opts, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -270,9 +267,9 @@ func TestStreamingHealthServices_FullSnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("blocks until deregistration", func(t *testing.T) {
|
runStep(t, "blocks until deregistration", func(t *testing.T) {
|
||||||
// Make another blocking query with a longer timeout and trigger an update
|
// Make another blocking query with a longer timeout and trigger an update
|
||||||
// event part way through.
|
// event part way through.
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -280,7 +277,7 @@ func TestStreamingHealthServices_FullSnapshot(t *testing.T) {
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
// Deregister instance on node1
|
// Deregister instance on node1
|
||||||
healthEv := agentpb.TestEventServiceHealthDeregister(t, 20, 1, "web")
|
healthEv := newEventServiceHealthDeregister(20, 1, "web")
|
||||||
client.QueueEvents(&healthEv)
|
client.QueueEvents(&healthEv)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -299,21 +296,19 @@ func TestStreamingHealthServices_FullSnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("server reload is respected", func(t *testing.T) {
|
runStep(t, "server reload is respected", func(t *testing.T) {
|
||||||
// Simulates the server noticing the request's ACL token privs changing. To
|
// Simulates the server noticing the request's ACL token privs changing. To
|
||||||
// detect this we'll queue up the new snapshot as a different set of nodes
|
// detect this we'll queue up the new snapshot as a different set of nodes
|
||||||
// to the first.
|
// to the first.
|
||||||
resetEv := agentpb.TestEventResetStream(t, agentpb.Topic_ServiceHealth, 45)
|
client.QueueErr(status.Error(codes.Aborted, "reset by server"))
|
||||||
eosEv := agentpb.TestEventEndOfSnapshot(t, agentpb.Topic_ServiceHealth, 50)
|
|
||||||
client.QueueEvents(
|
client.QueueEvents(
|
||||||
&resetEv,
|
|
||||||
makeReg(50, 3), // overlap existing node
|
makeReg(50, 3), // overlap existing node
|
||||||
makeReg(50, 4),
|
makeReg(50, 4),
|
||||||
makeReg(50, 5),
|
makeReg(50, 5),
|
||||||
&eosEv,
|
newEndOfSnapshotEvent(pbsubscribe.Topic_ServiceHealth, 50))
|
||||||
)
|
|
||||||
|
|
||||||
// Make another blocking query with THE SAME index. It should immediately
|
// Make another blocking query with THE SAME index. It should immediately
|
||||||
// return the new snapshot.
|
// return the new snapshot.
|
||||||
|
@ -331,7 +326,7 @@ func TestStreamingHealthServices_FullSnapshot(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStreamingHealthServices_EventBatches(t *testing.T) {
|
func TestStreamingHealthServices_EventBatches(t *testing.T) {
|
||||||
|
@ -342,17 +337,13 @@ func TestStreamingHealthServices_EventBatches(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an initial snapshot of 3 instances but in a single event batch
|
// Create an initial snapshot of 3 instances but in a single event batch
|
||||||
batchEv := agentpb.TestEventBatchWithEvents(t,
|
batchEv := newEventBatchWithEvents(
|
||||||
agentpb.TestEventServiceHealthRegister(t, 5, 1, "web"),
|
newEventServiceHealthRegister(5, 1, "web"),
|
||||||
agentpb.TestEventServiceHealthRegister(t, 5, 2, "web"),
|
newEventServiceHealthRegister(5, 2, "web"),
|
||||||
agentpb.TestEventServiceHealthRegister(t, 5, 3, "web"),
|
newEventServiceHealthRegister(5, 3, "web"))
|
||||||
)
|
|
||||||
eosEv := agentpb.TestEventEndOfSnapshot(t, agentpb.Topic_ServiceHealth, 5)
|
|
||||||
|
|
||||||
client.QueueEvents(
|
client.QueueEvents(
|
||||||
&batchEv,
|
&batchEv,
|
||||||
&eosEv,
|
newEndOfSnapshotEvent(pbsubscribe.Topic_ServiceHealth, 5))
|
||||||
)
|
|
||||||
|
|
||||||
// This contains the view state so important we share it between calls.
|
// This contains the view state so important we share it between calls.
|
||||||
opts := cache.FetchOptions{
|
opts := cache.FetchOptions{
|
||||||
|
@ -373,7 +364,7 @@ func TestStreamingHealthServices_EventBatches(t *testing.T) {
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
require.True(t, t.Run("full snapshot returned", func(t *testing.T) {
|
runStep(t, "full snapshot returned", func(t *testing.T) {
|
||||||
result, err := typ.Fetch(opts, req)
|
result, err := typ.Fetch(opts, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -383,16 +374,16 @@ func TestStreamingHealthServices_EventBatches(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("batched updates work too", func(t *testing.T) {
|
runStep(t, "batched updates work too", func(t *testing.T) {
|
||||||
// Simulate multiple registrations happening in one Txn (so all have same
|
// Simulate multiple registrations happening in one Txn (so all have same
|
||||||
// index)
|
// index)
|
||||||
batchEv := agentpb.TestEventBatchWithEvents(t,
|
batchEv := newEventBatchWithEvents(
|
||||||
// Deregister an existing node
|
// Deregister an existing node
|
||||||
agentpb.TestEventServiceHealthDeregister(t, 20, 1, "web"),
|
newEventServiceHealthDeregister(20, 1, "web"),
|
||||||
// Register another
|
// Register another
|
||||||
agentpb.TestEventServiceHealthRegister(t, 20, 4, "web"),
|
newEventServiceHealthRegister(20, 4, "web"),
|
||||||
)
|
)
|
||||||
client.QueueEvents(&batchEv)
|
client.QueueEvents(&batchEv)
|
||||||
opts.Timeout = time.Second
|
opts.Timeout = time.Second
|
||||||
|
@ -405,7 +396,7 @@ func TestStreamingHealthServices_EventBatches(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStreamingHealthServices_Filtering(t *testing.T) {
|
func TestStreamingHealthServices_Filtering(t *testing.T) {
|
||||||
|
@ -416,16 +407,13 @@ func TestStreamingHealthServices_Filtering(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an initial snapshot of 3 instances but in a single event batch
|
// Create an initial snapshot of 3 instances but in a single event batch
|
||||||
batchEv := agentpb.TestEventBatchWithEvents(t,
|
batchEv := newEventBatchWithEvents(
|
||||||
agentpb.TestEventServiceHealthRegister(t, 5, 1, "web"),
|
newEventServiceHealthRegister(5, 1, "web"),
|
||||||
agentpb.TestEventServiceHealthRegister(t, 5, 2, "web"),
|
newEventServiceHealthRegister(5, 2, "web"),
|
||||||
agentpb.TestEventServiceHealthRegister(t, 5, 3, "web"),
|
newEventServiceHealthRegister(5, 3, "web"))
|
||||||
)
|
|
||||||
eosEv := agentpb.TestEventEndOfSnapshot(t, agentpb.Topic_ServiceHealth, 5)
|
|
||||||
client.QueueEvents(
|
client.QueueEvents(
|
||||||
&batchEv,
|
&batchEv,
|
||||||
&eosEv,
|
newEndOfSnapshotEvent(pbsubscribe.Topic_ServiceHealth, 5))
|
||||||
)
|
|
||||||
|
|
||||||
// This contains the view state so important we share it between calls.
|
// This contains the view state so important we share it between calls.
|
||||||
opts := cache.FetchOptions{
|
opts := cache.FetchOptions{
|
||||||
|
@ -449,7 +437,7 @@ func TestStreamingHealthServices_Filtering(t *testing.T) {
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
require.True(t, t.Run("filtered snapshot returned", func(t *testing.T) {
|
runStep(t, "filtered snapshot returned", func(t *testing.T) {
|
||||||
result, err := typ.Fetch(opts, req)
|
result, err := typ.Fetch(opts, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -459,16 +447,16 @@ func TestStreamingHealthServices_Filtering(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
|
||||||
require.True(t, t.Run("filtered updates work too", func(t *testing.T) {
|
runStep(t, "filtered updates work too", func(t *testing.T) {
|
||||||
// Simulate multiple registrations happening in one Txn (so all have same
|
// Simulate multiple registrations happening in one Txn (so all have same
|
||||||
// index)
|
// index)
|
||||||
batchEv := agentpb.TestEventBatchWithEvents(t,
|
batchEv := newEventBatchWithEvents(
|
||||||
// Deregister an existing node
|
// Deregister an existing node
|
||||||
agentpb.TestEventServiceHealthDeregister(t, 20, 1, "web"),
|
newEventServiceHealthDeregister(20, 1, "web"),
|
||||||
// Register another
|
// Register another
|
||||||
agentpb.TestEventServiceHealthRegister(t, 20, 4, "web"),
|
newEventServiceHealthRegister(20, 4, "web"),
|
||||||
)
|
)
|
||||||
client.QueueEvents(&batchEv)
|
client.QueueEvents(&batchEv)
|
||||||
opts.Timeout = time.Second
|
opts.Timeout = time.Second
|
||||||
|
@ -481,5 +469,11 @@ func TestStreamingHealthServices_Filtering(t *testing.T) {
|
||||||
|
|
||||||
opts.MinIndex = result.Index
|
opts.MinIndex = result.Index
|
||||||
opts.LastResult = &result
|
opts.LastResult = &result
|
||||||
}))
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runStep(t *testing.T, name string, fn func(t *testing.T)) {
|
||||||
|
if !t.Run(name, fn) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,14 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/agentpb"
|
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
|
||||||
"github.com/hashicorp/consul/lib"
|
|
||||||
"github.com/hashicorp/go-hclog"
|
"github.com/hashicorp/go-hclog"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
"github.com/hashicorp/consul/lib"
|
||||||
|
"github.com/hashicorp/consul/proto/pbsubscribe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,7 +25,7 @@ const (
|
||||||
// StreamingClient is the interface we need from the gRPC client stub. Separate
|
// StreamingClient is the interface we need from the gRPC client stub. Separate
|
||||||
// interface simplifies testing.
|
// interface simplifies testing.
|
||||||
type StreamingClient interface {
|
type StreamingClient interface {
|
||||||
Subscribe(ctx context.Context, in *agentpb.SubscribeRequest, opts ...grpc.CallOption) (agentpb.Consul_SubscribeClient, error)
|
Subscribe(ctx context.Context, in *pbsubscribe.SubscribeRequest, opts ...grpc.CallOption) (pbsubscribe.StateChangeSubscription_SubscribeClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaterializedViewState is the interface used to manage they type-specific
|
// MaterializedViewState is the interface used to manage they type-specific
|
||||||
|
@ -42,7 +45,7 @@ type MaterializedViewState interface {
|
||||||
// include _all_ events in the initial snapshot which may be an empty set.
|
// include _all_ events in the initial snapshot which may be an empty set.
|
||||||
// Subsequent calls will contain one or more update events in the order they
|
// Subsequent calls will contain one or more update events in the order they
|
||||||
// are received.
|
// are received.
|
||||||
Update(events []*agentpb.Event) error
|
Update(events []*pbsubscribe.Event) error
|
||||||
|
|
||||||
// Result returns the type-specific cache result based on the state. When no
|
// Result returns the type-specific cache result based on the state. When no
|
||||||
// events have been delivered yet the result should be an empty value type
|
// events have been delivered yet the result should be an empty value type
|
||||||
|
@ -83,6 +86,14 @@ func (e resetErr) Error() string {
|
||||||
return string(e)
|
return string(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
pbsubscribe.SubscribeRequest
|
||||||
|
// Filter is a bexpr filter expression that is used to filter events on the
|
||||||
|
// client side.
|
||||||
|
// TODO: is this used?
|
||||||
|
Filter string
|
||||||
|
}
|
||||||
|
|
||||||
// MaterializedView is a partial view of the state on servers, maintained via
|
// MaterializedView is a partial view of the state on servers, maintained via
|
||||||
// streaming subscriptions. It is specialized for different cache types by
|
// streaming subscriptions. It is specialized for different cache types by
|
||||||
// providing a MaterializedViewState that encapsulates the logic to update the
|
// providing a MaterializedViewState that encapsulates the logic to update the
|
||||||
|
@ -97,7 +108,7 @@ type MaterializedView struct {
|
||||||
typ StreamingCacheType
|
typ StreamingCacheType
|
||||||
client StreamingClient
|
client StreamingClient
|
||||||
logger hclog.Logger
|
logger hclog.Logger
|
||||||
req agentpb.SubscribeRequest
|
req Request
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelFunc func()
|
cancelFunc func()
|
||||||
|
|
||||||
|
@ -120,9 +131,11 @@ type MaterializedView struct {
|
||||||
// the cache evicts the result. If the view is not returned in a result state
|
// the cache evicts the result. If the view is not returned in a result state
|
||||||
// though Close must be called some other way to avoid leaking the goroutine and
|
// though Close must be called some other way to avoid leaking the goroutine and
|
||||||
// memory.
|
// memory.
|
||||||
func MaterializedViewFromFetch(t StreamingCacheType, opts cache.FetchOptions,
|
func MaterializedViewFromFetch(
|
||||||
subReq agentpb.SubscribeRequest) *MaterializedView {
|
t StreamingCacheType,
|
||||||
|
opts cache.FetchOptions,
|
||||||
|
subReq Request,
|
||||||
|
) *MaterializedView {
|
||||||
if opts.LastResult == nil || opts.LastResult.State == nil {
|
if opts.LastResult == nil || opts.LastResult.State == nil {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
v := &MaterializedView{
|
v := &MaterializedView{
|
||||||
|
@ -225,26 +238,21 @@ func (v *MaterializedView) runSubscription() error {
|
||||||
|
|
||||||
v.l.Unlock()
|
v.l.Unlock()
|
||||||
|
|
||||||
s, err := v.client.Subscribe(ctx, &req)
|
s, err := v.client.Subscribe(ctx, &req.SubscribeRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotEvents := make([]*agentpb.Event, 0)
|
snapshotEvents := make([]*pbsubscribe.Event, 0)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
event, err := s.Recv()
|
event, err := s.Recv()
|
||||||
if err != nil {
|
switch {
|
||||||
return err
|
case isGrpcStatus(err, codes.Aborted):
|
||||||
}
|
|
||||||
|
|
||||||
if event.GetResetStream() {
|
|
||||||
// Server has requested we reset the view and start with a fresh snapshot
|
|
||||||
// - perhaps because our ACL policy changed. We reset the view state and
|
|
||||||
// then return an error to allow the `run` method to retry after a backoff
|
|
||||||
// if necessary.
|
|
||||||
v.reset()
|
v.reset()
|
||||||
return resetErr("stream reset requested")
|
return resetErr("stream reset requested")
|
||||||
|
case err != nil:
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.GetEndOfSnapshot() {
|
if event.GetEndOfSnapshot() {
|
||||||
|
@ -276,7 +284,7 @@ func (v *MaterializedView) runSubscription() error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.GetResumeStream() {
|
if event.GetEndOfEmptySnapshot() {
|
||||||
// We've opened a new subscribe with a non-zero index to resume a
|
// We've opened a new subscribe with a non-zero index to resume a
|
||||||
// connection and the server confirms it's not sending a new snapshot.
|
// connection and the server confirms it's not sending a new snapshot.
|
||||||
if !snapshotDone {
|
if !snapshotDone {
|
||||||
|
@ -291,7 +299,7 @@ func (v *MaterializedView) runSubscription() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have an event for the topic
|
// We have an event for the topic
|
||||||
events := []*agentpb.Event{event}
|
events := []*pbsubscribe.Event{event}
|
||||||
|
|
||||||
// If the event is a batch, unwrap and deliver the raw events
|
// If the event is a batch, unwrap and deliver the raw events
|
||||||
if batch := event.GetEventBatch(); batch != nil {
|
if batch := event.GetEventBatch(); batch != nil {
|
||||||
|
@ -322,6 +330,11 @@ func (v *MaterializedView) runSubscription() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isGrpcStatus(err error, code codes.Code) bool {
|
||||||
|
s, ok := status.FromError(err)
|
||||||
|
return ok && s.Code() == code
|
||||||
|
}
|
||||||
|
|
||||||
// reset clears the state ready to start a new stream from scratch.
|
// reset clears the state ready to start a new stream from scratch.
|
||||||
func (v *MaterializedView) reset() {
|
func (v *MaterializedView) reset() {
|
||||||
v.l.Lock()
|
v.l.Lock()
|
||||||
|
|
|
@ -3,9 +3,10 @@ package cachetype
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/agentpb"
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/proto/pbsubscribe"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestStreamingClient is a mock StreamingClient for testing that allows
|
// TestStreamingClient is a mock StreamingClient for testing that allows
|
||||||
|
@ -17,7 +18,7 @@ type TestStreamingClient struct {
|
||||||
|
|
||||||
type eventOrErr struct {
|
type eventOrErr struct {
|
||||||
Err error
|
Err error
|
||||||
Event *agentpb.Event
|
Event *pbsubscribe.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTestStreamingClient() *TestStreamingClient {
|
func NewTestStreamingClient() *TestStreamingClient {
|
||||||
|
@ -26,13 +27,16 @@ func NewTestStreamingClient() *TestStreamingClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestStreamingClient) Subscribe(ctx context.Context, in *agentpb.SubscribeRequest, opts ...grpc.CallOption) (agentpb.Consul_SubscribeClient, error) {
|
func (t *TestStreamingClient) Subscribe(
|
||||||
|
ctx context.Context,
|
||||||
|
_ *pbsubscribe.SubscribeRequest,
|
||||||
|
_ ...grpc.CallOption,
|
||||||
|
) (pbsubscribe.StateChangeSubscription_SubscribeClient, error) {
|
||||||
t.ctx = ctx
|
t.ctx = ctx
|
||||||
|
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestStreamingClient) QueueEvents(events ...*agentpb.Event) {
|
func (t *TestStreamingClient) QueueEvents(events ...*pbsubscribe.Event) {
|
||||||
for _, e := range events {
|
for _, e := range events {
|
||||||
t.events <- eventOrErr{Event: e}
|
t.events <- eventOrErr{Event: e}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +46,7 @@ func (t *TestStreamingClient) QueueErr(err error) {
|
||||||
t.events <- eventOrErr{Err: err}
|
t.events <- eventOrErr{Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestStreamingClient) Recv() (*agentpb.Event, error) {
|
func (t *TestStreamingClient) Recv() (*pbsubscribe.Event, error) {
|
||||||
select {
|
select {
|
||||||
case eoe := <-t.events:
|
case eoe := <-t.events:
|
||||||
if eoe.Err != nil {
|
if eoe.Err != nil {
|
||||||
|
|
|
@ -856,11 +856,12 @@ func TestCacheGet_expireClose(t *testing.T) {
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
typ := TestType(t)
|
typ := &MockType{}
|
||||||
defer typ.AssertExpectations(t)
|
defer typ.AssertExpectations(t)
|
||||||
c := New(Options{})
|
c := New(Options{})
|
||||||
typ.On("RegisterOptions").Return(RegisterOptions{
|
typ.On("RegisterOptions").Return(RegisterOptions{
|
||||||
LastGetTTL: 100 * time.Millisecond,
|
SupportsBlocking: true,
|
||||||
|
LastGetTTL: 100 * time.Millisecond,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Register the type with a timeout
|
// Register the type with a timeout
|
||||||
|
|
Loading…
Reference in New Issue