consul/api/config_entry_discoverychain...

500 lines
13 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
config_entries := c.ConfigEntries()
verifyResolver := func(t *testing.T, initial ConfigEntry) {
t.Helper()
require.IsType(t, &ServiceResolverConfigEntry{}, initial)
testEntry := initial.(*ServiceResolverConfigEntry)
// set it
_, wm, err := config_entries.Set(testEntry, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// generic verification
require.Equal(t, testEntry.Meta, entry.GetMeta())
// verify it
readResolver, ok := entry.(*ServiceResolverConfigEntry)
require.True(t, ok)
readResolver.ModifyIndex = 0 // reset for Equals()
readResolver.CreateIndex = 0 // reset for Equals()
require.Equal(t, testEntry, readResolver)
// TODO(rb): cas?
// TODO(rb): list?
}
verifySplitter := func(t *testing.T, initial ConfigEntry) {
t.Helper()
require.IsType(t, &ServiceSplitterConfigEntry{}, initial)
testEntry := initial.(*ServiceSplitterConfigEntry)
// set it
_, wm, err := config_entries.Set(testEntry, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := config_entries.Get(ServiceSplitter, testEntry.Name, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// generic verification
require.Equal(t, testEntry.Meta, entry.GetMeta())
// verify it
readSplitter, ok := entry.(*ServiceSplitterConfigEntry)
require.True(t, ok)
readSplitter.ModifyIndex = 0 // reset for Equals()
readSplitter.CreateIndex = 0 // reset for Equals()
require.Equal(t, testEntry, readSplitter)
// TODO(rb): cas?
// TODO(rb): list?
}
verifyRouter := func(t *testing.T, initial ConfigEntry) {
t.Helper()
require.IsType(t, &ServiceRouterConfigEntry{}, initial)
testEntry := initial.(*ServiceRouterConfigEntry)
// set it
_, wm, err := config_entries.Set(testEntry, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := config_entries.Get(ServiceRouter, testEntry.Name, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// generic verification
require.Equal(t, testEntry.Meta, entry.GetMeta())
// verify it
readRouter, ok := entry.(*ServiceRouterConfigEntry)
require.True(t, ok)
readRouter.ModifyIndex = 0 // reset for Equals()
readRouter.CreateIndex = 0 // reset for Equals()
require.Equal(t, testEntry, readRouter)
// TODO(rb): cas?
// TODO(rb): list?
}
// First set the necessary protocols to allow advanced routing features.
for _, service := range []string{
"test-failover",
"test-redirect",
"alternate",
"test-split",
"test-route",
"test-route-case-insensitive",
} {
serviceDefaults := &ServiceConfigEntry{
Kind: ServiceDefaults,
Name: service,
Protocol: "http",
}
_, _, err := config_entries.Set(serviceDefaults, nil)
require.NoError(t, err)
}
// NOTE: Due to service graph validation, these have to happen in a specific order.
for _, tc := range []struct {
name string
entry ConfigEntry
verify func(t *testing.T, initial ConfigEntry)
}{
{
name: "failover",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-failover",
Partition: defaultPartition,
Namespace: defaultNamespace,
DefaultSubset: "v1",
Subsets: map[string]ServiceResolverSubset{
"v1": {
Filter: "Service.Meta.version == v1",
},
"v2": {
Filter: "Service.Meta.version == v2",
},
"v3": {
Filter: "Service.Meta.version == v3",
},
},
Failover: map[string]ServiceResolverFailover{
"*": {
Datacenters: []string{"dc2"},
},
"v1": {
Service: "alternate",
Namespace: defaultNamespace,
},
"v3": {
Targets: []ServiceResolverFailoverTarget{
{Peer: "cluster-01"},
{Datacenter: "dc1"},
{Service: "another-service", ServiceSubset: "v1"},
},
},
},
ConnectTimeout: 5 * time.Second,
RequestTimeout: 10 * time.Second,
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
verify: verifyResolver,
},
{
name: "redirect",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-redirect",
Partition: defaultPartition,
Namespace: defaultNamespace,
Redirect: &ServiceResolverRedirect{
Service: "test-failover",
ServiceSubset: "v2",
Namespace: defaultNamespace,
Datacenter: "d",
},
},
verify: verifyResolver,
},
{
name: "redirect to peer",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-redirect",
Partition: defaultPartition,
Namespace: defaultNamespace,
Redirect: &ServiceResolverRedirect{
Service: "test-failover",
Peer: "cluster-01",
},
},
verify: verifyResolver,
},
{
name: "mega splitter", // use one mega object to avoid multiple trips
entry: &ServiceSplitterConfigEntry{
Kind: ServiceSplitter,
Name: "test-split",
Partition: defaultPartition,
Namespace: defaultNamespace,
Splits: []ServiceSplit{
{
Weight: 90,
Service: "test-failover",
ServiceSubset: "v1",
Namespace: defaultNamespace,
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{
"x-foo": "bar",
},
},
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-foo"},
},
},
{
Weight: 10,
Service: "test-redirect",
Namespace: defaultNamespace,
},
},
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
verify: verifySplitter,
},
{
name: "mega router", // use one mega object to avoid multiple trips
entry: &ServiceRouterConfigEntry{
Kind: ServiceRouter,
Name: "test-route",
Partition: defaultPartition,
Namespace: defaultNamespace,
Routes: []ServiceRoute{
{
Match: &ServiceRouteMatch{
HTTP: &ServiceRouteHTTPMatch{
PathPrefix: "/prefix",
Header: []ServiceRouteHTTPMatchHeader{
{Name: "x-debug", Exact: "1"},
},
QueryParam: []ServiceRouteHTTPMatchQueryParam{
{Name: "debug", Exact: "1"},
},
},
},
Destination: &ServiceRouteDestination{
Service: "test-failover",
ServiceSubset: "v2",
Namespace: defaultNamespace,
Partition: defaultPartition,
PrefixRewrite: "/",
RequestTimeout: 5 * time.Second,
NumRetries: 5,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{500, 503, 401},
RetryOn: []string{
"gateway-error",
"reset",
"envoy-ratelimited",
"retriable-4xx",
"refused-stream",
"cancelled",
"deadline-exceeded",
"internal",
"resource-exhausted",
"unavailable",
},
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{
"x-foo": "bar",
},
},
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-foo"},
},
},
},
},
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
verify: verifyRouter,
},
{
name: "mega router case insensitive", // use one mega object to avoid multiple trips
entry: &ServiceRouterConfigEntry{
Kind: ServiceRouter,
Name: "test-route-case-insensitive",
Partition: defaultPartition,
Namespace: defaultNamespace,
Routes: []ServiceRoute{
{
Match: &ServiceRouteMatch{
HTTP: &ServiceRouteHTTPMatch{
PathPrefix: "/prEfix",
CaseInsensitive: true,
Header: []ServiceRouteHTTPMatchHeader{
{Name: "x-debug", Exact: "1"},
},
QueryParam: []ServiceRouteHTTPMatchQueryParam{
{Name: "debug", Exact: "1"},
},
},
},
Destination: &ServiceRouteDestination{
Service: "test-failover",
ServiceSubset: "v2",
Namespace: defaultNamespace,
Partition: defaultPartition,
PrefixRewrite: "/",
RequestTimeout: 5 * time.Second,
NumRetries: 5,
RetryOnConnectFailure: true,
RetryOnStatusCodes: []uint32{500, 503, 401},
RetryOn: []string{
"gateway-error",
"reset",
"envoy-ratelimited",
"retriable-4xx",
"refused-stream",
"cancelled",
"deadline-exceeded",
"internal",
"resource-exhausted",
"unavailable",
},
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{
"x-foo": "bar",
},
},
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-foo"},
},
},
},
},
Meta: map[string]string{
"foo": "bar",
"gir": "zim",
},
},
verify: verifyRouter,
},
} {
tc := tc
name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name)
ok := t.Run(name, func(t *testing.T) {
tc.verify(t, tc.entry)
})
require.True(t, ok, "subtest %q failed so aborting remainder", name)
}
}
2020-08-23 00:05:09 +00:00
func TestAPI_ConfigEntry_ServiceResolver_LoadBalancer(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
config_entries := c.ConfigEntries()
verifyResolver := func(t *testing.T, initial ConfigEntry) {
t.Helper()
require.IsType(t, &ServiceResolverConfigEntry{}, initial)
testEntry := initial.(*ServiceResolverConfigEntry)
// set it
_, wm, err := config_entries.Set(testEntry, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := config_entries.Get(ServiceResolver, testEntry.Name, nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// verify it
readResolver, ok := entry.(*ServiceResolverConfigEntry)
require.True(t, ok)
readResolver.ModifyIndex = 0 // reset for Equals()
readResolver.CreateIndex = 0 // reset for Equals()
require.Equal(t, testEntry, readResolver)
}
// First set the necessary protocols to allow advanced routing features.
for _, service := range []string{
"test-least-req",
"test-ring-hash",
} {
serviceDefaults := &ServiceConfigEntry{
Kind: ServiceDefaults,
Name: service,
Protocol: "http",
}
_, _, err := config_entries.Set(serviceDefaults, nil)
require.NoError(t, err)
}
// NOTE: Due to service graph validation, these have to happen in a specific order.
for _, tc := range []struct {
name string
entry ConfigEntry
verify func(t *testing.T, initial ConfigEntry)
}{
{
name: "least-req",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-least-req",
Partition: defaultPartition,
Namespace: defaultNamespace,
LoadBalancer: &LoadBalancer{
2020-09-11 15:21:43 +00:00
Policy: "least_request",
LeastRequestConfig: &LeastRequestConfig{ChoiceCount: 10},
2020-08-23 00:05:09 +00:00
},
},
verify: verifyResolver,
},
{
name: "ring-hash-with-policies",
entry: &ServiceResolverConfigEntry{
Kind: ServiceResolver,
Name: "test-ring-hash",
Namespace: defaultNamespace,
Partition: defaultPartition,
LoadBalancer: &LoadBalancer{
2020-09-11 15:21:43 +00:00
Policy: "ring_hash",
RingHashConfig: &RingHashConfig{
MinimumRingSize: 1024 * 2,
MaximumRingSize: 1024 * 4,
},
HashPolicies: []HashPolicy{
{
Field: "header",
FieldValue: "my-session-header",
Terminal: true,
2020-08-23 00:05:09 +00:00
},
2020-09-11 15:21:43 +00:00
{
Field: "cookie",
FieldValue: "oreo",
CookieConfig: &CookieConfig{
Path: "/tray",
TTL: 20 * time.Millisecond,
},
2020-08-23 00:05:09 +00:00
},
2020-09-12 00:34:03 +00:00
{
Field: "cookie",
FieldValue: "sugar",
CookieConfig: &CookieConfig{
Session: true,
Path: "/tin",
},
},
2020-09-11 15:21:43 +00:00
{
SourceIP: true,
},
2020-08-23 00:05:09 +00:00
},
},
},
verify: verifyResolver,
},
} {
tc := tc
name := fmt.Sprintf("%s:%s: %s", tc.entry.GetKind(), tc.entry.GetName(), tc.name)
ok := t.Run(name, func(t *testing.T) {
tc.verify(t, tc.entry)
})
require.True(t, ok, "subtest %q failed so aborting remainder", name)
}
}