2023-03-28 18:39:22 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2023-08-11 13:12:13 +00:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-28 18:39:22 +00:00
|
|
|
|
2020-09-09 18:04:33 +00:00
|
|
|
package subscribe
|
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2024-03-22 20:59:54 +00:00
|
|
|
"fmt"
|
2020-09-25 23:40:10 +00:00
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2020-10-08 22:35:56 +00:00
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
2020-09-25 23:40:10 +00:00
|
|
|
"github.com/hashicorp/go-hclog"
|
2020-09-28 22:17:57 +00:00
|
|
|
"github.com/hashicorp/go-uuid"
|
2020-09-25 23:40:10 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
gogrpc "google.golang.org/grpc"
|
2020-09-28 22:17:57 +00:00
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
2023-01-11 14:39:10 +00:00
|
|
|
"google.golang.org/protobuf/types/known/durationpb"
|
2020-09-25 23:40:10 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/acl"
|
2022-12-13 20:09:55 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/rate"
|
2020-09-25 23:40:10 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/state"
|
|
|
|
"github.com/hashicorp/consul/agent/consul/stream"
|
2022-07-13 15:33:48 +00:00
|
|
|
grpc "github.com/hashicorp/consul/agent/grpc-internal"
|
2020-09-25 23:40:10 +00:00
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
2020-09-28 21:11:51 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2023-02-17 21:14:46 +00:00
|
|
|
"github.com/hashicorp/consul/proto/private/pbcommon"
|
|
|
|
"github.com/hashicorp/consul/proto/private/pbservice"
|
|
|
|
"github.com/hashicorp/consul/proto/private/pbsubscribe"
|
|
|
|
"github.com/hashicorp/consul/proto/private/prototest"
|
2022-05-10 20:25:51 +00:00
|
|
|
"github.com/hashicorp/consul/sdk/testutil"
|
2020-09-25 23:40:10 +00:00
|
|
|
"github.com/hashicorp/consul/types"
|
|
|
|
)
|
|
|
|
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
func TestServer_Subscribe_SubjectIsRequired(t *testing.T) {
|
2022-04-12 13:47:42 +00:00
|
|
|
backend := newTestBackend(t)
|
2022-01-28 12:27:00 +00:00
|
|
|
|
|
|
|
addr := runTestServer(t, NewServer(backend, hclog.New(nil)))
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2022-01-28 12:27:00 +00:00
|
|
|
conn, err := gogrpc.DialContext(ctx, addr.String(), gogrpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(logError(t, conn.Close))
|
|
|
|
|
|
|
|
client := pbsubscribe.NewStateChangeSubscriptionClient(conn)
|
|
|
|
|
|
|
|
stream, err := client.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
_, err = stream.Recv()
|
|
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
require.Contains(t, err.Error(), "either WildcardSubject or NamedSubject.Key is required")
|
2022-01-28 12:27:00 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
func TestServer_Subscribe_IntegrationWithBackend(t *testing.T) {
|
2022-04-12 13:47:42 +00:00
|
|
|
backend := newTestBackend(t)
|
2020-10-08 19:38:01 +00:00
|
|
|
addr := runTestServer(t, NewServer(backend, hclog.New(nil)))
|
2020-09-25 23:40:10 +00:00
|
|
|
ids := newCounter()
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
var req *structs.RegisterRequest
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "register two instances of the redis service", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
req = &structs.RegisterRequest{
|
|
|
|
Node: "node1",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
Port: 8080,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg2"), req))
|
|
|
|
|
|
|
|
req = &structs.RegisterRequest{
|
|
|
|
Node: "node2",
|
|
|
|
Address: "1.2.3.4",
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "1.1.1.1",
|
|
|
|
Port: 8080,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg3"), req))
|
|
|
|
})
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "register a service by a different name", func(t *testing.T) {
|
2020-09-09 18:04:33 +00:00
|
|
|
req := &structs.RegisterRequest{
|
|
|
|
Node: "other",
|
|
|
|
Address: "2.3.4.5",
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "api1",
|
|
|
|
Service: "api",
|
|
|
|
Address: "2.3.4.5",
|
|
|
|
Port: 9000,
|
|
|
|
},
|
|
|
|
}
|
2020-09-25 23:40:10 +00:00
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("other"), req))
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 19:43:29 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2020-09-25 23:40:10 +00:00
|
|
|
conn, err := gogrpc.DialContext(ctx, addr.String(), gogrpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(logError(t, conn.Close))
|
|
|
|
|
|
|
|
chEvents := make(chan eventOrError, 0)
|
2020-09-09 18:04:33 +00:00
|
|
|
var snapshotEvents []*pbsubscribe.Event
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "setup a client and subscribe to a topic", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
streamClient := pbsubscribe.NewStateChangeSubscriptionClient(conn)
|
|
|
|
streamHandle, err := streamClient.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
|
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
|
|
Key: "redis",
|
|
|
|
Namespace: pbcommon.DefaultEnterpriseMeta.Namespace,
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
go recvEvents(chEvents, streamHandle)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
snapshotEvents = append(snapshotEvents, getEvent(t, chEvents))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "receive the initial snapshot of events", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
expected := []*pbsubscribe.Event{
|
|
|
|
{
|
|
|
|
Index: ids.For("reg3"),
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{
|
|
|
|
Node: "node1",
|
2021-08-17 18:29:39 +00:00
|
|
|
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
|
2020-10-08 19:38:01 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
RaftIndex: raftIndex(ids, "reg2", "reg2"),
|
|
|
|
},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
Port: 8080,
|
|
|
|
Weights: &pbservice.Weights{Passing: 1, Warning: 1},
|
|
|
|
// Sad empty state
|
2022-03-23 16:10:03 +00:00
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2022-03-23 16:10:03 +00:00
|
|
|
Connect: &pbservice.ServiceConnect{},
|
2020-10-08 19:38:01 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg2", "reg2"),
|
2022-03-23 16:10:03 +00:00
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Index: ids.For("reg3"),
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{
|
|
|
|
Node: "node2",
|
2021-08-17 18:29:39 +00:00
|
|
|
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
|
2020-10-08 19:38:01 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Address: "1.2.3.4",
|
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
|
|
|
},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "1.1.1.1",
|
|
|
|
Port: 8080,
|
|
|
|
Weights: &pbservice.Weights{Passing: 1, Warning: 1},
|
|
|
|
// Sad empty state
|
2022-03-23 16:10:03 +00:00
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2022-03-23 16:10:03 +00:00
|
|
|
Connect: &pbservice.ServiceConnect{},
|
2020-10-08 19:38:01 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
2022-03-23 16:10:03 +00:00
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
{
|
|
|
|
Index: ids.For("reg3"),
|
|
|
|
Payload: &pbsubscribe.Event_EndOfSnapshot{EndOfSnapshot: true},
|
|
|
|
},
|
|
|
|
}
|
2022-03-30 16:51:56 +00:00
|
|
|
prototest.AssertDeepEqual(t, expected, snapshotEvents)
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "update the registration by adding a check", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
req.Check = &structs.HealthCheck{
|
|
|
|
Node: "node2",
|
|
|
|
CheckID: "check1",
|
|
|
|
ServiceID: "redis1",
|
|
|
|
ServiceName: "redis",
|
|
|
|
Name: "check 1",
|
|
|
|
}
|
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("update"), req))
|
|
|
|
|
|
|
|
event := getEvent(t, chEvents)
|
|
|
|
expectedEvent := &pbsubscribe.Event{
|
2020-09-25 23:40:10 +00:00
|
|
|
Index: ids.Last(),
|
2020-09-09 18:04:33 +00:00
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
2020-09-25 23:40:10 +00:00
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{
|
2020-09-09 18:04:33 +00:00
|
|
|
Node: "node2",
|
2021-08-17 18:29:39 +00:00
|
|
|
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
|
2020-09-09 18:04:33 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Address: "1.2.3.4",
|
2020-09-25 23:40:10 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-09-25 23:40:10 +00:00
|
|
|
Service: &pbservice.NodeService{
|
2020-09-09 18:04:33 +00:00
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "1.1.1.1",
|
|
|
|
Port: 8080,
|
2020-09-25 23:40:10 +00:00
|
|
|
Weights: &pbservice.Weights{Passing: 1, Warning: 1},
|
2020-09-09 18:04:33 +00:00
|
|
|
// Sad empty state
|
2022-03-23 16:10:03 +00:00
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2022-03-23 16:10:03 +00:00
|
|
|
Connect: &pbservice.ServiceConnect{},
|
2020-09-25 23:40:10 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
2022-03-23 16:10:03 +00:00
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
Checks: []*pbservice.HealthCheck{
|
|
|
|
{
|
|
|
|
CheckID: "check1",
|
|
|
|
Name: "check 1",
|
|
|
|
Node: "node2",
|
|
|
|
Status: "critical",
|
|
|
|
ServiceID: "redis1",
|
|
|
|
ServiceName: "redis",
|
|
|
|
RaftIndex: raftIndex(ids, "update", "update"),
|
2022-03-23 16:10:03 +00:00
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
|
|
|
Definition: &pbservice.HealthCheckDefinition{
|
2023-01-11 14:39:10 +00:00
|
|
|
Interval: &durationpb.Duration{},
|
|
|
|
Timeout: &durationpb.Duration{},
|
|
|
|
DeregisterCriticalServiceAfter: &durationpb.Duration{},
|
|
|
|
TTL: &durationpb.Duration{},
|
2022-03-23 16:10:03 +00:00
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
}
|
2022-03-30 16:51:56 +00:00
|
|
|
prototest.AssertDeepEqual(t, expectedEvent, event)
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-25 23:40:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type eventOrError struct {
|
|
|
|
event *pbsubscribe.Event
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
// recvEvents from handle and sends them to the provided channel.
|
|
|
|
func recvEvents(ch chan eventOrError, handle pbsubscribe.StateChangeSubscription_SubscribeClient) {
|
|
|
|
defer close(ch)
|
|
|
|
for {
|
|
|
|
event, err := handle.Recv()
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, io.EOF):
|
|
|
|
return
|
|
|
|
case errors.Is(err, context.Canceled), errors.Is(err, context.DeadlineExceeded):
|
|
|
|
return
|
|
|
|
case err != nil:
|
|
|
|
ch <- eventOrError{err: err}
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
ch <- eventOrError{event: event}
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-25 23:40:10 +00:00
|
|
|
}
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
func getEvent(t *testing.T, ch chan eventOrError) *pbsubscribe.Event {
|
|
|
|
t.Helper()
|
2020-09-09 18:04:33 +00:00
|
|
|
select {
|
2020-09-25 23:40:10 +00:00
|
|
|
case item := <-ch:
|
|
|
|
require.NoError(t, item.err)
|
|
|
|
return item.event
|
2020-10-21 20:08:33 +00:00
|
|
|
case <-time.After(2 * time.Second):
|
2020-09-25 23:40:10 +00:00
|
|
|
t.Fatalf("timeout waiting on event from server")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type testBackend struct {
|
2024-03-22 20:59:54 +00:00
|
|
|
publisher *stream.EventPublisher
|
|
|
|
store *state.Store
|
|
|
|
authorizer func(token string, entMeta *acl.EnterpriseMeta) acl.Authorizer
|
|
|
|
resolveTokenAndDefaultMeta func(token string, entMeta *acl.EnterpriseMeta, _ *acl.AuthorizerContext) (acl.Authorizer, error)
|
|
|
|
forwardConn *gogrpc.ClientConn
|
2020-09-25 23:40:10 +00:00
|
|
|
}
|
|
|
|
|
2020-10-21 20:08:33 +00:00
|
|
|
func (b testBackend) ResolveTokenAndDefaultMeta(
|
|
|
|
token string,
|
2022-04-05 21:10:06 +00:00
|
|
|
entMeta *acl.EnterpriseMeta,
|
2024-03-22 20:59:54 +00:00
|
|
|
authCtx *acl.AuthorizerContext,
|
2020-10-21 20:08:33 +00:00
|
|
|
) (acl.Authorizer, error) {
|
2024-03-22 20:59:54 +00:00
|
|
|
if b.resolveTokenAndDefaultMeta != nil {
|
|
|
|
return b.resolveTokenAndDefaultMeta(token, entMeta, authCtx)
|
|
|
|
}
|
2021-07-22 18:58:08 +00:00
|
|
|
return b.authorizer(token, entMeta), nil
|
2020-09-25 23:40:10 +00:00
|
|
|
}
|
|
|
|
|
2021-09-22 18:14:26 +00:00
|
|
|
func (b testBackend) Forward(_ structs.RPCInfo, fn func(*gogrpc.ClientConn) error) (handled bool, err error) {
|
2020-09-28 19:43:29 +00:00
|
|
|
if b.forwardConn != nil {
|
|
|
|
return true, fn(b.forwardConn)
|
|
|
|
}
|
2020-09-25 23:40:10 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b testBackend) Subscribe(req *stream.SubscribeRequest) (*stream.Subscription, error) {
|
2022-04-12 13:47:42 +00:00
|
|
|
return b.publisher.Subscribe(req)
|
2020-09-25 23:40:10 +00:00
|
|
|
}
|
|
|
|
|
2022-04-12 13:47:42 +00:00
|
|
|
func newTestBackend(t *testing.T) *testBackend {
|
|
|
|
t.Helper()
|
2020-09-25 23:40:10 +00:00
|
|
|
gc, err := state.NewTombstoneGC(time.Second, time.Millisecond)
|
2022-04-12 13:47:42 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
publisher := stream.NewEventPublisher(10 * time.Second)
|
|
|
|
|
|
|
|
store := state.NewStateStoreWithEventPublisher(gc, publisher)
|
|
|
|
|
|
|
|
// normally the handlers are registered on the FSM as state stores may come
|
|
|
|
// and go during snapshot restores. For the purposes of this test backend though we
|
|
|
|
// just register them directly to
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
require.NoError(t, publisher.RegisterHandler(state.EventTopicCARoots, store.CARootsSnapshot, false))
|
|
|
|
require.NoError(t, publisher.RegisterHandler(state.EventTopicServiceHealth, store.ServiceHealthSnapshot, false))
|
|
|
|
require.NoError(t, publisher.RegisterHandler(state.EventTopicServiceHealthConnect, store.ServiceHealthSnapshot, false))
|
2022-04-12 13:47:42 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
go publisher.Run(ctx)
|
|
|
|
t.Cleanup(cancel)
|
|
|
|
|
2022-04-05 21:10:06 +00:00
|
|
|
allowAll := func(string, *acl.EnterpriseMeta) acl.Authorizer {
|
2020-09-28 21:11:51 +00:00
|
|
|
return acl.AllowAll()
|
|
|
|
}
|
2022-04-12 13:47:42 +00:00
|
|
|
return &testBackend{publisher: publisher, store: store, authorizer: allowAll}
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
var _ Backend = (*testBackend)(nil)
|
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
func runTestServer(t *testing.T, server *Server) net.Addr {
|
2024-01-12 16:54:07 +00:00
|
|
|
// create the errgroup and register its cleanup. Its cleanup needs to occur
|
|
|
|
// after all others and that is why this is being done so early on in this function
|
|
|
|
// as cleanup routines are processed in reverse order of them being added.
|
|
|
|
g := new(errgroup.Group)
|
|
|
|
// this cleanup needs to happen after others defined in this func so we do it early
|
|
|
|
// on up here.
|
|
|
|
t.Cleanup(func() {
|
|
|
|
if err := g.Wait(); err != nil {
|
|
|
|
t.Logf("grpc server error: %v", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// start the handler
|
2020-09-25 23:40:10 +00:00
|
|
|
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
2022-10-11 22:00:32 +00:00
|
|
|
handler := grpc.NewHandler(
|
|
|
|
hclog.New(nil),
|
|
|
|
addr,
|
|
|
|
nil,
|
2022-12-20 22:00:22 +00:00
|
|
|
rate.NullRequestLimitsHandler(),
|
2022-10-11 22:00:32 +00:00
|
|
|
)
|
2024-01-12 16:54:07 +00:00
|
|
|
pbsubscribe.RegisterStateChangeSubscriptionServer(handler, server)
|
|
|
|
g.Go(handler.Run)
|
2020-09-25 23:40:10 +00:00
|
|
|
t.Cleanup(func() {
|
|
|
|
if err := handler.Shutdown(); err != nil {
|
|
|
|
t.Logf("grpc server shutdown: %v", err)
|
|
|
|
}
|
2024-01-12 16:54:07 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// create the routing to forward network conns to the gRPC handler
|
|
|
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
|
|
|
require.NoError(t, err)
|
|
|
|
g.Go(func() error {
|
|
|
|
for {
|
|
|
|
// select {
|
|
|
|
// case <-ctx.Done():
|
|
|
|
// return ctx.Err()
|
|
|
|
// default:
|
|
|
|
// }
|
|
|
|
|
|
|
|
conn, err := lis.Accept()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// select {
|
|
|
|
// case <-ctx.Done():
|
|
|
|
// return ctx.Err()
|
|
|
|
// default:
|
|
|
|
// }
|
|
|
|
|
|
|
|
handler.Handle(conn)
|
2020-09-25 23:40:10 +00:00
|
|
|
}
|
|
|
|
})
|
2024-01-12 16:54:07 +00:00
|
|
|
// closing the listener should cause the Accept to unblock and error out
|
|
|
|
t.Cleanup(logError(t, lis.Close))
|
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
return lis.Addr()
|
|
|
|
}
|
|
|
|
|
|
|
|
type counter struct {
|
|
|
|
value uint64
|
|
|
|
labels map[string]uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *counter) Next(label string) uint64 {
|
|
|
|
c.value++
|
|
|
|
c.labels[label] = c.value
|
|
|
|
return c.value
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *counter) For(label string) uint64 {
|
|
|
|
return c.labels[label]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *counter) Last() uint64 {
|
|
|
|
return c.value
|
|
|
|
}
|
|
|
|
|
|
|
|
func newCounter() *counter {
|
|
|
|
return &counter{labels: make(map[string]uint64)}
|
|
|
|
}
|
|
|
|
|
2022-03-23 16:10:03 +00:00
|
|
|
func raftIndex(ids *counter, created, modified string) *pbcommon.RaftIndex {
|
|
|
|
return &pbcommon.RaftIndex{
|
2020-09-25 23:40:10 +00:00
|
|
|
CreateIndex: ids.For(created),
|
|
|
|
ModifyIndex: ids.For(modified),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 19:43:29 +00:00
|
|
|
func TestServer_Subscribe_IntegrationWithBackend_ForwardToDC(t *testing.T) {
|
2022-04-12 13:47:42 +00:00
|
|
|
backendLocal := newTestBackend(t)
|
2020-10-08 19:38:01 +00:00
|
|
|
addrLocal := runTestServer(t, NewServer(backendLocal, hclog.New(nil)))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-04-12 13:47:42 +00:00
|
|
|
backendRemoteDC := newTestBackend(t)
|
2020-09-28 22:52:31 +00:00
|
|
|
srvRemoteDC := NewServer(backendRemoteDC, hclog.New(nil))
|
2020-10-08 19:38:01 +00:00
|
|
|
addrRemoteDC := runTestServer(t, srvRemoteDC)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 19:43:29 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2020-09-28 19:43:29 +00:00
|
|
|
connRemoteDC, err := gogrpc.DialContext(ctx, addrRemoteDC.String(), gogrpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(logError(t, connRemoteDC.Close))
|
|
|
|
backendLocal.forwardConn = connRemoteDC
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 19:43:29 +00:00
|
|
|
ids := newCounter()
|
2020-10-08 19:38:01 +00:00
|
|
|
|
|
|
|
var req *structs.RegisterRequest
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "register three services", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
req = &structs.RegisterRequest{
|
2020-09-09 18:04:33 +00:00
|
|
|
Node: "other",
|
|
|
|
Address: "2.3.4.5",
|
|
|
|
Datacenter: "dc2",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "api1",
|
|
|
|
Service: "api",
|
|
|
|
Address: "2.3.4.5",
|
|
|
|
Port: 9000,
|
|
|
|
},
|
|
|
|
}
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, backendRemoteDC.store.EnsureRegistration(ids.Next("reg1"), req))
|
2020-10-08 19:38:01 +00:00
|
|
|
req = &structs.RegisterRequest{
|
2020-09-09 18:04:33 +00:00
|
|
|
Node: "node1",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
Datacenter: "dc2",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
Port: 8080,
|
|
|
|
},
|
|
|
|
}
|
2020-09-28 19:43:29 +00:00
|
|
|
require.NoError(t, backendRemoteDC.store.EnsureRegistration(ids.Next("reg2"), req))
|
2020-10-08 19:38:01 +00:00
|
|
|
req = &structs.RegisterRequest{
|
|
|
|
Node: "node2",
|
|
|
|
Address: "1.2.3.4",
|
|
|
|
Datacenter: "dc2",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "1.1.1.1",
|
|
|
|
Port: 8080,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
require.NoError(t, backendRemoteDC.store.EnsureRegistration(ids.Next("reg3"), req))
|
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2020-09-28 19:43:29 +00:00
|
|
|
connLocal, err := gogrpc.DialContext(ctx, addrLocal.String(), gogrpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(logError(t, connLocal.Close))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 19:43:29 +00:00
|
|
|
chEvents := make(chan eventOrError, 0)
|
2020-09-09 18:04:33 +00:00
|
|
|
var snapshotEvents []*pbsubscribe.Event
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "setup a client and subscribe to a topic", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
streamClient := pbsubscribe.NewStateChangeSubscriptionClient(connLocal)
|
|
|
|
streamHandle, err := streamClient.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
|
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
|
|
Key: "redis",
|
|
|
|
Namespace: pbcommon.DefaultEnterpriseMeta.Namespace,
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
Datacenter: "dc2",
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
go recvEvents(chEvents, streamHandle)
|
|
|
|
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
snapshotEvents = append(snapshotEvents, getEvent(t, chEvents))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "receive the initial snapshot of events", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
expected := []*pbsubscribe.Event{
|
|
|
|
{
|
|
|
|
Index: ids.Last(),
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{
|
|
|
|
Node: "node1",
|
2021-08-17 18:29:39 +00:00
|
|
|
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
|
2020-10-08 19:38:01 +00:00
|
|
|
Datacenter: "dc2",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
RaftIndex: raftIndex(ids, "reg2", "reg2"),
|
|
|
|
},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "3.4.5.6",
|
|
|
|
Port: 8080,
|
|
|
|
Weights: &pbservice.Weights{Passing: 1, Warning: 1},
|
|
|
|
// Sad empty state
|
2022-03-23 16:10:03 +00:00
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2022-03-23 16:10:03 +00:00
|
|
|
Connect: &pbservice.ServiceConnect{},
|
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
2020-10-08 19:38:01 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg2", "reg2"),
|
|
|
|
},
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Index: ids.Last(),
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{
|
|
|
|
Node: "node2",
|
2021-08-17 18:29:39 +00:00
|
|
|
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
|
2020-10-08 19:38:01 +00:00
|
|
|
Datacenter: "dc2",
|
|
|
|
Address: "1.2.3.4",
|
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
|
|
|
},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "1.1.1.1",
|
|
|
|
Port: 8080,
|
|
|
|
Weights: &pbservice.Weights{Passing: 1, Warning: 1},
|
|
|
|
// Sad empty state
|
2022-03-23 16:10:03 +00:00
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2022-03-23 16:10:03 +00:00
|
|
|
Connect: &pbservice.ServiceConnect{},
|
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
2020-10-08 19:38:01 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
{
|
|
|
|
Index: ids.Last(),
|
|
|
|
Payload: &pbsubscribe.Event_EndOfSnapshot{EndOfSnapshot: true},
|
|
|
|
},
|
|
|
|
}
|
2022-03-30 16:51:56 +00:00
|
|
|
prototest.AssertDeepEqual(t, expected, snapshotEvents)
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "update the registration by adding a check", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
req.Check = &structs.HealthCheck{
|
|
|
|
Node: "node2",
|
|
|
|
CheckID: types.CheckID("check1"),
|
|
|
|
ServiceID: "redis1",
|
|
|
|
ServiceName: "redis",
|
|
|
|
Name: "check 1",
|
|
|
|
}
|
|
|
|
require.NoError(t, backendRemoteDC.store.EnsureRegistration(ids.Next("update"), req))
|
|
|
|
|
|
|
|
event := getEvent(t, chEvents)
|
|
|
|
expectedEvent := &pbsubscribe.Event{
|
2020-09-28 19:43:29 +00:00
|
|
|
Index: ids.Last(),
|
2020-09-09 18:04:33 +00:00
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
2020-09-28 19:43:29 +00:00
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
|
|
|
Node: &pbservice.Node{
|
2020-09-09 18:04:33 +00:00
|
|
|
Node: "node2",
|
2021-08-17 18:29:39 +00:00
|
|
|
Partition: structs.NodeEnterpriseMetaInDefaultPartition().PartitionOrEmpty(),
|
2020-09-09 18:04:33 +00:00
|
|
|
Datacenter: "dc2",
|
|
|
|
Address: "1.2.3.4",
|
2020-09-28 19:43:29 +00:00
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-09-28 19:43:29 +00:00
|
|
|
Service: &pbservice.NodeService{
|
2020-10-08 19:38:01 +00:00
|
|
|
ID: "redis1",
|
|
|
|
Service: "redis",
|
|
|
|
Address: "1.1.1.1",
|
|
|
|
Port: 8080,
|
|
|
|
RaftIndex: raftIndex(ids, "reg3", "reg3"),
|
|
|
|
Weights: &pbservice.Weights{Passing: 1, Warning: 1},
|
2020-09-09 18:04:33 +00:00
|
|
|
// Sad empty state
|
2022-03-23 16:10:03 +00:00
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2022-03-23 16:10:03 +00:00
|
|
|
Connect: &pbservice.ServiceConnect{},
|
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
Checks: []*pbservice.HealthCheck{
|
|
|
|
{
|
|
|
|
CheckID: "check1",
|
|
|
|
Name: "check 1",
|
|
|
|
Node: "node2",
|
|
|
|
Status: "critical",
|
|
|
|
ServiceID: "redis1",
|
|
|
|
ServiceName: "redis",
|
|
|
|
RaftIndex: raftIndex(ids, "update", "update"),
|
2022-03-23 16:10:03 +00:00
|
|
|
EnterpriseMeta: pbcommon.DefaultEnterpriseMeta,
|
|
|
|
Definition: &pbservice.HealthCheckDefinition{
|
2023-01-11 14:39:10 +00:00
|
|
|
Interval: &durationpb.Duration{},
|
|
|
|
Timeout: &durationpb.Duration{},
|
|
|
|
DeregisterCriticalServiceAfter: &durationpb.Duration{},
|
|
|
|
TTL: &durationpb.Duration{},
|
2022-03-23 16:10:03 +00:00
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
},
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
}
|
2022-03-30 16:51:56 +00:00
|
|
|
prototest.AssertDeepEqual(t, expectedEvent, event)
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
func TestServer_Subscribe_IntegrationWithBackend_FilterEventsByACLToken(t *testing.T) {
|
2020-12-07 18:42:55 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for testing.Short")
|
|
|
|
}
|
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
if testing.Short() {
|
|
|
|
t.Skip("too slow for -short run")
|
|
|
|
}
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-04-12 13:47:42 +00:00
|
|
|
backend := newTestBackend(t)
|
2020-10-08 19:38:01 +00:00
|
|
|
addr := runTestServer(t, NewServer(backend, hclog.New(nil)))
|
|
|
|
token := "this-token-is-good"
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "create an ACL policy", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
rules := `
|
2020-09-28 21:11:51 +00:00
|
|
|
service "foo" {
|
|
|
|
policy = "write"
|
|
|
|
}
|
|
|
|
node "node1" {
|
|
|
|
policy = "write"
|
|
|
|
}
|
|
|
|
`
|
2020-10-30 18:07:32 +00:00
|
|
|
cfg := &acl.Config{WildcardName: structs.WildcardSpecifier}
|
2023-02-06 15:35:52 +00:00
|
|
|
authorizer, err := acl.NewAuthorizerFromRules(rules, cfg, nil)
|
2020-10-08 19:38:01 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
authorizer = acl.NewChainedAuthorizer([]acl.Authorizer{authorizer, acl.DenyAll()})
|
|
|
|
require.Equal(t, acl.Deny, authorizer.NodeRead("denied", nil))
|
|
|
|
|
|
|
|
// TODO: is there any easy way to do this with the acl package?
|
2022-04-05 21:10:06 +00:00
|
|
|
backend.authorizer = func(tok string, _ *acl.EnterpriseMeta) acl.Authorizer {
|
2020-10-08 19:38:01 +00:00
|
|
|
if tok == token {
|
|
|
|
return authorizer
|
|
|
|
}
|
|
|
|
return acl.DenyAll()
|
2020-09-28 21:11:51 +00:00
|
|
|
}
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
ids := newCounter()
|
2020-10-08 19:38:01 +00:00
|
|
|
var req *structs.RegisterRequest
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "register services", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
req = &structs.RegisterRequest{
|
2020-09-09 18:04:33 +00:00
|
|
|
Datacenter: "dc1",
|
2020-09-28 21:11:51 +00:00
|
|
|
Node: "node1",
|
|
|
|
Address: "127.0.0.1",
|
2020-09-09 18:04:33 +00:00
|
|
|
Service: &structs.NodeService{
|
2020-09-28 21:11:51 +00:00
|
|
|
ID: "foo",
|
|
|
|
Service: "foo",
|
|
|
|
},
|
|
|
|
Check: &structs.HealthCheck{
|
|
|
|
CheckID: "service:foo",
|
|
|
|
Name: "service:foo",
|
|
|
|
Node: "node1",
|
|
|
|
ServiceID: "foo",
|
|
|
|
Status: api.HealthPassing,
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
}
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg1"), req))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
// Register a service which should be denied
|
|
|
|
req = &structs.RegisterRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "node1",
|
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "bar",
|
|
|
|
Service: "bar",
|
|
|
|
},
|
|
|
|
Check: &structs.HealthCheck{
|
|
|
|
CheckID: "service:bar",
|
|
|
|
Name: "service:bar",
|
|
|
|
Node: "node1",
|
|
|
|
ServiceID: "bar",
|
|
|
|
},
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg2"), req))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
req = &structs.RegisterRequest{
|
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "denied",
|
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "foo",
|
|
|
|
Service: "foo",
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
2020-09-28 21:11:51 +00:00
|
|
|
}
|
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg3"), req))
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2020-09-28 21:11:51 +00:00
|
|
|
conn, err := gogrpc.DialContext(ctx, addr.String(), gogrpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(logError(t, conn.Close))
|
|
|
|
streamClient := pbsubscribe.NewStateChangeSubscriptionClient(conn)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
chEvents := make(chan eventOrError, 0)
|
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "setup a client, subscribe to a topic, and receive a snapshot", func(t *testing.T) {
|
2020-09-09 18:04:33 +00:00
|
|
|
streamHandle, err := streamClient.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
|
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
|
|
Key: "foo",
|
|
|
|
Namespace: pbcommon.DefaultEnterpriseMeta.Namespace,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Token: token,
|
2020-09-09 18:04:33 +00:00
|
|
|
})
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, err)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
go recvEvents(chEvents, streamHandle)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 22:17:57 +00:00
|
|
|
event := getEvent(t, chEvents)
|
|
|
|
require.Equal(t, "foo", event.GetServiceHealth().CheckServiceNode.Service.Service)
|
|
|
|
require.Equal(t, "node1", event.GetServiceHealth().CheckServiceNode.Node.Node)
|
2020-09-28 21:11:51 +00:00
|
|
|
|
2020-09-28 22:17:57 +00:00
|
|
|
require.True(t, getEvent(t, chEvents).GetEndOfSnapshot())
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "update the service to receive an event", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
req = &structs.RegisterRequest{
|
2020-09-09 18:04:33 +00:00
|
|
|
Datacenter: "dc1",
|
2020-09-28 21:11:51 +00:00
|
|
|
Node: "node1",
|
2020-09-09 18:04:33 +00:00
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "foo",
|
|
|
|
Service: "foo",
|
|
|
|
Port: 1234,
|
|
|
|
},
|
|
|
|
Check: &structs.HealthCheck{
|
|
|
|
CheckID: "service:foo",
|
|
|
|
Name: "service:foo",
|
|
|
|
ServiceID: "foo",
|
|
|
|
Status: api.HealthPassing,
|
2020-09-28 21:11:51 +00:00
|
|
|
Node: "node1",
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg4"), req))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
event := getEvent(t, chEvents)
|
2020-09-28 21:11:51 +00:00
|
|
|
service := event.GetServiceHealth().CheckServiceNode.Service
|
|
|
|
require.Equal(t, "foo", service.Service)
|
|
|
|
require.Equal(t, int32(1234), service.Port)
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "updates to the service on the denied node, should not send an event", func(t *testing.T) {
|
2020-09-28 21:11:51 +00:00
|
|
|
req = &structs.RegisterRequest{
|
2020-09-09 18:04:33 +00:00
|
|
|
Datacenter: "dc1",
|
|
|
|
Node: "denied",
|
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "foo",
|
|
|
|
Service: "foo",
|
|
|
|
Port: 2345,
|
|
|
|
},
|
|
|
|
Check: &structs.HealthCheck{
|
|
|
|
CheckID: "service:foo",
|
|
|
|
Name: "service:foo",
|
|
|
|
ServiceID: "foo",
|
|
|
|
Status: api.HealthPassing,
|
2020-09-28 21:11:51 +00:00
|
|
|
Node: "denied",
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg5"), req))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
assertNoEvents(t, chEvents)
|
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "subscribe to a topic where events are not visible", func(t *testing.T) {
|
2020-09-09 18:04:33 +00:00
|
|
|
streamHandle, err := streamClient.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
|
|
Key: "bar",
|
|
|
|
},
|
|
|
|
},
|
2020-09-28 21:11:51 +00:00
|
|
|
Token: token,
|
2020-09-09 18:04:33 +00:00
|
|
|
})
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, err)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
chEvents := make(chan eventOrError, 0)
|
|
|
|
go recvEvents(chEvents, streamHandle)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 21:11:51 +00:00
|
|
|
require.True(t, getEvent(t, chEvents).GetEndOfSnapshot())
|
2020-09-09 18:04:33 +00:00
|
|
|
|
|
|
|
// Update the service and make sure we don't get a new event.
|
2020-09-28 21:11:51 +00:00
|
|
|
req := &structs.RegisterRequest{
|
2020-09-09 18:04:33 +00:00
|
|
|
Datacenter: "dc1",
|
2020-09-28 21:11:51 +00:00
|
|
|
Node: "node1",
|
2020-09-09 18:04:33 +00:00
|
|
|
Address: "127.0.0.1",
|
|
|
|
Service: &structs.NodeService{
|
|
|
|
ID: "bar",
|
|
|
|
Service: "bar",
|
|
|
|
Port: 2345,
|
|
|
|
},
|
|
|
|
Check: &structs.HealthCheck{
|
|
|
|
CheckID: "service:bar",
|
|
|
|
Name: "service:bar",
|
|
|
|
ServiceID: "bar",
|
2020-09-28 21:11:51 +00:00
|
|
|
Node: "node1",
|
2020-09-09 18:04:33 +00:00
|
|
|
},
|
|
|
|
WriteRequest: structs.WriteRequest{Token: "root"},
|
|
|
|
}
|
2020-09-28 21:11:51 +00:00
|
|
|
require.NoError(t, backend.store.EnsureRegistration(ids.Next("reg6"), req))
|
2020-10-08 19:38:01 +00:00
|
|
|
assertNoEvents(t, chEvents)
|
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
|
|
|
|
2020-09-28 22:17:57 +00:00
|
|
|
func TestServer_Subscribe_IntegrationWithBackend_ACLUpdate(t *testing.T) {
|
2022-04-12 13:47:42 +00:00
|
|
|
backend := newTestBackend(t)
|
2020-10-08 19:38:01 +00:00
|
|
|
addr := runTestServer(t, NewServer(backend, hclog.New(nil)))
|
|
|
|
token := "this-token-is-good"
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "create an ACL policy", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
rules := `
|
2020-09-28 22:17:57 +00:00
|
|
|
service "foo" {
|
|
|
|
policy = "write"
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
2020-09-28 22:17:57 +00:00
|
|
|
node "node1" {
|
|
|
|
policy = "write"
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
2020-09-28 22:17:57 +00:00
|
|
|
`
|
2023-02-06 15:35:52 +00:00
|
|
|
authorizer, err := acl.NewAuthorizerFromRules(rules, &acl.Config{WildcardName: structs.WildcardSpecifier}, nil)
|
2020-10-08 19:38:01 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
authorizer = acl.NewChainedAuthorizer([]acl.Authorizer{authorizer, acl.DenyAll()})
|
|
|
|
require.Equal(t, acl.Deny, authorizer.NodeRead("denied", nil))
|
|
|
|
|
|
|
|
// TODO: is there any easy way to do this with the acl package?
|
2022-04-05 21:10:06 +00:00
|
|
|
backend.authorizer = func(tok string, _ *acl.EnterpriseMeta) acl.Authorizer {
|
2020-10-08 19:38:01 +00:00
|
|
|
if tok == token {
|
|
|
|
return authorizer
|
|
|
|
}
|
|
|
|
return acl.DenyAll()
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
2020-10-08 19:38:01 +00:00
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 22:17:57 +00:00
|
|
|
ids := newCounter()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
t.Cleanup(cancel)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-11-07 16:34:30 +00:00
|
|
|
//nolint:staticcheck
|
2020-09-28 22:17:57 +00:00
|
|
|
conn, err := gogrpc.DialContext(ctx, addr.String(), gogrpc.WithInsecure())
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(logError(t, conn.Close))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-09-28 22:17:57 +00:00
|
|
|
chEvents := make(chan eventOrError, 0)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "setup a client and subscribe to a topic", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
streamClient := pbsubscribe.NewStateChangeSubscriptionClient(conn)
|
|
|
|
streamHandle, err := streamClient.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
|
|
Key: "foo",
|
|
|
|
},
|
|
|
|
},
|
2020-10-08 19:38:01 +00:00
|
|
|
Token: token,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
go recvEvents(chEvents, streamHandle)
|
|
|
|
require.True(t, getEvent(t, chEvents).GetEndOfSnapshot())
|
|
|
|
})
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2022-05-10 20:25:51 +00:00
|
|
|
testutil.RunStep(t, "updates to the token should close the stream", func(t *testing.T) {
|
2020-10-08 19:38:01 +00:00
|
|
|
tokenID, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
aclToken := &structs.ACLToken{
|
|
|
|
AccessorID: tokenID,
|
|
|
|
SecretID: token,
|
|
|
|
}
|
2021-09-29 22:30:37 +00:00
|
|
|
require.NoError(t, backend.store.ACLTokenSet(ids.Next("update"), aclToken))
|
2020-09-09 18:04:33 +00:00
|
|
|
|
2020-10-08 19:38:01 +00:00
|
|
|
select {
|
|
|
|
case item := <-chEvents:
|
|
|
|
require.Error(t, item.err, "got event instead of an error: %v", item.event)
|
|
|
|
s, _ := status.FromError(item.err)
|
|
|
|
require.Equal(t, codes.Aborted, s.Code())
|
|
|
|
case <-time.After(2 * time.Second):
|
|
|
|
t.Fatalf("timeout waiting for aborted error")
|
|
|
|
}
|
|
|
|
})
|
2024-03-22 20:59:54 +00:00
|
|
|
|
|
|
|
// Re-subscribe because the previous test step terminated the stream.
|
|
|
|
chEvents = make(chan eventOrError, 0)
|
|
|
|
streamClient := pbsubscribe.NewStateChangeSubscriptionClient(conn)
|
|
|
|
streamHandle, err := streamClient.Subscribe(ctx, &pbsubscribe.SubscribeRequest{
|
|
|
|
Topic: pbsubscribe.Topic_ServiceHealth,
|
|
|
|
Subject: &pbsubscribe.SubscribeRequest_NamedSubject{
|
|
|
|
NamedSubject: &pbsubscribe.NamedSubject{
|
|
|
|
Key: "foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Token: token,
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
go recvEvents(chEvents, streamHandle)
|
|
|
|
|
|
|
|
// Stub out token authn function so that the token is no longer considered valid.
|
|
|
|
backend.resolveTokenAndDefaultMeta = func(t string, entMeta *acl.EnterpriseMeta, _ *acl.AuthorizerContext) (acl.Authorizer, error) {
|
|
|
|
return nil, fmt.Errorf("ACL not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
testutil.RunStep(t, "invalid token should return an error", func(t *testing.T) {
|
|
|
|
// Force another ACL update.
|
|
|
|
tokenID, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
aclToken := &structs.ACLToken{
|
|
|
|
AccessorID: tokenID,
|
|
|
|
SecretID: token,
|
|
|
|
}
|
|
|
|
require.NoError(t, backend.store.ACLTokenSet(ids.Next("update"), aclToken))
|
|
|
|
|
|
|
|
select {
|
|
|
|
case item := <-chEvents:
|
|
|
|
require.Error(t, item.err, "got event instead of an error: %v", item.event)
|
|
|
|
require.EqualError(t, item.err, "rpc error: code = Unknown desc = ACL not found")
|
|
|
|
case <-time.After(2 * time.Second):
|
|
|
|
t.Fatalf("timeout waiting for ACL not found error")
|
|
|
|
}
|
|
|
|
})
|
2020-10-08 19:38:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func assertNoEvents(t *testing.T, chEvents chan eventOrError) {
|
|
|
|
t.Helper()
|
2020-09-28 22:17:57 +00:00
|
|
|
select {
|
2020-10-08 19:38:01 +00:00
|
|
|
case event := <-chEvents:
|
|
|
|
t.Fatalf("should not have received event: %v", event)
|
|
|
|
case <-time.After(100 * time.Millisecond):
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
func logError(t *testing.T, f func() error) func() {
|
|
|
|
return func() {
|
2022-01-28 12:27:00 +00:00
|
|
|
t.Helper()
|
|
|
|
|
2020-09-25 23:40:10 +00:00
|
|
|
if err := f(); err != nil {
|
|
|
|
t.Logf(err.Error())
|
|
|
|
}
|
|
|
|
}
|
2020-09-09 18:04:33 +00:00
|
|
|
}
|
2020-10-08 19:38:01 +00:00
|
|
|
|
2020-10-08 22:35:56 +00:00
|
|
|
func TestNewEventFromSteamEvent(t *testing.T) {
|
|
|
|
type testCase struct {
|
|
|
|
name string
|
|
|
|
event stream.Event
|
2022-03-30 16:51:56 +00:00
|
|
|
expected *pbsubscribe.Event
|
2020-10-08 22:35:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn := func(t *testing.T, tc testCase) {
|
|
|
|
expected := tc.expected
|
peering: initial sync (#12842)
- Add endpoints related to peering: read, list, generate token, initiate peering
- Update node/service/check table indexing to account for peers
- Foundational changes for pushing service updates to a peer
- Plumb peer name through Health.ServiceNodes path
see: ENT-1765, ENT-1280, ENT-1283, ENT-1283, ENT-1756, ENT-1739, ENT-1750, ENT-1679,
ENT-1709, ENT-1704, ENT-1690, ENT-1689, ENT-1702, ENT-1701, ENT-1683, ENT-1663,
ENT-1650, ENT-1678, ENT-1628, ENT-1658, ENT-1640, ENT-1637, ENT-1597, ENT-1634,
ENT-1613, ENT-1616, ENT-1617, ENT-1591, ENT-1588, ENT-1596, ENT-1572, ENT-1555
Co-authored-by: R.B. Boyer <rb@hashicorp.com>
Co-authored-by: freddygv <freddy@hashicorp.com>
Co-authored-by: Chris S. Kim <ckim@hashicorp.com>
Co-authored-by: Evan Culver <eculver@hashicorp.com>
Co-authored-by: Nitya Dhanushkodi <nitya@hashicorp.com>
2022-04-21 22:34:40 +00:00
|
|
|
actual := tc.event.Payload.ToSubscriptionEvent(tc.event.Index)
|
2022-03-30 16:51:56 +00:00
|
|
|
prototest.AssertDeepEqual(t, expected, actual, cmpopts.EquateEmpty())
|
2020-10-08 22:35:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var testCases = []testCase{
|
|
|
|
{
|
|
|
|
name: "end of snapshot",
|
|
|
|
event: newEventFromSubscription(t, 0),
|
2022-03-30 16:51:56 +00:00
|
|
|
expected: &pbsubscribe.Event{
|
2020-10-08 22:35:56 +00:00
|
|
|
Index: 1,
|
|
|
|
Payload: &pbsubscribe.Event_EndOfSnapshot{EndOfSnapshot: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "new snapshot to follow",
|
|
|
|
event: newEventFromSubscription(t, 22),
|
2022-03-30 16:51:56 +00:00
|
|
|
expected: &pbsubscribe.Event{
|
2020-10-08 22:35:56 +00:00
|
|
|
Payload: &pbsubscribe.Event_NewSnapshotToFollow{NewSnapshotToFollow: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "event batch",
|
|
|
|
event: stream.Event{
|
|
|
|
Index: 2002,
|
2020-11-06 18:00:33 +00:00
|
|
|
Payload: newPayloadEvents(
|
2020-11-05 22:57:25 +00:00
|
|
|
stream.Event{
|
2020-10-08 22:35:56 +00:00
|
|
|
Index: 2002,
|
|
|
|
Payload: state.EventPayloadCheckServiceNode{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
Value: &structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node1"},
|
|
|
|
Service: &structs.NodeService{Service: "web1"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-11-05 22:57:25 +00:00
|
|
|
stream.Event{
|
2020-10-08 22:35:56 +00:00
|
|
|
Index: 2002,
|
|
|
|
Payload: state.EventPayloadCheckServiceNode{
|
|
|
|
Op: pbsubscribe.CatalogOp_Deregister,
|
|
|
|
Value: &structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node2"},
|
|
|
|
Service: &structs.NodeService{Service: "web1"},
|
|
|
|
},
|
|
|
|
},
|
2020-11-05 22:57:25 +00:00
|
|
|
}),
|
2020-10-08 22:35:56 +00:00
|
|
|
},
|
2022-03-30 16:51:56 +00:00
|
|
|
expected: &pbsubscribe.Event{
|
2020-10-08 22:35:56 +00:00
|
|
|
Index: 2002,
|
|
|
|
Payload: &pbsubscribe.Event_EventBatch{
|
|
|
|
EventBatch: &pbsubscribe.EventBatch{
|
|
|
|
Events: []*pbsubscribe.Event{
|
|
|
|
{
|
|
|
|
Index: 2002,
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
2022-03-23 16:10:03 +00:00
|
|
|
Node: &pbservice.Node{Node: "node1", RaftIndex: &pbcommon.RaftIndex{}},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
Service: "web1",
|
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2022-03-23 16:10:03 +00:00
|
|
|
},
|
|
|
|
Connect: &pbservice.ServiceConnect{},
|
|
|
|
EnterpriseMeta: &pbcommon.EnterpriseMeta{},
|
|
|
|
RaftIndex: &pbcommon.RaftIndex{},
|
|
|
|
},
|
2020-10-08 22:35:56 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Index: 2002,
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Deregister,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
2022-03-23 16:10:03 +00:00
|
|
|
Node: &pbservice.Node{Node: "node2", RaftIndex: &pbcommon.RaftIndex{}},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
Service: "web1",
|
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2022-03-23 16:10:03 +00:00
|
|
|
},
|
|
|
|
Connect: &pbservice.ServiceConnect{},
|
|
|
|
EnterpriseMeta: &pbcommon.EnterpriseMeta{},
|
|
|
|
RaftIndex: &pbcommon.RaftIndex{},
|
|
|
|
},
|
2020-10-08 22:35:56 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "event payload CheckServiceNode",
|
|
|
|
event: stream.Event{
|
|
|
|
Index: 2002,
|
|
|
|
Payload: state.EventPayloadCheckServiceNode{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
Value: &structs.CheckServiceNode{
|
|
|
|
Node: &structs.Node{Node: "node1"},
|
|
|
|
Service: &structs.NodeService{Service: "web1"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-03-30 16:51:56 +00:00
|
|
|
expected: &pbsubscribe.Event{
|
2020-10-08 22:35:56 +00:00
|
|
|
Index: 2002,
|
|
|
|
Payload: &pbsubscribe.Event_ServiceHealth{
|
|
|
|
ServiceHealth: &pbsubscribe.ServiceHealthUpdate{
|
|
|
|
Op: pbsubscribe.CatalogOp_Register,
|
|
|
|
CheckServiceNode: &pbservice.CheckServiceNode{
|
2022-03-23 16:10:03 +00:00
|
|
|
Node: &pbservice.Node{Node: "node1", RaftIndex: &pbcommon.RaftIndex{}},
|
|
|
|
Service: &pbservice.NodeService{
|
|
|
|
Service: "web1",
|
|
|
|
Proxy: &pbservice.ConnectProxyConfig{
|
|
|
|
MeshGateway: &pbservice.MeshGatewayConfig{},
|
|
|
|
Expose: &pbservice.ExposeConfig{},
|
|
|
|
TransparentProxy: &pbservice.TransparentProxyConfig{},
|
2022-12-22 20:18:15 +00:00
|
|
|
AccessLogs: &pbservice.AccessLogsConfig{},
|
2022-03-23 16:10:03 +00:00
|
|
|
},
|
|
|
|
Connect: &pbservice.ServiceConnect{},
|
|
|
|
EnterpriseMeta: &pbcommon.EnterpriseMeta{},
|
|
|
|
RaftIndex: &pbcommon.RaftIndex{},
|
|
|
|
},
|
2020-10-08 22:35:56 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
fn(t, tc)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-06 18:00:33 +00:00
|
|
|
func newPayloadEvents(items ...stream.Event) *stream.PayloadEvents {
|
|
|
|
return &stream.PayloadEvents{Items: items}
|
|
|
|
}
|
|
|
|
|
2020-10-08 22:35:56 +00:00
|
|
|
// newEventFromSubscription is used to return framing events. EndOfSnapshot and
|
|
|
|
// NewSnapshotToFollow are not exported, but we can get them from a subscription.
|
|
|
|
func newEventFromSubscription(t *testing.T, index uint64) stream.Event {
|
|
|
|
t.Helper()
|
|
|
|
|
2022-04-12 13:47:42 +00:00
|
|
|
serviceHealthConnectHandler := func(stream.SubscribeRequest, stream.SnapshotAppender) (index uint64, err error) {
|
|
|
|
return 1, nil
|
2020-10-08 22:35:56 +00:00
|
|
|
}
|
2022-04-12 13:47:42 +00:00
|
|
|
|
|
|
|
ep := stream.NewEventPublisher(0)
|
proxycfg: server-local config entry data sources
This is the OSS portion of enterprise PR 2056.
This commit provides server-local implementations of the proxycfg.ConfigEntry
and proxycfg.ConfigEntryList interfaces, that source data from streaming events.
It makes use of the LocalMaterializer type introduced for peering replication,
adding the necessary support for authorization.
It also adds support for "wildcard" subscriptions (within a topic) to the event
publisher, as this is needed to fetch service-resolvers for all services when
configuring mesh gateways.
Currently, events will be emitted for just the ingress-gateway, service-resolver,
and mesh config entry types, as these are the only entries required by proxycfg
— the events will be emitted on topics named IngressGateway, ServiceResolver,
and MeshConfig topics respectively.
Though these events will only be consumed "locally" for now, they can also be
consumed via the gRPC endpoint (confirmed using grpcurl) so using them from
client agents should be a case of swapping the LocalMaterializer for an
RPCMaterializer.
2022-07-01 15:09:47 +00:00
|
|
|
ep.RegisterHandler(pbsubscribe.Topic_ServiceHealthConnect, serviceHealthConnectHandler, false)
|
2022-04-05 14:26:14 +00:00
|
|
|
req := &stream.SubscribeRequest{Topic: pbsubscribe.Topic_ServiceHealthConnect, Subject: stream.SubjectNone, Index: index}
|
2020-10-08 22:35:56 +00:00
|
|
|
|
|
|
|
sub, err := ep.Subscribe(req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
event, err := sub.Next(ctx)
|
|
|
|
require.NoError(t, err)
|
|
|
|
return event
|
|
|
|
}
|