support watch namespaces matched namespace selector (#7472)

skip caching namespaces at cluster scope if only watching single namespace

add --watch-namespace-selector in user guide

add e2e test
This commit is contained in:
zryfish 2021-11-13 03:46:28 +08:00 committed by GitHub
parent 67e13bf692
commit 7203a0b8bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 461 additions and 19 deletions

View file

@ -0,0 +1,36 @@
# TODO: remove the need to use fullnameOverride
fullnameOverride: nginx-ingress
controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest:
containerPort:
http: "1080"
https: "1443"
extraArgs:
http-port: "1080"
https-port: "1443"
# e2e tests do not require information about ingress status
update-status: "false"
ingressClassResource:
# We will create and remove each IC/ClusterRole/ClusterRoleBinding per test so there's no conflict
enabled: false
scope:
enabled: false
namespaceSelector: "foo=bar"
config:
worker-processes: "1"
service:
type: NodePort
admissionWebhooks:
enabled: false
defaultBackend:
enabled: false
rbac:
create: true
scope: false

View file

@ -55,7 +55,15 @@ func (f *Framework) NewEchoDeploymentWithReplicas(replicas int) {
// replicas is configurable and
// name is configurable
func (f *Framework) NewEchoDeploymentWithNameAndReplicas(name string, replicas int) {
deployment := newDeployment(name, f.Namespace, "k8s.gcr.io/ingress-nginx/e2e-test-echo@sha256:131ece0637b29231470cfaa04690c2966a2e0b147d3c9df080a0857b78982410", 80, int32(replicas),
f.newEchoDeployment(f.Namespace, name, replicas)
}
func (f *Framework) NewEchoDeploymentWithNamespaceAndReplicas(namespace string, replicas int) {
f.newEchoDeployment(namespace, EchoService, replicas)
}
func (f *Framework) newEchoDeployment(namespace, name string, replicas int) {
deployment := newDeployment(name, namespace, "k8s.gcr.io/ingress-nginx/e2e-test-echo@sha256:131ece0637b29231470cfaa04690c2966a2e0b147d3c9df080a0857b78982410", 80, int32(replicas),
nil,
[]corev1.VolumeMount{},
[]corev1.Volume{},
@ -66,7 +74,7 @@ func (f *Framework) NewEchoDeploymentWithNameAndReplicas(name string, replicas i
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: f.Namespace,
Namespace: namespace,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
@ -85,7 +93,7 @@ func (f *Framework) NewEchoDeploymentWithNameAndReplicas(name string, replicas i
f.EnsureService(service)
err := WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, replicas)
err := WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, namespace, replicas)
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
}

View file

@ -126,7 +126,7 @@ func (f *Framework) AfterEach() {
defer func(kubeClient kubernetes.Interface, ns string) {
go func() {
defer ginkgo.GinkgoRecover()
err := deleteKubeNamespace(kubeClient, ns)
err := DeleteKubeNamespace(kubeClient, ns)
assert.Nil(ginkgo.GinkgoT(), err, "deleting namespace %v", f.Namespace)
}()
}(f.KubeClientSet, f.Namespace)
@ -588,6 +588,12 @@ func NewSingleIngress(name, path, host, ns, service string, port int, annotation
return newSingleIngressWithRules(name, path, host, ns, service, port, annotations, nil)
}
func NewSingleIngressWithIngressClass(name, path, host, ns, service, ingressClass string, port int, annotations map[string]string) *networking.Ingress {
ing := newSingleIngressWithRules(name, path, host, ns, service, port, annotations, nil)
ing.Spec.IngressClassName = &ingressClass
return ing
}
// NewSingleIngressWithMultiplePaths creates a simple ingress rule with multiple paths
func NewSingleIngressWithMultiplePaths(name string, paths []string, host, ns, service string, port int, annotations map[string]string) *networking.Ingress {
pathtype := networking.PathTypePrefix

View file

@ -38,7 +38,7 @@ import (
// EnsureSecret creates a Secret object or returns it if it already exists.
func (f *Framework) EnsureSecret(secret *api.Secret) *api.Secret {
err := createSecretWithRetries(f.KubeClientSet, f.Namespace, secret)
err := createSecretWithRetries(f.KubeClientSet, secret.Namespace, secret)
assert.Nil(ginkgo.GinkgoT(), err, "creating secret")
s, err := f.KubeClientSet.CoreV1().Secrets(secret.Namespace).Get(context.TODO(), secret.Name, metav1.GetOptions{})
@ -50,10 +50,10 @@ func (f *Framework) EnsureSecret(secret *api.Secret) *api.Secret {
// EnsureConfigMap creates a ConfigMap object or returns it if it already exists.
func (f *Framework) EnsureConfigMap(configMap *api.ConfigMap) (*api.ConfigMap, error) {
cm, err := f.KubeClientSet.CoreV1().ConfigMaps(f.Namespace).Create(context.TODO(), configMap, metav1.CreateOptions{})
cm, err := f.KubeClientSet.CoreV1().ConfigMaps(configMap.Namespace).Create(context.TODO(), configMap, metav1.CreateOptions{})
if err != nil {
if k8sErrors.IsAlreadyExists(err) {
return f.KubeClientSet.CoreV1().ConfigMaps(f.Namespace).Update(context.TODO(), configMap, metav1.UpdateOptions{})
return f.KubeClientSet.CoreV1().ConfigMaps(configMap.Namespace).Update(context.TODO(), configMap, metav1.UpdateOptions{})
}
return nil, err
}
@ -72,13 +72,13 @@ func (f *Framework) GetIngress(namespace string, name string) *networking.Ingres
// EnsureIngress creates an Ingress object and returns it, throws error if it already exists.
func (f *Framework) EnsureIngress(ingress *networking.Ingress) *networking.Ingress {
fn := func() {
err := createIngressWithRetries(f.KubeClientSet, f.Namespace, ingress)
err := createIngressWithRetries(f.KubeClientSet, ingress.Namespace, ingress)
assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
}
f.WaitForReload(fn)
ing := f.GetIngress(f.Namespace, ingress.Name)
ing := f.GetIngress(ingress.Namespace, ingress.Name)
if ing.Annotations == nil {
ing.Annotations = make(map[string]string)
}
@ -88,10 +88,10 @@ func (f *Framework) EnsureIngress(ingress *networking.Ingress) *networking.Ingre
// UpdateIngress updates an Ingress object and returns the updated object.
func (f *Framework) UpdateIngress(ingress *networking.Ingress) *networking.Ingress {
err := updateIngressWithRetries(f.KubeClientSet, f.Namespace, ingress)
err := updateIngressWithRetries(f.KubeClientSet, ingress.Namespace, ingress)
assert.Nil(ginkgo.GinkgoT(), err, "updating ingress")
ing := f.GetIngress(f.Namespace, ingress.Name)
ing := f.GetIngress(ingress.Namespace, ingress.Name)
if ing.Annotations == nil {
ing.Annotations = make(map[string]string)
}
@ -113,15 +113,15 @@ func (f *Framework) GetService(namespace string, name string) *core.Service {
// EnsureService creates a Service object and returns it, throws error if it already exists.
func (f *Framework) EnsureService(service *core.Service) *core.Service {
err := createServiceWithRetries(f.KubeClientSet, f.Namespace, service)
err := createServiceWithRetries(f.KubeClientSet, service.Namespace, service)
assert.Nil(ginkgo.GinkgoT(), err, "creating service")
return f.GetService(f.Namespace, service.Name)
return f.GetService(service.Namespace, service.Name)
}
// EnsureDeployment creates a Deployment object and returns it, throws error if it already exists.
func (f *Framework) EnsureDeployment(deployment *appsv1.Deployment) *appsv1.Deployment {
err := createDeploymentWithRetries(f.KubeClientSet, f.Namespace, deployment)
err := createDeploymentWithRetries(f.KubeClientSet, deployment.Namespace, deployment)
assert.Nil(ginkgo.GinkgoT(), err, "creating deployment")
d, err := f.KubeClientSet.AppsV1().Deployments(deployment.Namespace).Get(context.TODO(), deployment.Name, metav1.GetOptions{})

View file

@ -85,14 +85,15 @@ func RestclientConfig(config, context string) (*api.Config, error) {
// RunID unique identifier of the e2e run
var RunID = uuid.NewUUID()
// CreateKubeNamespace creates a new namespace in the cluster
func CreateKubeNamespace(baseName string, c kubernetes.Interface) (string, error) {
func createNamespace(baseName string, labels map[string]string, c kubernetes.Interface) (string, error) {
ts := time.Now().UnixNano()
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("e2e-tests-%v-%v-", baseName, ts),
Labels: labels,
},
}
// Be robust about making the namespace creation call.
var got *corev1.Namespace
var err error
@ -111,8 +112,20 @@ func CreateKubeNamespace(baseName string, c kubernetes.Interface) (string, error
return got.Name, nil
}
// deleteKubeNamespace deletes a namespace and all the objects inside
func deleteKubeNamespace(c kubernetes.Interface, namespace string) error {
// CreateKubeNamespace creates a new namespace in the cluster
func CreateKubeNamespace(baseName string, c kubernetes.Interface) (string, error) {
return createNamespace(baseName, nil, c)
}
// CreateKubeNamespaceWithLabel creates a new namespace with given labels in the cluster
func CreateKubeNamespaceWithLabel(baseName string, labels map[string]string, c kubernetes.Interface) (string, error) {
return createNamespace(baseName, labels, c)
}
// DeleteKubeNamespace deletes a namespace and all the objects inside
func DeleteKubeNamespace(c kubernetes.Interface, namespace string) error {
grace := int64(0)
pb := metav1.DeletePropagationBackground
return c.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{

View file

@ -0,0 +1,123 @@
/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package settings
import (
"context"
"net/http"
"strings"
"github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/test/e2e/framework"
)
var _ = framework.IngressNginxDescribe("[Flag] watch namespace selector", func() {
f := framework.NewDefaultFramework("namespace-selector")
notMatchedHost, matchedHost := "bar", "foo"
var notMatchedNs string
var matchedNs string
// create a test namespace, under which create an ingress and backend deployment
prepareTestIngress := func(baseName string, host string, labels map[string]string) string {
ns, err := framework.CreateKubeNamespaceWithLabel(f.BaseName, labels, f.KubeClientSet)
assert.Nil(ginkgo.GinkgoT(), err, "creating test namespace")
f.NewEchoDeploymentWithNamespaceAndReplicas(ns, 1)
ing := framework.NewSingleIngressWithIngressClass(host, "/", host, ns, framework.EchoService, f.IngressClass, 80, nil)
f.EnsureIngress(ing)
return ns
}
cleanupNamespace := func(ns string) {
err := framework.DeleteKubeNamespace(f.KubeClientSet, ns)
assert.Nil(ginkgo.GinkgoT(), err, "deleting temporarily crated namespace")
}
ginkgo.BeforeEach(func() {
notMatchedNs = prepareTestIngress(notMatchedHost, notMatchedHost, nil) // create namespace without label "foo=bar"
matchedNs = prepareTestIngress(matchedHost, matchedHost, map[string]string{"foo": "bar"})
})
ginkgo.AfterEach(func() {
cleanupNamespace(notMatchedNs)
cleanupNamespace(matchedNs)
// cleanup clusterrole/clusterrolebinding created by installing chart with controller.scope.enabled=false
err := f.KubeClientSet.RbacV1().ClusterRoles().Delete(context.TODO(), "nginx-ingress", metav1.DeleteOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "deleting clusterrole nginx-ingress")
err = f.KubeClientSet.RbacV1().ClusterRoleBindings().Delete(context.TODO(), "nginx-ingress", metav1.DeleteOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "deleting clusterrolebinging nginx-ingress")
})
ginkgo.Context("With specific watch-namespace-selector flags", func() {
ginkgo.It("should ingore Ingress of namespace without label foo=bar and accept those of namespace with label foo=bar", func() {
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name bar") &&
strings.Contains(cfg, "server_name foo")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", matchedHost).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", notMatchedHost).
Expect().
Status(http.StatusNotFound)
// should accept Ingress when namespace labeled with foo=bar
ns, err := f.KubeClientSet.CoreV1().Namespaces().Get(context.TODO(), notMatchedNs, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
if ns.Labels == nil {
ns.Labels = make(map[string]string)
}
ns.Labels["foo"] = "bar"
_, err = f.KubeClientSet.CoreV1().Namespaces().Update(context.TODO(), ns, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "labeling not matched namespace")
// update ingress to trigger reconcilation
ing, err := f.KubeClientSet.NetworkingV1().Ingresses(notMatchedNs).Get(context.TODO(), notMatchedHost, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "retrieve test ingress")
if ing.Labels == nil {
ing.Labels = make(map[string]string)
}
ing.Labels["foo"] = "bar"
_, err = f.KubeClientSet.NetworkingV1().Ingresses(notMatchedNs).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "updating ingress")
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name bar")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", notMatchedHost).
Expect().
Status(http.StatusOK)
})
})
})