open-consul/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go
Dan Upton 20c87d235f
dataplane: update envoy bootstrap params for consul-dataplane (#14017)
Contains 2 changes to the GetEnvoyBootstrapParams response to support
consul-dataplane.

Exposing node_name and node_id:

consul-dataplane will support providing either the node_id or node_name in its
configuration. Unfortunately, supporting both in the xDS meta adds a fair amount
of complexity (partly because most tables are currently indexed on node_name)
so for now we're going to return them both from the bootstrap params endpoint,
allowing consul-dataplane to exchange a node_id for a node_name (which it will
supply in the xDS meta).

Properly setting service for gateways:

To avoid the need to special case gateways in consul-dataplane, service will now
either be the destination service name for connect proxies, or the gateway
service name. This means it can be used as-is in Envoy configuration (i.e. as a
cluster name or in metric tags).
2022-08-24 12:03:15 +01:00

269 lines
8.9 KiB
Go

package dataplane
import (
"context"
"testing"
"github.com/hashicorp/go-hclog"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
acl "github.com/hashicorp/consul/acl"
resolver "github.com/hashicorp/consul/acl/resolver"
external "github.com/hashicorp/consul/agent/grpc-external"
"github.com/hashicorp/consul/agent/grpc-external/testutils"
structs "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/proto-public/pbdataplane"
"github.com/hashicorp/consul/types"
)
const (
testToken = "acl-token-get-envoy-bootstrap-params"
proxyServiceID = "web-proxy"
nodeName = "foo"
nodeID = "2980b72b-bd9d-9d7b-d4f9-951bf7508d95"
proxyConfigKey = "envoy_dogstatsd_url"
proxyConfigValue = "udp://127.0.0.1:8125"
serverDC = "dc1"
)
func testRegisterRequestProxy(t *testing.T) *structs.RegisterRequest {
return &structs.RegisterRequest{
Datacenter: serverDC,
Node: nodeName,
ID: types.NodeID(nodeID),
Address: "127.0.0.1",
Service: &structs.NodeService{
Kind: structs.ServiceKindConnectProxy,
Service: proxyServiceID,
ID: proxyServiceID,
Address: "127.0.0.2",
Port: 2222,
Proxy: structs.ConnectProxyConfig{
DestinationServiceName: "web",
Config: map[string]interface{}{
proxyConfigKey: proxyConfigValue,
},
},
EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(),
},
}
}
func testRegisterIngressGateway(t *testing.T) *structs.RegisterRequest {
registerReq := structs.TestRegisterIngressGateway(t)
registerReq.ID = types.NodeID("2980b72b-bd9d-9d7b-d4f9-951bf7508d95")
registerReq.Service.ID = registerReq.Service.Service
registerReq.Service.Proxy.Config = map[string]interface{}{
proxyConfigKey: proxyConfigValue,
}
return registerReq
}
func TestGetEnvoyBootstrapParams_Success(t *testing.T) {
type testCase struct {
name string
registerReq *structs.RegisterRequest
nodeID bool
}
run := func(t *testing.T, tc testCase) {
store := testutils.TestStateStore(t, nil)
err := store.EnsureRegistration(1, tc.registerReq)
require.NoError(t, err)
aclResolver := &MockACLResolver{}
aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything).
Return(testutils.TestAuthorizerServiceRead(t, tc.registerReq.Service.ID), nil)
ctx := external.ContextWithToken(context.Background(), testToken)
server := NewServer(Config{
GetStore: func() StateStore { return store },
Logger: hclog.NewNullLogger(),
ACLResolver: aclResolver,
Datacenter: serverDC,
})
client := testClient(t, server)
req := &pbdataplane.GetEnvoyBootstrapParamsRequest{
ServiceId: tc.registerReq.Service.ID,
NodeSpec: &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeName{NodeName: tc.registerReq.Node}}
if tc.nodeID {
req.NodeSpec = &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeId{NodeId: string(tc.registerReq.ID)}
}
resp, err := client.GetEnvoyBootstrapParams(ctx, req)
require.NoError(t, err)
if tc.registerReq.Service.IsGateway() {
require.Equal(t, tc.registerReq.Service.Service, resp.Service)
} else {
require.Equal(t, tc.registerReq.Service.Proxy.DestinationServiceName, resp.Service)
}
require.Equal(t, serverDC, resp.Datacenter)
require.Equal(t, tc.registerReq.EnterpriseMeta.PartitionOrDefault(), resp.Partition)
require.Equal(t, tc.registerReq.EnterpriseMeta.NamespaceOrDefault(), resp.Namespace)
require.Contains(t, resp.Config.Fields, proxyConfigKey)
require.Equal(t, structpb.NewStringValue(proxyConfigValue), resp.Config.Fields[proxyConfigKey])
require.Equal(t, convertToResponseServiceKind(tc.registerReq.Service.Kind), resp.ServiceKind)
require.Equal(t, tc.registerReq.Node, resp.NodeName)
require.Equal(t, string(tc.registerReq.ID), resp.NodeId)
}
testCases := []testCase{
{
name: "lookup service side car proxy by node name",
registerReq: testRegisterRequestProxy(t),
},
{
name: "lookup service side car proxy by node ID",
registerReq: testRegisterRequestProxy(t),
nodeID: true,
},
{
name: "lookup ingress gw service by node name",
registerReq: testRegisterIngressGateway(t),
},
{
name: "lookup ingress gw service by node ID",
registerReq: testRegisterIngressGateway(t),
nodeID: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
run(t, tc)
})
}
}
func TestGetEnvoyBootstrapParams_Error(t *testing.T) {
type testCase struct {
name string
req *pbdataplane.GetEnvoyBootstrapParamsRequest
expectedErrCode codes.Code
expecteErrMsg string
}
run := func(t *testing.T, tc testCase) {
aclResolver := &MockACLResolver{}
aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything).
Return(testutils.TestAuthorizerServiceRead(t, proxyServiceID), nil)
ctx := external.ContextWithToken(context.Background(), testToken)
store := testutils.TestStateStore(t, nil)
registerReq := testRegisterRequestProxy(t)
err := store.EnsureRegistration(1, registerReq)
require.NoError(t, err)
server := NewServer(Config{
GetStore: func() StateStore { return store },
Logger: hclog.NewNullLogger(),
ACLResolver: aclResolver,
})
client := testClient(t, server)
resp, err := client.GetEnvoyBootstrapParams(ctx, tc.req)
require.Nil(t, resp)
require.Error(t, err)
errStatus, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, tc.expectedErrCode.String(), errStatus.Code().String())
require.Equal(t, tc.expecteErrMsg, errStatus.Message())
}
testCases := []testCase{
{
name: "lookup-service-by-unregistered-node-name",
req: &pbdataplane.GetEnvoyBootstrapParamsRequest{
ServiceId: proxyServiceID,
NodeSpec: &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeName{NodeName: "blah"}},
expectedErrCode: codes.NotFound,
expecteErrMsg: "node not found",
},
{
name: "lookup-service-by-unregistered-node-id",
req: &pbdataplane.GetEnvoyBootstrapParamsRequest{
ServiceId: proxyServiceID,
NodeSpec: &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeId{NodeId: "5980b72b-bd9d-9d7b-d4f9-951bf7508d98"}},
expectedErrCode: codes.NotFound,
expecteErrMsg: "node not found",
},
{
name: "lookup-service-by-unregistered-service",
req: &pbdataplane.GetEnvoyBootstrapParamsRequest{
ServiceId: "blah-service",
NodeSpec: &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeName{NodeName: nodeName}},
expectedErrCode: codes.NotFound,
expecteErrMsg: "Service not found",
},
{
name: "lookup-service-without-node-details",
req: &pbdataplane.GetEnvoyBootstrapParamsRequest{
ServiceId: proxyServiceID},
expectedErrCode: codes.InvalidArgument,
expecteErrMsg: "Node ID or name required to lookup the service",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
run(t, tc)
})
}
}
func TestGetEnvoyBootstrapParams_Unauthenticated(t *testing.T) {
// Mock the ACL resolver to return ErrNotFound.
aclResolver := &MockACLResolver{}
aclResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything).
Return(resolver.Result{}, acl.ErrNotFound)
ctx := external.ContextWithToken(context.Background(), testToken)
store := testutils.TestStateStore(t, nil)
server := NewServer(Config{
GetStore: func() StateStore { return store },
Logger: hclog.NewNullLogger(),
ACLResolver: aclResolver,
})
client := testClient(t, server)
resp, err := client.GetEnvoyBootstrapParams(ctx, &pbdataplane.GetEnvoyBootstrapParamsRequest{})
require.Error(t, err)
require.Equal(t, codes.Unauthenticated.String(), status.Code(err).String())
require.Nil(t, resp)
}
func TestGetEnvoyBootstrapParams_PermissionDenied(t *testing.T) {
// Mock the ACL resolver to return a deny all authorizer
aclResolver := &MockACLResolver{}
aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything).
Return(testutils.TestAuthorizerDenyAll(t), nil)
ctx := external.ContextWithToken(context.Background(), testToken)
store := testutils.TestStateStore(t, nil)
registerReq := structs.TestRegisterRequestProxy(t)
proxyServiceID := "web-sidecar-proxy"
registerReq.Service.ID = proxyServiceID
err := store.EnsureRegistration(1, registerReq)
require.NoError(t, err)
server := NewServer(Config{
GetStore: func() StateStore { return store },
Logger: hclog.NewNullLogger(),
ACLResolver: aclResolver,
})
client := testClient(t, server)
req := &pbdataplane.GetEnvoyBootstrapParamsRequest{
ServiceId: proxyServiceID,
NodeSpec: &pbdataplane.GetEnvoyBootstrapParamsRequest_NodeName{NodeName: registerReq.Node}}
resp, err := client.GetEnvoyBootstrapParams(ctx, req)
require.Error(t, err)
require.Equal(t, codes.PermissionDenied.String(), status.Code(err).String())
require.Nil(t, resp)
}