2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2022-07-08 17:01:13 +00:00
|
|
|
package peerstream
|
2022-05-19 19:21:44 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/stretchr/testify/require"
|
2023-01-11 14:39:10 +00:00
|
|
|
"google.golang.org/protobuf/proto"
|
2022-05-19 19:21:44 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
|
|
"github.com/hashicorp/consul/agent/cache"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2023-02-17 21:14:46 +00:00
|
|
|
"github.com/hashicorp/consul/proto/private/pbservice"
|
2022-05-19 19:21:44 +00:00
|
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestSubscriptionState_Events(t *testing.T) {
|
|
|
|
logger := hclog.NewNullLogger()
|
|
|
|
|
|
|
|
partition := acl.DefaultEnterpriseMeta().PartitionOrEmpty()
|
|
|
|
|
2022-05-25 17:37:44 +00:00
|
|
|
state := newSubscriptionState("my-peering", partition)
|
2022-05-19 19:21:44 +00:00
|
|
|
|
|
|
|
testutil.RunStep(t, "empty", func(t *testing.T) {
|
|
|
|
pending := &pendingPayload{}
|
|
|
|
|
|
|
|
ch := make(chan cache.UpdateEvent, 1)
|
|
|
|
state.publicUpdateCh = ch
|
|
|
|
go func() {
|
|
|
|
state.sendPendingEvents(context.Background(), logger, pending)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
got := drainEvents(t, ch)
|
|
|
|
require.Len(t, got, 0)
|
|
|
|
})
|
|
|
|
|
|
|
|
meshNode1 := &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{Node: "foo"},
|
|
|
|
Service: &pbservice.NodeService{ID: "mgw-1", Service: "mgw", Kind: "mesh-gateway"},
|
|
|
|
}
|
|
|
|
|
|
|
|
testutil.RunStep(t, "one", func(t *testing.T) {
|
|
|
|
pending := &pendingPayload{}
|
|
|
|
require.NoError(t, pending.Add(
|
|
|
|
meshGatewayPayloadID,
|
|
|
|
subMeshGateway+partition,
|
|
|
|
&pbservice.IndexedCheckServiceNodes{
|
|
|
|
Nodes: []*pbservice.CheckServiceNode{
|
|
|
|
proto.Clone(meshNode1).(*pbservice.CheckServiceNode),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
))
|
|
|
|
|
|
|
|
ch := make(chan cache.UpdateEvent, 1)
|
|
|
|
state.publicUpdateCh = ch
|
|
|
|
go func() {
|
|
|
|
state.sendPendingEvents(context.Background(), logger, pending)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
got := drainEvents(t, ch)
|
|
|
|
require.Len(t, got, 1)
|
|
|
|
|
|
|
|
evt := got[0]
|
|
|
|
require.Equal(t, subMeshGateway+partition, evt.CorrelationID)
|
|
|
|
require.Len(t, evt.Result.(*pbservice.IndexedCheckServiceNodes).Nodes, 1)
|
|
|
|
})
|
|
|
|
|
|
|
|
testutil.RunStep(t, "a duplicate is omitted", func(t *testing.T) {
|
|
|
|
pending := &pendingPayload{}
|
|
|
|
require.NoError(t, pending.Add(
|
|
|
|
meshGatewayPayloadID,
|
|
|
|
subMeshGateway+partition,
|
|
|
|
&pbservice.IndexedCheckServiceNodes{
|
|
|
|
Nodes: []*pbservice.CheckServiceNode{
|
|
|
|
proto.Clone(meshNode1).(*pbservice.CheckServiceNode),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
))
|
|
|
|
|
|
|
|
ch := make(chan cache.UpdateEvent, 1)
|
|
|
|
state.publicUpdateCh = ch
|
|
|
|
go func() {
|
|
|
|
state.sendPendingEvents(context.Background(), logger, pending)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
got := drainEvents(t, ch)
|
|
|
|
require.Len(t, got, 0)
|
|
|
|
})
|
|
|
|
|
|
|
|
webNode1 := &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{Node: "zim"},
|
|
|
|
Service: &pbservice.NodeService{ID: "web-1", Service: "web"},
|
|
|
|
}
|
|
|
|
|
|
|
|
webSN := structs.NewServiceName("web", nil)
|
|
|
|
|
|
|
|
testutil.RunStep(t, "a duplicate is omitted even if mixed", func(t *testing.T) {
|
|
|
|
pending := &pendingPayload{}
|
|
|
|
require.NoError(t, pending.Add(
|
|
|
|
meshGatewayPayloadID,
|
|
|
|
subMeshGateway+partition,
|
|
|
|
&pbservice.IndexedCheckServiceNodes{
|
|
|
|
Nodes: []*pbservice.CheckServiceNode{
|
|
|
|
proto.Clone(meshNode1).(*pbservice.CheckServiceNode),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
))
|
|
|
|
require.NoError(t, pending.Add(
|
|
|
|
servicePayloadIDPrefix+webSN.String(),
|
|
|
|
subExportedService+webSN.String(),
|
|
|
|
&pbservice.IndexedCheckServiceNodes{
|
|
|
|
Nodes: []*pbservice.CheckServiceNode{
|
|
|
|
proto.Clone(webNode1).(*pbservice.CheckServiceNode),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
))
|
|
|
|
|
|
|
|
ch := make(chan cache.UpdateEvent, 1)
|
|
|
|
state.publicUpdateCh = ch
|
|
|
|
go func() {
|
|
|
|
state.sendPendingEvents(context.Background(), logger, pending)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
got := drainEvents(t, ch)
|
|
|
|
require.Len(t, got, 1)
|
|
|
|
|
|
|
|
evt := got[0]
|
|
|
|
require.Equal(t, subExportedService+webSN.String(), evt.CorrelationID)
|
|
|
|
require.Len(t, evt.Result.(*pbservice.IndexedCheckServiceNodes).Nodes, 1)
|
|
|
|
})
|
|
|
|
|
|
|
|
meshNode2 := &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{Node: "bar"},
|
|
|
|
Service: &pbservice.NodeService{ID: "mgw-2", Service: "mgw", Kind: "mesh-gateway"},
|
|
|
|
}
|
|
|
|
|
|
|
|
testutil.RunStep(t, "an update to an existing item is published", func(t *testing.T) {
|
|
|
|
pending := &pendingPayload{}
|
|
|
|
require.NoError(t, pending.Add(
|
|
|
|
meshGatewayPayloadID,
|
|
|
|
subMeshGateway+partition,
|
|
|
|
&pbservice.IndexedCheckServiceNodes{
|
|
|
|
Nodes: []*pbservice.CheckServiceNode{
|
|
|
|
proto.Clone(meshNode1).(*pbservice.CheckServiceNode),
|
|
|
|
proto.Clone(meshNode2).(*pbservice.CheckServiceNode),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
))
|
|
|
|
|
|
|
|
ch := make(chan cache.UpdateEvent, 1)
|
|
|
|
state.publicUpdateCh = ch
|
|
|
|
go func() {
|
|
|
|
state.sendPendingEvents(context.Background(), logger, pending)
|
|
|
|
close(ch)
|
|
|
|
}()
|
|
|
|
|
|
|
|
got := drainEvents(t, ch)
|
|
|
|
require.Len(t, got, 1)
|
|
|
|
|
|
|
|
evt := got[0]
|
|
|
|
require.Equal(t, subMeshGateway+partition, evt.CorrelationID)
|
|
|
|
require.Len(t, evt.Result.(*pbservice.IndexedCheckServiceNodes).Nodes, 2)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func drainEvents(t *testing.T, ch <-chan cache.UpdateEvent) []cache.UpdateEvent {
|
|
|
|
var out []cache.UpdateEvent
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case evt, ok := <-ch:
|
|
|
|
if !ok {
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
out = append(out, evt)
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
|
|
|
t.Fatalf("channel did not close in time")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func testNewSubscriptionState(partition string) (
|
|
|
|
*subscriptionState,
|
|
|
|
chan cache.UpdateEvent,
|
|
|
|
) {
|
|
|
|
var (
|
|
|
|
publicUpdateCh = make(chan cache.UpdateEvent, 1)
|
|
|
|
)
|
|
|
|
|
2022-05-25 17:37:44 +00:00
|
|
|
state := newSubscriptionState("my-peering", partition)
|
2022-05-19 19:21:44 +00:00
|
|
|
state.publicUpdateCh = publicUpdateCh
|
|
|
|
|
|
|
|
return state, publicUpdateCh
|
|
|
|
}
|