test(e2e): scoped Ingress hostname and path collision

This commit is contained in:
Dario Tranchitella
2021-08-12 19:30:27 +02:00
parent df08c9e63e
commit 8949be7497
12 changed files with 851 additions and 530 deletions
+9 -7
View File
@@ -30,12 +30,14 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
Kind: "User",
},
},
IngressClasses: &capsulev1beta1.AllowedListSpec{
Exact: []string{
"nginx",
"haproxy",
IngressOptions: capsulev1beta1.IngressOptions{
AllowedClasses: &capsulev1beta1.AllowedListSpec{
Exact: []string{
"nginx",
"haproxy",
},
Regex: "^oil-.*$",
},
Regex: "^oil-.*$",
},
},
}
@@ -133,7 +135,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
for _, c := range tnt.Spec.IngressClasses.Exact {
for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
@@ -171,7 +173,7 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
for _, c := range tnt.Spec.IngressClasses.Exact {
for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
i := &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
+9 -7
View File
@@ -29,12 +29,14 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
Kind: "User",
},
},
IngressClasses: &capsulev1beta1.AllowedListSpec{
Exact: []string{
"nginx",
"haproxy",
IngressOptions: capsulev1beta1.IngressOptions{
AllowedClasses: &capsulev1beta1.AllowedListSpec{
Exact: []string{
"nginx",
"haproxy",
},
Regex: "^oil-.*$",
},
Regex: "^oil-.*$",
},
},
}
@@ -144,7 +146,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
for _, c := range tnt.Spec.IngressClasses.Exact {
for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
i := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
@@ -183,7 +185,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
for _, c := range tnt.Spec.IngressClasses.Exact {
for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact {
Eventually(func() (err error) {
i := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
@@ -1,172 +0,0 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when handling Ingress hostnames collision", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "ingress-hostnames-allowed-collision",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-allowed",
Kind: "User",
},
},
},
}
// scaffold a basic networking.k8s.io Ingress with name and host
networkingIngress := func(name, hostname string) *networkingv1.Ingress {
return &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: hostname,
},
},
},
}
}
// scaffold a basic extensions Ingress with name and host
extensionsIngress := func(name, hostname string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: hostname,
},
},
},
}
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowIngressHostnameCollision = true
})
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowIngressHostnameCollision = false
})
})
It("should not allow creating several Ingress with same hostname", func() {
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowIngressHostnameCollision = false
})
maj, min, _ := GetKubernetesSemVer()
ns := NewNamespace("denied-collision")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io")
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io")
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io")
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io")
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
})
It("should allow creating several Ingress with same hostname", func() {
maj, min, _ := GetKubernetesSemVer()
ns := NewNamespace("allowed-collision")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io")
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io")
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io")
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io")
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
})
}
})
})
@@ -0,0 +1,221 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when handling Cluster scoped Ingress hostnames collision", func() {
tnt1 := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-cluster-one",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-tenant-one",
Kind: "User",
},
},
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster,
},
},
}
tnt2 := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-cluster-two",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-tenant-two",
Kind: "User",
},
},
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeCluster,
},
},
}
// scaffold a basic networking.k8s.io Ingress with name and host
networkingIngress := func(name, hostname, path string) *networkingv1.Ingress {
return &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: hostname,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: path,
PathType: func(v networkingv1.PathType) *networkingv1.PathType {
return &v
}(networkingv1.PathTypeExact),
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "example",
Port: networkingv1.ServiceBackendPort{
Number: 8080,
},
},
},
},
},
},
},
},
},
},
}
}
// scaffold a basic extensions Ingress with name and host
extensionsIngress := func(name, hostname, path string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: hostname,
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
Paths: []extensionsv1beta1.HTTPIngressPath{
{
Path: path,
PathType: func(v extensionsv1beta1.PathType) *extensionsv1beta1.PathType {
return &v
}(extensionsv1beta1.PathTypeExact),
Backend: extensionsv1beta1.IngressBackend{
ServiceName: "example",
ServicePort: intstr.FromInt(8080),
},
},
},
},
},
},
},
},
}
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt1.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt1)
}).Should(Succeed())
EventuallyCreation(func() error {
tnt2.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt2)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt1)).Should(Succeed())
Expect(k8sClient.Delete(context.TODO(), tnt2)).Should(Succeed())
})
It("should ensure Cluster scope for Ingress hostname and path collision", func() {
maj, min, _ := GetKubernetesSemVer()
ns1 := NewNamespace("tenant-one-ns")
cs1 := ownerClient(tnt1.Spec.Owners[0])
NamespaceCreation(ns1, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName()))
ns2 := NewNamespace("tenant-two-ns")
cs2 := ownerClient(tnt2.Spec.Owners[0])
NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io", "/path")
_, err = cs1.NetworkingV1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// Creating a second Ingress with same hostname but a different path in a Namespace managed by the same
// Tenant should not trigger a collision...
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io", "/docs")
_, err = cs2.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// ...but it happens if hostname and path collide with the first Ingress,
// although in a different Namespace
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-3", "kubernetes.io", "/path")
_, err = cs2.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io", "/foo")
_, err = cs1.ExtensionsV1beta1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// Creating a second Ingress with same hostname but a different path in a Namespace managed by the same
// Tenant should not trigger a collision...
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io", "/bar")
_, err = cs2.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// ...but it happens if hostname and path collide with the first Ingress,
// although in a different Namespace
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-3", "cncf.io", "/foo")
_, err = cs2.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
})
})
@@ -0,0 +1,204 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when disabling Ingress hostnames collision", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-disabled",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-disabled",
Kind: "User",
},
},
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeDisabled,
},
},
}
// scaffold a basic networking.k8s.io Ingress with name and host
networkingIngress := func(name, hostname, path string) *networkingv1.Ingress {
return &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: hostname,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: path,
PathType: func(v networkingv1.PathType) *networkingv1.PathType {
return &v
}(networkingv1.PathTypeExact),
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "example",
Port: networkingv1.ServiceBackendPort{
Number: 8080,
},
},
},
},
},
},
},
},
},
},
}
}
// scaffold a basic extensions Ingress with name and host
extensionsIngress := func(name, hostname, path string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: hostname,
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
Paths: []extensionsv1beta1.HTTPIngressPath{
{
Path: path,
PathType: func(v extensionsv1beta1.PathType) *extensionsv1beta1.PathType {
return &v
}(extensionsv1beta1.PathTypeExact),
Backend: extensionsv1beta1.IngressBackend{
ServiceName: "example",
ServicePort: intstr.FromInt(8080),
},
},
},
},
},
},
},
},
}
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should not check any kind of collision", func() {
maj, min, _ := GetKubernetesSemVer()
ns1 := NewNamespace("namespace-collision-one")
ns2 := NewNamespace("namespace-collision-two")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns1.GetName()))
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns2.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-3", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-4", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-3", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-4", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
})
}
})
})
@@ -0,0 +1,206 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when handling Namespace scoped Ingress hostnames collision", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-namespace",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-namespace",
Kind: "User",
},
},
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeNamespace,
},
},
}
// scaffold a basic networking.k8s.io Ingress with name and host
networkingIngress := func(name, hostname, path string) *networkingv1.Ingress {
return &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: hostname,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: path,
PathType: func(v networkingv1.PathType) *networkingv1.PathType {
return &v
}(networkingv1.PathTypeExact),
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "example",
Port: networkingv1.ServiceBackendPort{
Number: 8080,
},
},
},
},
},
},
},
},
},
},
}
}
// scaffold a basic extensions Ingress with name and host
extensionsIngress := func(name, hostname, path string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: hostname,
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
Paths: []extensionsv1beta1.HTTPIngressPath{
{
Path: path,
PathType: func(v extensionsv1beta1.PathType) *extensionsv1beta1.PathType {
return &v
}(extensionsv1beta1.PathTypeExact),
Backend: extensionsv1beta1.IngressBackend{
ServiceName: "example",
ServicePort: intstr.FromInt(8080),
},
},
},
},
},
},
},
},
}
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should ensure Namespace scope for Ingress hostname and path collision", func() {
maj, min, _ := GetKubernetesSemVer()
ns1 := NewNamespace("namespace-collision-one")
ns2 := NewNamespace("namespace-collision-two")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns1.GetName()))
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns2.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// A same Ingress with hostname and path pair can be created in a different Namespace,
// although of the same Tenant
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// ...but a collision occurs if the same pair is created in the same Namespace
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-3", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-4", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// A same Ingress with hostname and path pair can be created in a different Namespace,
// although of the same Tenant
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// ...but a collision occurs if the same pair is created in the same Namespace
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-3", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-4", "cncf.io", "/docs")
_, err = cs.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
})
})
@@ -0,0 +1,192 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when handling Tenant scoped Ingress hostnames collision", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "hostnames-collision-tenant",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-tenant",
Kind: "User",
},
},
IngressOptions: capsulev1beta1.IngressOptions{
HostnameCollisionScope: capsulev1beta1.HostnameCollisionScopeTenant,
},
},
}
// scaffold a basic networking.k8s.io Ingress with name and host
networkingIngress := func(name, hostname, path string) *networkingv1.Ingress {
return &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: hostname,
IngressRuleValue: networkingv1.IngressRuleValue{
HTTP: &networkingv1.HTTPIngressRuleValue{
Paths: []networkingv1.HTTPIngressPath{
{
Path: path,
PathType: func(v networkingv1.PathType) *networkingv1.PathType {
return &v
}(networkingv1.PathTypeExact),
Backend: networkingv1.IngressBackend{
Service: &networkingv1.IngressServiceBackend{
Name: "example",
Port: networkingv1.ServiceBackendPort{
Number: 8080,
},
},
},
},
},
},
},
},
},
},
}
}
// scaffold a basic extensions Ingress with name and host
extensionsIngress := func(name, hostname, path string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: hostname,
IngressRuleValue: extensionsv1beta1.IngressRuleValue{
HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
Paths: []extensionsv1beta1.HTTPIngressPath{
{
Path: path,
PathType: func(v extensionsv1beta1.PathType) *extensionsv1beta1.PathType {
return &v
}(extensionsv1beta1.PathTypeExact),
Backend: extensionsv1beta1.IngressBackend{
ServiceName: "example",
ServicePort: intstr.FromInt(8080),
},
},
},
},
},
},
},
},
}
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should ensure Tenant scope for Ingress hostname and path collision", func() {
maj, min, _ := GetKubernetesSemVer()
ns1 := NewNamespace("cluster-collision-one")
ns2 := NewNamespace("cluster-collision-two")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns1.GetName()))
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns2.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// Creating a second Ingress with same hostname but a different path in a Namespace managed by the same
// Tenant should not trigger a collision...
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io", "/docs")
_, err = cs.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// ...but it happens if hostname and path collide with the first Ingress,
// although in a different Namespace
EventuallyCreation(func() (err error) {
obj := networkingIngress("networking-3", "kubernetes.io", "/path")
_, err = cs.NetworkingV1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io", "/foo")
_, err = cs.ExtensionsV1beta1().Ingresses(ns1.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// Creating a second Ingress with same hostname but a different path in a Namespace managed by the same
// Tenant should not trigger a collision...
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io", "/bar")
_, err = cs.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).Should(Succeed())
// ...but it happens if hostname and path collide with the first Ingress,
// although in a different Namespace
EventuallyCreation(func() (err error) {
obj := extensionsIngress("extensions-3", "cncf.io", "/foo")
_, err = cs.ExtensionsV1beta1().Ingresses(ns2.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}).ShouldNot(Succeed())
})
}
})
})
@@ -1,125 +0,0 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
networkingv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when handling Ingress hostnames collision", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "ingress-hostnames-denied-collision",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "ingress-denied",
Kind: "User",
},
},
},
}
// scaffold a basic networking.k8s.io Ingress with name and host
networkingIngress := func(name, hostname string) *networkingv1.Ingress {
return &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: networkingv1.IngressSpec{
Rules: []networkingv1.IngressRule{
{
Host: hostname,
},
},
},
}
}
// scaffold a basic extensions Ingress with name and host
extensionsIngress := func(name, hostname string) *extensionsv1beta1.Ingress {
return &extensionsv1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: extensionsv1beta1.IngressSpec{
Rules: []extensionsv1beta1.IngressRule{
{
Host: hostname,
},
},
},
}
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowIngressHostnameCollision = true
})
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowIngressHostnameCollision = false
})
})
It("should not allow creating several Ingress with same hostname", func() {
maj, min, _ := GetKubernetesSemVer()
ns := NewNamespace("allowed-collision")
cs := ownerClient(tnt.Spec.Owners[0])
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
if maj == 1 && min > 18 {
By("testing networking.k8s.io", func() {
Eventually(func() (err error) {
obj := networkingIngress("networking-1", "kubernetes.io")
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
Eventually(func() (err error) {
obj := networkingIngress("networking-2", "kubernetes.io")
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
}
if maj == 1 && min < 22 {
By("testing extensions", func() {
Eventually(func() (err error) {
obj := extensionsIngress("extensions-1", "cncf.io")
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
Eventually(func() (err error) {
obj := extensionsIngress("extensions-2", "cncf.io")
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
return
}, defaultTimeoutInterval, defaultPollInterval).ShouldNot(Succeed())
})
}
})
})
+7 -5
View File
@@ -31,9 +31,11 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
Kind: "User",
},
},
IngressHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"},
Regex: `.*\.clastix\.io`,
IngressOptions: capsulev1beta1.IngressOptions{
AllowedHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{"sigs.k8s.io", "operator.sdk", "domain.tld"},
Regex: `.*\.clastix\.io`,
},
},
},
}
@@ -174,7 +176,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
By("testing networking.k8s.io", func() {
for i, h := range tnt.Spec.IngressHostnames.Exact {
for i, h := range tnt.Spec.IngressOptions.AllowedHostnames.Exact {
Eventually(func() (err error) {
obj := networkingIngress(fmt.Sprintf("allowed-networking-%d", i), h)
_, err = cs.NetworkingV1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
@@ -200,7 +202,7 @@ var _ = Describe("when Tenant handles Ingress hostnames", func() {
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
By("testing extensions", func() {
for i, h := range tnt.Spec.IngressHostnames.Exact {
for i, h := range tnt.Spec.IngressOptions.AllowedHostnames.Exact {
Eventually(func() (err error) {
obj := extensionsIngress(fmt.Sprintf("allowed-extensions-%d", i), h)
_, err = cs.ExtensionsV1beta1().Ingresses(ns.GetName()).Create(context.TODO(), obj, metav1.CreateOptions{})
@@ -1,130 +0,0 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
capsulev1alpha1 "github.com/clastix/capsule/api/v1alpha1"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when a second Tenant contains an already declared allowed Ingress hostname", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "allowed-collision-ingress-hostnames",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "first-user",
Kind: "User",
},
},
IngressHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{"capsule.clastix.io", "docs.capsule.k8s", "42.clatix.io"},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowTenantIngressHostnamesCollision = false
})
})
It("should block creation if contains collided Ingress hostnames", func() {
var cleanupFuncs []func()
for i, h := range tnt.Spec.IngressHostnames.Exact {
duplicated := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%d", tnt.GetName(), i),
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "second-user",
Kind: "User",
},
},
IngressHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{h},
},
},
}
EventuallyCreation(func() error {
return k8sClient.Create(context.TODO(), duplicated)
}).ShouldNot(Succeed())
cleanupFuncs = append(cleanupFuncs, func() {
duplicatedTnt := *duplicated
_ = k8sClient.Delete(context.TODO(), &duplicatedTnt)
})
}
for _, fn := range cleanupFuncs {
fn()
}
})
It("should not block creation if contains collided Ingress hostnames", func() {
var cleanupFuncs []func()
ModifyCapsuleConfigurationOpts(func(configuration *capsulev1alpha1.CapsuleConfiguration) {
configuration.Spec.AllowTenantIngressHostnamesCollision = true
})
for i, h := range tnt.Spec.IngressHostnames.Exact {
duplicated := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%d", tnt.GetName(), i),
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "second-user",
Kind: "User",
},
},
IngressHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{h},
},
},
}
EventuallyCreation(func() error {
return k8sClient.Create(context.TODO(), duplicated)
}).Should(Succeed())
cleanupFuncs = append(cleanupFuncs, func() {
duplicatedTnt := *duplicated
_ = k8sClient.Delete(context.TODO(), &duplicatedTnt)
})
}
for _, fn := range cleanupFuncs {
fn()
}
})
})
@@ -1,83 +0,0 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("when a second Tenant contains an already declared allowed Ingress hostname", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "no-collision-ingress-hostnames",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "first-user",
Kind: "User",
},
},
IngressHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{"capsule.clastix.io", "docs.capsule.k8s", "42.clatix.io"},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should block creation if contains collided Ingress hostnames", func() {
var cleanupFuncs []func()
for i, h := range tnt.Spec.IngressHostnames.Exact {
duplicated := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%d", tnt.GetName(), i),
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "second-user",
Kind: "User",
},
},
IngressHostnames: &capsulev1beta1.AllowedListSpec{
Exact: []string{h},
},
},
}
EventuallyCreation(func() error {
return k8sClient.Create(context.TODO(), duplicated)
}).ShouldNot(Succeed())
cleanupFuncs = append(cleanupFuncs, func() {
duplicatedTenant := *duplicated
k8sClient.Delete(context.TODO(), &duplicatedTenant)
})
}
for _, fn := range cleanupFuncs {
fn()
}
})
})
+3 -1
View File
@@ -208,7 +208,9 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() {
n := fmt.Sprintf("capsule-%s-%d", tnt.GetName(), i)
rq := &corev1.ResourceQuota{}
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: n, Namespace: name}, rq)).Should(Succeed())
if err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: n, Namespace: name}, rq); err != nil {
return corev1.ResourceQuotaSpec{}
}
return rq.Spec
}, defaultTimeoutInterval, defaultPollInterval).Should(Equal(s))