// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package api
import (
"encoding/json"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/sdk/testutil"
)
func TestAPI_ConfigEntries ( t * testing . T ) {
t . Parallel ( )
c , s := makeClient ( t )
defer s . Stop ( )
config_entries := c . ConfigEntries ( )
t . Run ( "Proxy Defaults" , func ( t * testing . T ) {
global_proxy := & ProxyConfigEntry {
Kind : ProxyDefaults ,
Name : ProxyConfigGlobal ,
Config : map [ string ] interface { } {
"foo" : "bar" ,
"bar" : 1.0 ,
} ,
MutualTLSMode : MutualTLSModeStrict ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
}
// set it
_ , wm , err := config_entries . Set ( global_proxy , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// get it
entry , qm , err := config_entries . Get ( ProxyDefaults , ProxyConfigGlobal , nil )
require . NoError ( t , err )
require . NotNil ( t , qm )
require . NotEqual ( t , 0 , qm . RequestTime )
// verify it
readProxy , ok := entry . ( * ProxyConfigEntry )
require . True ( t , ok )
require . Equal ( t , global_proxy . Kind , readProxy . Kind )
require . Equal ( t , global_proxy . Name , readProxy . Name )
require . Equal ( t , global_proxy . Config , readProxy . Config )
require . Equal ( t , global_proxy . MutualTLSMode , readProxy . MutualTLSMode )
require . Equal ( t , global_proxy . Meta , readProxy . GetMeta ( ) )
require . Equal ( t , global_proxy . Meta , readProxy . GetMeta ( ) )
global_proxy . Config [ "baz" ] = true
// CAS update fail
written , _ , err := config_entries . CAS ( global_proxy , 0 , nil )
require . NoError ( t , err )
require . False ( t , written )
// CAS update success
written , wm , err = config_entries . CAS ( global_proxy , readProxy . ModifyIndex , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
require . NoError ( t , err )
require . True ( t , written )
// Non CAS update
global_proxy . Config [ "baz" ] = "baz"
_ , wm , err = config_entries . Set ( global_proxy , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// list it
entries , qm , err := config_entries . List ( ProxyDefaults , nil )
require . NoError ( t , err )
require . NotNil ( t , qm )
require . NotEqual ( t , 0 , qm . RequestTime )
require . Len ( t , entries , 1 )
readProxy , ok = entries [ 0 ] . ( * ProxyConfigEntry )
require . True ( t , ok )
require . Equal ( t , global_proxy . Kind , readProxy . Kind )
require . Equal ( t , global_proxy . Name , readProxy . Name )
require . Equal ( t , global_proxy . Config , readProxy . Config )
// delete it
wm , err = config_entries . Delete ( ProxyDefaults , ProxyConfigGlobal , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
_ , _ , err = config_entries . Get ( ProxyDefaults , ProxyConfigGlobal , nil )
require . Error ( t , err )
} )
t . Run ( "Service Defaults" , func ( t * testing . T ) {
service := & ServiceConfigEntry {
Kind : ServiceDefaults ,
Name : "foo" ,
Protocol : "http" ,
MutualTLSMode : MutualTLSModeStrict ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
MaxInboundConnections : 5 ,
BalanceInboundConnections : "exact_balance" ,
LocalConnectTimeoutMs : 5000 ,
LocalRequestTimeoutMs : 7000 ,
}
dest := & DestinationConfig {
Addresses : [ ] string { "my.example.com" } ,
Port : 80 ,
}
service2 := & ServiceConfigEntry {
Kind : ServiceDefaults ,
Name : "bar" ,
Protocol : "http" ,
Destination : dest ,
}
// set it
_ , wm , err := config_entries . Set ( service , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// also set the second one
_ , wm , err = config_entries . Set ( service2 , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// get it
entry , qm , err := config_entries . Get ( ServiceDefaults , "foo" , nil )
require . NoError ( t , err )
require . NotNil ( t , qm )
require . NotEqual ( t , 0 , qm . RequestTime )
// verify it
readService , ok := entry . ( * ServiceConfigEntry )
require . True ( t , ok )
require . Equal ( t , service . Kind , readService . Kind )
require . Equal ( t , service . Name , readService . Name )
require . Equal ( t , service . Protocol , readService . Protocol )
require . Equal ( t , service . MutualTLSMode , readService . MutualTLSMode )
require . Equal ( t , service . Meta , readService . Meta )
require . Equal ( t , service . Meta , readService . GetMeta ( ) )
require . Equal ( t , service . MaxInboundConnections , readService . MaxInboundConnections )
require . Equal ( t , service . BalanceInboundConnections , readService . BalanceInboundConnections )
require . Equal ( t , service . LocalConnectTimeoutMs , readService . LocalConnectTimeoutMs )
require . Equal ( t , service . LocalRequestTimeoutMs , readService . LocalRequestTimeoutMs )
// update it
service . Protocol = "tcp"
// CAS fail
written , _ , err := config_entries . CAS ( service , 0 , nil )
require . NoError ( t , err )
require . False ( t , written )
// CAS success
written , wm , err = config_entries . CAS ( service , readService . ModifyIndex , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
require . True ( t , written )
// update no cas
service . Protocol = "tcp"
_ , wm , err = config_entries . Set ( service , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// list them
entries , qm , err := config_entries . List ( ServiceDefaults , nil )
require . NoError ( t , err )
require . NotNil ( t , qm )
require . NotEqual ( t , 0 , qm . RequestTime )
require . Len ( t , entries , 2 )
for _ , entry = range entries {
switch entry . GetName ( ) {
case "foo" :
// this also verifies that the update value was persisted and
// the updated values are seen
readService , ok = entry . ( * ServiceConfigEntry )
require . True ( t , ok )
require . Equal ( t , service . Kind , readService . Kind )
require . Equal ( t , service . Name , readService . Name )
require . Equal ( t , service . Protocol , readService . Protocol )
case "bar" :
readService , ok = entry . ( * ServiceConfigEntry )
require . True ( t , ok )
require . Equal ( t , service2 . Kind , readService . Kind )
require . Equal ( t , service2 . Name , readService . Name )
require . Equal ( t , service2 . Protocol , readService . Protocol )
require . Equal ( t , dest , readService . Destination )
}
}
// delete it
wm , err = config_entries . Delete ( ServiceDefaults , "foo" , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// verify deletion
_ , _ , err = config_entries . Get ( ServiceDefaults , "foo" , nil )
require . Error ( t , err )
} )
t . Run ( "Mesh" , func ( t * testing . T ) {
mesh := & MeshConfigEntry {
TransparentProxy : TransparentProxyMeshConfig { MeshDestinationsOnly : true } ,
AllowEnablingPermissiveMutualTLS : true ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Partition : defaultPartition ,
Namespace : defaultNamespace ,
}
ce := c . ConfigEntries ( )
testutil . RunStep ( t , "set and get" , func ( t * testing . T ) {
_ , wm , err := ce . Set ( mesh , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
entry , qm , err := ce . Get ( MeshConfig , MeshConfigMesh , nil )
require . NoError ( t , err )
require . NotNil ( t , qm )
require . NotEqual ( t , 0 , qm . RequestTime )
result , ok := entry . ( * MeshConfigEntry )
require . True ( t , ok )
// ignore indexes
result . CreateIndex = 0
result . ModifyIndex = 0
require . Equal ( t , mesh , result )
} )
testutil . RunStep ( t , "list" , func ( t * testing . T ) {
entries , qm , err := ce . List ( MeshConfig , nil )
require . NoError ( t , err )
require . NotNil ( t , qm )
require . NotEqual ( t , 0 , qm . RequestTime )
require . Len ( t , entries , 1 )
} )
testutil . RunStep ( t , "delete" , func ( t * testing . T ) {
wm , err := ce . Delete ( MeshConfig , MeshConfigMesh , nil )
require . NoError ( t , err )
require . NotNil ( t , wm )
require . NotEqual ( t , 0 , wm . RequestTime )
// verify deletion
_ , _ , err = ce . Get ( MeshConfig , MeshConfigMesh , nil )
require . Error ( t , err )
} )
} )
t . Run ( "CAS deletion" , func ( t * testing . T ) {
entry := & ProxyConfigEntry {
Kind : ProxyDefaults ,
Name : ProxyConfigGlobal ,
Config : map [ string ] interface { } {
"foo" : "bar" ,
} ,
}
// Create a config entry.
created , _ , err := config_entries . Set ( entry , nil )
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
3 years ago
require . NoError ( t , err )
require . True ( t , created , "entry should have been created" )
// Read it back to get the ModifyIndex.
result , _ , err := config_entries . Get ( entry . Kind , entry . Name , nil )
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
3 years ago
require . NoError ( t , err )
require . NotNil ( t , entry )
// Attempt a deletion with an invalid index.
deleted , _ , err := config_entries . DeleteCAS ( entry . Kind , entry . Name , result . GetModifyIndex ( ) - 1 , nil )
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
3 years ago
require . NoError ( t , err )
require . False ( t , deleted , "entry should not have been deleted" )
// Attempt a deletion with a valid index.
deleted , _ , err = config_entries . DeleteCAS ( entry . Kind , entry . Name , result . GetModifyIndex ( ) , nil )
bulk rewrite using this script
set -euo pipefail
unset CDPATH
cd "$(dirname "$0")"
for f in $(git grep '\brequire := require\.New(' | cut -d':' -f1 | sort -u); do
echo "=== require: $f ==="
sed -i '/require := require.New(t)/d' $f
# require.XXX(blah) but not require.XXX(tblah) or require.XXX(rblah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\([^tr]\)/require.\1(t,\2/g' $f
# require.XXX(tblah) but not require.XXX(t, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/require.\1(t,\2/g' $f
# require.XXX(rblah) but not require.XXX(r, blah)
sed -i 's/\brequire\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/require.\1(t,\2/g' $f
gofmt -s -w $f
done
for f in $(git grep '\bassert := assert\.New(' | cut -d':' -f1 | sort -u); do
echo "=== assert: $f ==="
sed -i '/assert := assert.New(t)/d' $f
# assert.XXX(blah) but not assert.XXX(tblah) or assert.XXX(rblah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\([^tr]\)/assert.\1(t,\2/g' $f
# assert.XXX(tblah) but not assert.XXX(t, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(t[^,]\)/assert.\1(t,\2/g' $f
# assert.XXX(rblah) but not assert.XXX(r, blah)
sed -i 's/\bassert\.\([a-zA-Z0-9_]*\)(\(r[^,]\)/assert.\1(t,\2/g' $f
gofmt -s -w $f
done
3 years ago
require . NoError ( t , err )
require . True ( t , deleted , "entry should have been deleted" )
} )
}
func TestDecodeConfigEntry ( t * testing . T ) {
t . Parallel ( )
for _ , tc := range [ ] struct {
name string
body string
expect ConfigEntry
expectErr string
} {
{
name : "expose-paths: kitchen sink proxy" ,
body : `
{
"Kind" : "proxy-defaults" ,
"Name" : "global" ,
"Expose" : {
"Checks" : true ,
"Paths" : [
{
"LocalPathPort" : 8080 ,
"ListenerPort" : 21500 ,
"Path" : "/healthz" ,
"Protocol" : "http2"
}
]
}
}
` ,
expect : & ProxyConfigEntry {
Kind : "proxy-defaults" ,
Name : "global" ,
Expose : ExposeConfig {
Checks : true ,
Paths : [ ] ExposePath {
{
LocalPathPort : 8080 ,
ListenerPort : 21500 ,
Path : "/healthz" ,
Protocol : "http2" ,
} ,
} ,
} ,
} ,
} ,
{
name : "expose-paths: kitchen sink service default" ,
body : `
{
"Kind" : "service-defaults" ,
"Name" : "global" ,
"Expose" : {
"Checks" : true ,
"Paths" : [
{
"LocalPathPort" : 8080 ,
"ListenerPort" : 21500 ,
"Path" : "/healthz" ,
"Protocol" : "http2"
}
]
}
}
` ,
expect : & ServiceConfigEntry {
Kind : "service-defaults" ,
Name : "global" ,
Expose : ExposeConfig {
Checks : true ,
Paths : [ ] ExposePath {
{
LocalPathPort : 8080 ,
ListenerPort : 21500 ,
Path : "/healthz" ,
Protocol : "http2" ,
} ,
} ,
} ,
} ,
} ,
{
name : "proxy-defaults" ,
body : `
{
"Kind" : "proxy-defaults" ,
"Name" : "main" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Config" : {
"foo" : 19 ,
"bar" : "abc" ,
"moreconfig" : {
"moar" : "config"
}
} ,
"MeshGateway" : {
"Mode" : "remote"
} ,
"Mode" : "transparent" ,
"TransparentProxy" : {
"OutboundListenerPort" : 808 ,
"DialedDirectly" : true
} ,
"AccessLogs" : {
"Enabled" : true ,
"DisableListenerLogs" : true ,
"Type" : "file" ,
"Path" : "/tmp/logs.txt" ,
"TextFormat" : "[%START_TIME%]"
} ,
"FailoverPolicy" : {
"Mode" : "default"
}
}
` ,
expect : & ProxyConfigEntry {
Kind : "proxy-defaults" ,
Name : "main" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Config : map [ string ] interface { } {
"foo" : float64 ( 19 ) ,
"bar" : "abc" ,
"moreconfig" : map [ string ] interface { } {
"moar" : "config" ,
} ,
} ,
MeshGateway : MeshGatewayConfig {
Mode : MeshGatewayModeRemote ,
} ,
Mode : ProxyModeTransparent ,
TransparentProxy : & TransparentProxyConfig {
OutboundListenerPort : 808 ,
DialedDirectly : true ,
} ,
AccessLogs : & AccessLogsConfig {
Enabled : true ,
DisableListenerLogs : true ,
Type : FileLogSinkType ,
Path : "/tmp/logs.txt" ,
TextFormat : "[%START_TIME%]" ,
} ,
FailoverPolicy : & ServiceResolverFailoverPolicy {
Mode : "default" ,
} ,
} ,
} ,
{
name : "service-defaults" ,
body : `
{
"Kind" : "service-defaults" ,
"Name" : "main" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Protocol" : "http" ,
"ExternalSNI" : "abc-123" ,
"MeshGateway" : {
"Mode" : "remote"
} ,
"Mode" : "transparent" ,
"TransparentProxy" : {
"OutboundListenerPort" : 808 ,
"DialedDirectly" : true
} ,
"BalanceInboundConnections" : "exact_balance" ,
"UpstreamConfig" : {
"Overrides" : [
{
"Name" : "redis" ,
"PassiveHealthCheck" : {
"MaxFailures" : 3 ,
"Interval" : "2s" ,
"EnforcingConsecutive5xx" : 60 ,
"MaxEjectionPercent" : 4 ,
"BaseEjectionTime" : "5s"
} ,
"BalanceOutboundConnections" : "exact_balance"
} ,
{
"Name" : "finance--billing" ,
"MeshGateway" : {
"Mode" : "remote"
}
}
] ,
"Defaults" : {
"EnvoyClusterJSON" : "zip" ,
"EnvoyListenerJSON" : "zop" ,
"ConnectTimeoutMs" : 5000 ,
"Protocol" : "http" ,
"Limits" : {
"MaxConnections" : 3 ,
"MaxPendingRequests" : 4 ,
"MaxConcurrentRequests" : 5
} ,
"PassiveHealthCheck" : {
"MaxFailures" : 5 ,
"Interval" : "4s" ,
"EnforcingConsecutive5xx" : 61 ,
"MaxEjectionPercent" : 5 ,
"BaseEjectionTime" : "6s"
}
}
}
}
` ,
expect : & ServiceConfigEntry {
Kind : "service-defaults" ,
Name : "main" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Protocol : "http" ,
ExternalSNI : "abc-123" ,
MeshGateway : MeshGatewayConfig {
Mode : MeshGatewayModeRemote ,
} ,
Mode : ProxyModeTransparent ,
TransparentProxy : & TransparentProxyConfig {
OutboundListenerPort : 808 ,
DialedDirectly : true ,
} ,
BalanceInboundConnections : "exact_balance" ,
UpstreamConfig : & UpstreamConfiguration {
Overrides : [ ] * UpstreamConfig {
{
Name : "redis" ,
PassiveHealthCheck : & PassiveHealthCheck {
MaxFailures : 3 ,
Interval : 2 * time . Second ,
EnforcingConsecutive5xx : uint32Pointer ( 60 ) ,
MaxEjectionPercent : uint32Pointer ( 4 ) ,
BaseEjectionTime : durationPointer ( 5 * time . Second ) ,
} ,
BalanceOutboundConnections : "exact_balance" ,
} ,
{
Name : "finance--billing" ,
MeshGateway : MeshGatewayConfig { Mode : "remote" } ,
} ,
} ,
Defaults : & UpstreamConfig {
EnvoyClusterJSON : "zip" ,
EnvoyListenerJSON : "zop" ,
Protocol : "http" ,
ConnectTimeoutMs : 5000 ,
Limits : & UpstreamLimits {
MaxConnections : intPointer ( 3 ) ,
MaxPendingRequests : intPointer ( 4 ) ,
MaxConcurrentRequests : intPointer ( 5 ) ,
} ,
PassiveHealthCheck : & PassiveHealthCheck {
MaxFailures : 5 ,
Interval : 4 * time . Second ,
EnforcingConsecutive5xx : uint32Pointer ( 61 ) ,
MaxEjectionPercent : uint32Pointer ( 5 ) ,
BaseEjectionTime : durationPointer ( 6 * time . Second ) ,
} ,
} ,
} ,
} ,
} ,
{
name : "service-defaults-endpoint" ,
body : `
{
"Kind" : "service-defaults" ,
"Name" : "external" ,
"Protocol" : "http" ,
"Destination" : {
"Addresses" : [
"1.2.3.4"
] ,
"Port" : 443
}
}
` ,
expect : & ServiceConfigEntry {
Kind : "service-defaults" ,
Name : "external" ,
Protocol : "http" ,
Destination : & DestinationConfig {
Addresses : [ ] string { "1.2.3.4" } ,
Port : 443 ,
} ,
} ,
} ,
{
name : "service-router: kitchen sink" ,
body : `
{
"Kind" : "service-router" ,
"Name" : "main" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Routes" : [
{
"Match" : {
"HTTP" : {
"PathExact" : "/foo" ,
"Header" : [
{
"Name" : "debug1" ,
"Present" : true
} ,
{
"Name" : "debug2" ,
"Present" : false ,
"Invert" : true
} ,
{
"Name" : "debug3" ,
"Exact" : "1"
} ,
{
"Name" : "debug4" ,
"Prefix" : "aaa"
} ,
{
"Name" : "debug5" ,
"Suffix" : "bbb"
} ,
{
"Name" : "debug6" ,
"Regex" : "a.*z"
}
]
}
} ,
"Destination" : {
"Service" : "carrot" ,
"ServiceSubset" : "kale" ,
"Namespace" : "leek" ,
"PrefixRewrite" : "/alternate" ,
"RequestTimeout" : "99s" ,
"IdleTimeout" : "99s" ,
"NumRetries" : 12345 ,
"RetryOnConnectFailure" : true ,
"RetryOnStatusCodes" : [ 401 , 209 ]
}
} ,
{
"Match" : {
"HTTP" : {
"PathPrefix" : "/foo" ,
"Methods" : [ "GET" , "DELETE" ] ,
"QueryParam" : [
{
"Name" : "hack1" ,
"Present" : true
} ,
{
"Name" : "hack2" ,
"Exact" : "1"
} ,
{
"Name" : "hack3" ,
"Regex" : "a.*z"
}
]
}
}
} ,
{
"Match" : {
"HTTP" : {
"PathRegex" : "/foo"
}
}
}
]
}
` ,
expect : & ServiceRouterConfigEntry {
Kind : "service-router" ,
Name : "main" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Routes : [ ] ServiceRoute {
{
Match : & ServiceRouteMatch {
HTTP : & ServiceRouteHTTPMatch {
PathExact : "/foo" ,
Header : [ ] ServiceRouteHTTPMatchHeader {
{
Name : "debug1" ,
Present : true ,
} ,
{
Name : "debug2" ,
Present : false ,
Invert : true ,
} ,
{
Name : "debug3" ,
Exact : "1" ,
} ,
{
Name : "debug4" ,
Prefix : "aaa" ,
} ,
{
Name : "debug5" ,
Suffix : "bbb" ,
} ,
{
Name : "debug6" ,
Regex : "a.*z" ,
} ,
} ,
} ,
} ,
Destination : & ServiceRouteDestination {
Service : "carrot" ,
ServiceSubset : "kale" ,
Namespace : "leek" ,
PrefixRewrite : "/alternate" ,
RequestTimeout : 99 * time . Second ,
IdleTimeout : 99 * time . Second ,
NumRetries : 12345 ,
RetryOnConnectFailure : true ,
RetryOnStatusCodes : [ ] uint32 { 401 , 209 } ,
} ,
} ,
{
Match : & ServiceRouteMatch {
HTTP : & ServiceRouteHTTPMatch {
PathPrefix : "/foo" ,
Methods : [ ] string { "GET" , "DELETE" } ,
QueryParam : [ ] ServiceRouteHTTPMatchQueryParam {
{
Name : "hack1" ,
Present : true ,
} ,
{
Name : "hack2" ,
Exact : "1" ,
} ,
{
Name : "hack3" ,
Regex : "a.*z" ,
} ,
} ,
} ,
} ,
} ,
{
Match : & ServiceRouteMatch {
HTTP : & ServiceRouteHTTPMatch {
PathRegex : "/foo" ,
} ,
} ,
} ,
} ,
} ,
} ,
{
name : "service-splitter: kitchen sink" ,
body : `
{
"Kind" : "service-splitter" ,
"Name" : "main" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Splits" : [
{
"Weight" : 99.1 ,
"ServiceSubset" : "v1"
} ,
{
"Weight" : 0.9 ,
"Service" : "other" ,
"Namespace" : "alt"
}
]
}
` ,
expect : & ServiceSplitterConfigEntry {
Kind : ServiceSplitter ,
Name : "main" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Splits : [ ] ServiceSplit {
{
Weight : 99.1 ,
ServiceSubset : "v1" ,
} ,
{
Weight : 0.9 ,
Service : "other" ,
Namespace : "alt" ,
} ,
} ,
} ,
} ,
{
name : "service-resolver: subsets with failover" ,
body : `
{
"Kind" : "service-resolver" ,
"Name" : "main" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"DefaultSubset" : "v1" ,
"ConnectTimeout" : "15s" ,
"Subsets" : {
"v1" : {
"Filter" : "Service.Meta.version == v1"
} ,
"v2" : {
"Filter" : "Service.Meta.version == v2" ,
"OnlyPassing" : true
}
} ,
"Failover" : {
"v2" : {
"Service" : "failcopy" ,
"ServiceSubset" : "sure" ,
"Namespace" : "neighbor" ,
"Datacenters" : [ "dc5" , "dc14" ]
} ,
"*" : {
"Datacenters" : [ "dc7" ]
}
}
} ` ,
expect : & ServiceResolverConfigEntry {
Kind : "service-resolver" ,
Name : "main" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
DefaultSubset : "v1" ,
ConnectTimeout : 15 * time . Second ,
Subsets : map [ string ] ServiceResolverSubset {
"v1" : {
Filter : "Service.Meta.version == v1" ,
} ,
"v2" : {
Filter : "Service.Meta.version == v2" ,
OnlyPassing : true ,
} ,
} ,
Failover : map [ string ] ServiceResolverFailover {
"v2" : {
Service : "failcopy" ,
ServiceSubset : "sure" ,
Namespace : "neighbor" ,
Datacenters : [ ] string { "dc5" , "dc14" } ,
} ,
"*" : {
Datacenters : [ ] string { "dc7" } ,
} ,
} ,
} ,
} ,
{
name : "service-resolver: redirect" ,
body : `
{
"Kind" : "service-resolver" ,
"Name" : "main" ,
"Redirect" : {
"Service" : "other" ,
"ServiceSubset" : "backup" ,
"Namespace" : "alt" ,
"Datacenter" : "dc9"
}
}
` ,
expect : & ServiceResolverConfigEntry {
Kind : "service-resolver" ,
Name : "main" ,
Redirect : & ServiceResolverRedirect {
Service : "other" ,
ServiceSubset : "backup" ,
Namespace : "alt" ,
Datacenter : "dc9" ,
} ,
} ,
} ,
{
name : "service-resolver: default" ,
body : `
{
"Kind" : "service-resolver" ,
"Name" : "main"
}
` ,
expect : & ServiceResolverConfigEntry {
Kind : "service-resolver" ,
Name : "main" ,
} ,
} ,
{
name : "service-resolver: envoy hash lb kitchen sink" ,
body : `
{
"Kind" : "service-resolver" ,
"Name" : "main" ,
"LoadBalancer" : {
"Policy" : "ring_hash" ,
"RingHashConfig" : {
"MinimumRingSize" : 1 ,
"MaximumRingSize" : 2
} ,
"HashPolicies" : [
{
"Field" : "cookie" ,
"FieldValue" : "good-cookie" ,
"CookieConfig" : {
"TTL" : "1s" ,
"Path" : "/oven"
} ,
"Terminal" : true
} ,
{
"Field" : "cookie" ,
"FieldValue" : "less-good-cookie" ,
"CookieConfig" : {
"Session" : true ,
"Path" : "/toaster"
} ,
"Terminal" : true
} ,
{
"Field" : "header" ,
"FieldValue" : "x-user-id"
} ,
{
"SourceIP" : true
}
]
}
}
` ,
expect : & ServiceResolverConfigEntry {
Kind : "service-resolver" ,
Name : "main" ,
LoadBalancer : & LoadBalancer {
Policy : "ring_hash" ,
RingHashConfig : & RingHashConfig {
MinimumRingSize : 1 ,
MaximumRingSize : 2 ,
} ,
HashPolicies : [ ] HashPolicy {
{
Field : "cookie" ,
FieldValue : "good-cookie" ,
CookieConfig : & CookieConfig {
TTL : 1 * time . Second ,
Path : "/oven" ,
} ,
Terminal : true ,
} ,
{
Field : "cookie" ,
FieldValue : "less-good-cookie" ,
CookieConfig : & CookieConfig {
Session : true ,
Path : "/toaster" ,
} ,
Terminal : true ,
} ,
{
Field : "header" ,
FieldValue : "x-user-id" ,
} ,
{
SourceIP : true ,
} ,
} ,
} ,
} ,
} ,
{
name : "service-resolver: envoy least request kitchen sink" ,
body : `
{
"Kind" : "service-resolver" ,
"Name" : "main" ,
"LoadBalancer" : {
"Policy" : "least_request" ,
"LeastRequestConfig" : {
"ChoiceCount" : 2
}
}
}
` ,
expect : & ServiceResolverConfigEntry {
Kind : "service-resolver" ,
Name : "main" ,
LoadBalancer : & LoadBalancer {
Policy : "least_request" ,
LeastRequestConfig : & LeastRequestConfig {
ChoiceCount : 2 ,
} ,
} ,
} ,
} ,
{
// TODO(rb): test SDS stuff here in both places (global/service)
name : "ingress-gateway" ,
body : `
{
"Kind" : "ingress-gateway" ,
"Name" : "ingress-web" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Tls" : {
"Enabled" : true ,
"TLSMinVersion" : "TLSv1_1" ,
"TLSMaxVersion" : "TLSv1_2" ,
"CipherSuites" : [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
} ,
"Listeners" : [
{
"Port" : 8080 ,
"Protocol" : "http" ,
"Services" : [
{
"Name" : "web" ,
"Namespace" : "foo" ,
"Partition" : "bar"
} ,
{
"Name" : "db"
}
]
} ,
{
"Port" : 9999 ,
"Protocol" : "tcp" ,
"Services" : [
{
"Name" : "mysql"
}
]
}
]
}
` ,
expect : & IngressGatewayConfigEntry {
Kind : "ingress-gateway" ,
Name : "ingress-web" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
TLS : GatewayTLSConfig {
Enabled : true ,
TLSMinVersion : "TLSv1_1" ,
TLSMaxVersion : "TLSv1_2" ,
CipherSuites : [ ] string {
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ,
} ,
} ,
Listeners : [ ] IngressListener {
{
Port : 8080 ,
Protocol : "http" ,
Services : [ ] IngressService {
{
Name : "web" ,
Namespace : "foo" ,
Partition : "bar" ,
} ,
{
Name : "db" ,
} ,
} ,
} ,
{
Port : 9999 ,
Protocol : "tcp" ,
Services : [ ] IngressService {
{
Name : "mysql" ,
} ,
} ,
} ,
} ,
} ,
} ,
{
name : "terminating-gateway" ,
body : `
{
"Kind" : "terminating-gateway" ,
"Name" : "terminating-west" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Services" : [
{
"Namespace" : "foo" ,
"Name" : "web" ,
"CAFile" : "/etc/ca.pem" ,
"CertFile" : "/etc/cert.pem" ,
"KeyFile" : "/etc/tls.key" ,
"SNI" : "mydomain"
} ,
{
"Name" : "api"
} ,
{
"Namespace" : "bar" ,
"Name" : "*"
}
]
} ` ,
expect : & TerminatingGatewayConfigEntry {
Kind : "terminating-gateway" ,
Name : "terminating-west" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Services : [ ] LinkedService {
{
Namespace : "foo" ,
Name : "web" ,
CAFile : "/etc/ca.pem" ,
CertFile : "/etc/cert.pem" ,
KeyFile : "/etc/tls.key" ,
SNI : "mydomain" ,
} ,
{
Name : "api" ,
} ,
{
Namespace : "bar" ,
Name : "*" ,
} ,
} ,
} ,
} ,
{
name : "service-intentions: kitchen sink" ,
body : `
{
"Kind" : "service-intentions" ,
"Name" : "web" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"Sources" : [
{
"Name" : "foo" ,
"Action" : "deny" ,
"Type" : "consul" ,
"Description" : "foo desc"
} ,
{
"Name" : "bar" ,
"Action" : "allow" ,
"Description" : "bar desc"
} ,
{
"Name" : "l7" ,
"Permissions" : [
{
"Action" : "deny" ,
"HTTP" : {
"PathExact" : "/admin" ,
"Header" : [
{
"Name" : "hdr-present" ,
"Present" : true
} ,
{
"Name" : "hdr-exact" ,
"Exact" : "exact"
} ,
{
"Name" : "hdr-prefix" ,
"Prefix" : "prefix"
} ,
{
"Name" : "hdr-suffix" ,
"Suffix" : "suffix"
} ,
{
"Name" : "hdr-regex" ,
"Regex" : "regex"
} ,
{
"Name" : "hdr-absent" ,
"Present" : true ,
"Invert" : true
}
]
}
} ,
{
"Action" : "allow" ,
"HTTP" : {
"PathPrefix" : "/v3/"
}
} ,
{
"Action" : "allow" ,
"HTTP" : {
"PathRegex" : "/v[12]/.*" ,
"Methods" : [
"GET" ,
"POST"
]
}
}
]
} ,
{
"Name" : "*" ,
"Action" : "deny" ,
"Description" : "wild desc"
}
]
}
` ,
expect : & ServiceIntentionsConfigEntry {
Kind : "service-intentions" ,
Name : "web" ,
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
Sources : [ ] * SourceIntention {
{
Name : "foo" ,
Action : "deny" ,
Type : "consul" ,
Description : "foo desc" ,
} ,
{
Name : "bar" ,
Action : "allow" ,
Description : "bar desc" ,
} ,
{
Name : "l7" ,
Permissions : [ ] * IntentionPermission {
{
Action : "deny" ,
HTTP : & IntentionHTTPPermission {
PathExact : "/admin" ,
Header : [ ] IntentionHTTPHeaderPermission {
{
Name : "hdr-present" ,
Present : true ,
} ,
{
Name : "hdr-exact" ,
Exact : "exact" ,
} ,
{
Name : "hdr-prefix" ,
Prefix : "prefix" ,
} ,
{
Name : "hdr-suffix" ,
Suffix : "suffix" ,
} ,
{
Name : "hdr-regex" ,
Regex : "regex" ,
} ,
{
Name : "hdr-absent" ,
Present : true ,
Invert : true ,
} ,
} ,
} ,
} ,
{
Action : "allow" ,
HTTP : & IntentionHTTPPermission {
PathPrefix : "/v3/" ,
} ,
} ,
{
Action : "allow" ,
HTTP : & IntentionHTTPPermission {
PathRegex : "/v[12]/.*" ,
Methods : [ ] string { "GET" , "POST" } ,
} ,
} ,
} ,
} ,
{
Name : "*" ,
Action : "deny" ,
Description : "wild desc" ,
} ,
} ,
} ,
} ,
{
name : "mesh" ,
body : `
{
"Kind" : "mesh" ,
"Meta" : {
"foo" : "bar" ,
"gir" : "zim"
} ,
"TransparentProxy" : {
"MeshDestinationsOnly" : true
} ,
"TLS" : {
"Incoming" : {
"TLSMinVersion" : "TLSv1_1" ,
"TLSMaxVersion" : "TLSv1_2" ,
"CipherSuites" : [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
} ,
"Outgoing" : {
"TLSMinVersion" : "TLSv1_1" ,
"TLSMaxVersion" : "TLSv1_2" ,
"CipherSuites" : [
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
]
}
} ,
"HTTP" : {
"SanitizeXForwardedClientCert" : true
} ,
"Peering" : {
"PeerThroughMeshGateways" : true
}
}
` ,
expect : & MeshConfigEntry {
Meta : map [ string ] string {
"foo" : "bar" ,
"gir" : "zim" ,
} ,
TransparentProxy : TransparentProxyMeshConfig {
MeshDestinationsOnly : true ,
} ,
TLS : & MeshTLSConfig {
Incoming : & MeshDirectionalTLSConfig {
TLSMinVersion : "TLSv1_1" ,
TLSMaxVersion : "TLSv1_2" ,
CipherSuites : [ ] string {
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ,
} ,
} ,
Outgoing : & MeshDirectionalTLSConfig {
TLSMinVersion : "TLSv1_1" ,
TLSMaxVersion : "TLSv1_2" ,
CipherSuites : [ ] string {
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" ,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ,
} ,
} ,
} ,
HTTP : & MeshHTTPConfig {
SanitizeXForwardedClientCert : true ,
} ,
Peering : & PeeringMeshConfig {
PeerThroughMeshGateways : true ,
} ,
} ,
} ,
} {
tc := tc
t . Run ( tc . name + ": DecodeConfigEntry" , func ( t * testing . T ) {
var raw map [ string ] interface { }
require . NoError ( t , json . Unmarshal ( [ ] byte ( tc . body ) , & raw ) )
got , err := DecodeConfigEntry ( raw )
require . NoError ( t , err )
require . Equal ( t , tc . expect , got )
} )
t . Run ( tc . name + ": DecodeConfigEntryFromJSON" , func ( t * testing . T ) {
got , err := DecodeConfigEntryFromJSON ( [ ] byte ( tc . body ) )
require . NoError ( t , err )
require . Equal ( t , tc . expect , got )
} )
t . Run ( tc . name + ": DecodeConfigEntrySlice" , func ( t * testing . T ) {
var raw [ ] map [ string ] interface { }
require . NoError ( t , json . Unmarshal ( [ ] byte ( "[" + tc . body + "]" ) , & raw ) )
got , err := decodeConfigEntrySlice ( raw )
require . NoError ( t , err )
require . Len ( t , got , 1 )
require . Equal ( t , tc . expect , got [ 0 ] )
} )
}
}
func intPointer ( v int ) * int {
return & v
}
func uint32Pointer ( v uint32 ) * uint32 {
return & v
}
func durationPointer ( d time . Duration ) * time . Duration {
return & d
}