Skip to content
This repository has been archived by the owner on Mar 19, 2024. It is now read-only.

k8s/controller: always map "default" Consul namespace to empty string #483

Merged
merged 10 commits into from
Jan 10, 2023
3 changes: 3 additions & 0 deletions .changelog/483.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
consul: fix Consul Enterprise gateway sync issue with Kubernetes namespace mirroring disabled and the Consul destination namespace set to "default"
```
67 changes: 67 additions & 0 deletions internal/commands/server/k8s_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,69 @@ func TestGatewayWithClassConfigChange(t *testing.T) {
testenv.Test(t, feature.Feature())
}

func TestGatewayWithoutNamespaceMirroring(t *testing.T) {
feature := features.New("gateway admission").
Assess("gateway sync without namespace mirroring", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
namespace := e2e.Namespace(ctx)
resources := cfg.Client().Resources(namespace)

// Disable namespace mirroring
ctx, err := e2e.SetNamespaceMirroring(false)(ctx, nil)
require.NoError(t, err)

useHostPorts := false
gcc, gc := createGatewayClassWithParams(ctx, t, resources, GatewayClassConfigParams{
UseHostPorts: &useHostPorts,
})
require.Eventually(t, gatewayClassStatusCheck(ctx, resources, gc.Name, namespace, conditionAccepted), checkTimeout, checkInterval, "gatewayclass not accepted in the allotted time")

// Create an HTTPS Gateway Listener to pass when creating gateways
httpsListener := createHTTPSListener(ctx, t, 443)

// Create a Gateway and wait for it to be ready
// This will attempt to sync to a randomly generated Consul desintation namespace
firstGatewayName := envconf.RandomName("gw", 16)
firstGateway := createGateway(ctx, t, resources, firstGatewayName, namespace, gc, []gwv1beta1.Listener{httpsListener})
require.Eventually(t, gatewayStatusCheck(ctx, resources, firstGatewayName, namespace, conditionReady), checkTimeout, checkInterval, "no gateway found in the allotted time")
checkGatewayConfigAnnotation(ctx, t, resources, firstGatewayName, namespace, gcc)

// Set a different Consul destination namespace
defaultNamespace := ""
ctx, err = e2e.SetConsulNamespace(&defaultNamespace)(ctx, nil)
require.NoError(t, err)

// Create a second Gateway and wait for it to be ready
secondGatewayName := envconf.RandomName("gw", 16)
secondGateway := createGateway(ctx, t, resources, secondGatewayName, namespace, gc, []gwv1beta1.Listener{httpsListener})
require.Eventually(t, gatewayStatusCheck(ctx, resources, secondGatewayName, namespace, conditionReady), checkTimeout, checkInterval, "no gateway found in the allotted time")
checkGatewayConfigAnnotation(ctx, t, resources, secondGatewayName, namespace, gcc)

// Set a different Consul destination namespace
defaultEnterpriseNamespace := "default"
ctx, err = e2e.SetConsulNamespace(&defaultEnterpriseNamespace)(ctx, nil)
require.NoError(t, err)

// Create a third Gateway and wait for it to be ready
thirdGatewayName := envconf.RandomName("gw", 16)
thirdGateway := createGateway(ctx, t, resources, thirdGatewayName, namespace, gc, []gwv1beta1.Listener{httpsListener})
require.Eventually(t, gatewayStatusCheck(ctx, resources, thirdGatewayName, namespace, conditionReady), checkTimeout, checkInterval, "no gateway found in the allotted time")
checkGatewayConfigAnnotation(ctx, t, resources, thirdGatewayName, namespace, gcc)

// Cleanup
assert.NoError(t, resources.Delete(ctx, firstGateway))
assert.NoError(t, resources.Delete(ctx, secondGateway))
assert.NoError(t, resources.Delete(ctx, thirdGateway))

// Re-enable namespace mirroring
ctx, err = e2e.SetNamespaceMirroring(true)(ctx, nil)
require.NoError(t, err)
Comment on lines +172 to +174
Copy link
Contributor Author

@mikemorris mikemorris Jan 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was surprised this would be necessary, as I expected SetUpStack to create a new context for each test, but it appears to be reused - I'm not sure if that's intentional/common, or if that should be changed and this cleanup could be removed?

Without this cleanup, the subsequent TestGatewayBasic e2e fails when running against Consul Enterprise, as it checks for the presence of a Consul service registered in an expected namespace.

If keeping this cleanup logic makes more sense, I'm wondering if e2e.SetConsulNamespace(nil) should also be called here to randomize the Consul namespace again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've run into this issue before when touching the e2e tests as well. I'm not sure what the right solution is because I definitely think spinning up a new kind cluster for each test would make these tests take way longer, but the current set up make it difficult to do any kind of namespace manipulation.


return ctx
})

testenv.Test(t, feature.Feature())
}

func TestGatewayWithReplicas(t *testing.T) {
feature := features.New("gateway class config configure instances").
Assess("gateway is created with appropriate number of replicas set", func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context {
Expand Down Expand Up @@ -285,6 +348,10 @@ func TestGatewayBasic(t *testing.T) {

// check for the service being registered
client := e2e.ConsulClient(ctx)
t.Log("k8s namespace:", e2e.Namespace(ctx))
t.Log("consul namespace:", e2e.ConsulNamespace(ctx))
t.Log("mirroring:", e2e.NamespaceMirroring(ctx))

require.Eventually(t, func() bool {
services, _, err := client.Catalog().Service(gatewayName, "", &api.QueryOptions{
Namespace: e2e.ConsulNamespace(ctx),
Expand Down
14 changes: 12 additions & 2 deletions internal/k8s/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,20 @@ type ConsulNamespaceConfig struct {
}

func (c ConsulNamespaceConfig) Namespace(namespace string) string {
var consulNamespace string

if c.MirrorKubernetesNamespaces {
return c.MirrorKubernetesNamespacePrefix + namespace
consulNamespace = c.MirrorKubernetesNamespacePrefix + namespace
} else {
consulNamespace = c.ConsulDestinationNamespace
}

// Always map the default namespace to "" for compatibility with Consul OSS
if consulNamespace == "default" {
consulNamespace = ""
mikemorris marked this conversation as resolved.
Show resolved Hide resolved
}
return c.ConsulDestinationNamespace

return consulNamespace
}

type Kubernetes struct {
Expand Down
56 changes: 34 additions & 22 deletions internal/testing/e2e/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,14 +510,15 @@ func ConsulHTTPPort(ctx context.Context) int {
return mustGetTestEnvironment(ctx).httpPort
}

func isConsulNamespaceMirroringOn() bool {
return IsEnterprise()
func isConsulNamespaceMirroringOn(ctx context.Context) bool {
return IsEnterprise() && NamespaceMirroring(ctx)
}
func ConsulNamespace(ctx context.Context) string {
if isConsulNamespaceMirroringOn() {
//assume mirroring is on
if isConsulNamespaceMirroringOn(ctx) {
return Namespace(ctx)
}

// Return Consul namespace
return mustGetTestEnvironment(ctx).namespace
}

Expand Down Expand Up @@ -588,27 +589,38 @@ func IsEnterprise() bool {
return strings.HasSuffix(consulImage, "ent")
}

func CreateConsulNamespace(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
if IsEnterprise() {
log.Print("Creating Consul Namespace")
namespace := envconf.RandomName("test", 16)
func SetConsulNamespace(namespace *string) env.Func {
return func(ctx context.Context, _ *envconf.Config) (context.Context, error) {
if IsEnterprise() {
log.Print("Creating Consul Namespace")
if namespace == nil {
name := envconf.RandomName("consul", 16)
namespace = &name
}

consulEnvironment := ctx.Value(consulTestContextKey)
if consulEnvironment == nil {
return ctx, nil
}
env := consulEnvironment.(*consulTestEnvironment)
_, _, err := env.consulClient.Namespaces().Create(&api.Namespace{
Name: namespace,
}, &api.WriteOptions{
Token: env.token,
})
if err != nil {
return nil, err
consulEnvironment := ctx.Value(consulTestContextKey)
if consulEnvironment == nil {
return ctx, nil
}
env := consulEnvironment.(*consulTestEnvironment)

// Passing an empty string for namespace name returns a Consul API error,
// and the default namespace should always exist
if *namespace != "" {
_, _, err := env.consulClient.Namespaces().Create(&api.Namespace{
Name: *namespace,
}, &api.WriteOptions{
Token: env.token,
})
if err != nil {
return nil, err
}
}

env.namespace = *namespace
}
env.namespace = namespace
return ctx, nil
}
return ctx, nil
}

func gatewayConsulAuthMethod(name, token string, k8sConfig *rest.Config) *api.ACLAuthMethod {
Expand Down
13 changes: 13 additions & 0 deletions internal/testing/e2e/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
)

type namespaceContext struct{}
type namespaceMirroringContext struct{}
type clusterNameContext struct{}

var namespaceContextKey = namespaceContext{}
var namespaceMirroringContextKey = namespaceMirroringContext{}
var clusterNameContextKey = clusterNameContext{}

func SetNamespace(namespace string) env.Func {
Expand All @@ -22,6 +24,12 @@ func SetNamespace(namespace string) env.Func {
}
}

func SetNamespaceMirroring(namespaceMirroring bool) env.Func {
return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
return context.WithValue(ctx, namespaceMirroringContextKey, namespaceMirroring), nil
}
}

func Namespace(ctx context.Context) string {
namespace := ctx.Value(namespaceContextKey)
if namespace == nil {
Expand All @@ -30,6 +38,11 @@ func Namespace(ctx context.Context) string {
return namespace.(string)
}

func NamespaceMirroring(ctx context.Context) bool {
namespaceMirroring := ctx.Value(namespaceMirroringContextKey)
return namespaceMirroring.(bool)
}

func SetClusterName(name string) env.Func {
return func(ctx context.Context, cfg *envconf.Config) (context.Context, error) {
return context.WithValue(ctx, clusterNameContextKey, name), nil
Expand Down
2 changes: 1 addition & 1 deletion internal/testing/e2e/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func (p *gatewayTestEnvironment) run(ctx context.Context, namespace string, cfg
CACert: ConsulCA(ctx),
ConsulNamespaceConfig: k8s.ConsulNamespaceConfig{
ConsulDestinationNamespace: ConsulNamespace(ctx),
MirrorKubernetesNamespaces: isConsulNamespaceMirroringOn(),
MirrorKubernetesNamespaces: isConsulNamespaceMirroringOn(ctx),
},
}

Expand Down
5 changes: 4 additions & 1 deletion internal/testing/e2e/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ func SetUpStack(hostRoute string) env.Func {
kindClusterName := envconf.RandomName("consul-api-gateway-test", 30)
namespace := envconf.RandomName("test", 16)

// TODO: should this instead create a new context?

ctx = SetHostRoute(ctx, hostRoute)

for _, f := range []env.Func{
SetClusterName(kindClusterName),
SetNamespace(namespace),
SetNamespaceMirroring(true),
CrossCompileProject,
BuildDockerImage,
CreateKindCluster(kindClusterName),
Expand All @@ -38,7 +41,7 @@ func SetUpStack(hostRoute string) env.Func {
CreateTestConsulContainer(kindClusterName, namespace),
CreateConsulACLPolicy,
CreateConsulAuthMethod(),
CreateConsulNamespace,
SetConsulNamespace(nil),
CreateTestGatewayServer(namespace),
} {
ctx, err = f(ctx, cfg)
Expand Down