Fix IngressClass logic for newer releases (#7341)

* Fix IngressClass logic for newer releases

Signed-off-by: Ricardo Pchevuzinske Katz <ricardo.katz@gmail.com>

* Change e2e tests for the new IngressClass presence

* Fix chart and admission tests

Signed-off-by: Ricardo Pchevuzinske Katz <ricardo.katz@gmail.com>

* Fix helm chart test

Signed-off-by: Ricardo Pchevuzinske Katz <ricardo.katz@gmail.com>

* Fix reviews

* Remove ingressclass code from admission
This commit is contained in:
Ricardo Katz 2021-07-28 18:58:46 -03:00 committed by GitHub
parent 0d57e87819
commit cef147a24d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 1450 additions and 637 deletions

View file

@ -57,8 +57,6 @@ jobs:
name: Build name: Build
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: changes needs: changes
if: |
(needs.changes.outputs.go == 'true')
steps: steps:
@ -116,6 +114,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- changes - changes
- build
if: | if: |
(needs.changes.outputs.charts == 'true') (needs.changes.outputs.charts == 'true')
@ -124,6 +123,11 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: cache
uses: actions/download-artifact@v2
with:
name: docker.tar.gz
- name: Lint - name: Lint
run: | run: |
./build/run-in-docker.sh ./hack/verify-chart-lint.sh ./build/run-in-docker.sh ./hack/verify-chart-lint.sh
@ -140,10 +144,21 @@ jobs:
version: v0.11.1 version: v0.11.1
image: kindest/node:v1.21.1 image: kindest/node:v1.21.1
- uses: geekyeggo/delete-artifact@v1
with:
name: docker.tar.gz
failOnError: false
- name: Load images from cache
run: |
echo "loading docker images..."
pigz -dc docker.tar.gz | docker load
- name: Test - name: Test
env: env:
KIND_CLUSTER_NAME: kind KIND_CLUSTER_NAME: kind
SKIP_CLUSTER_CREATION: true SKIP_CLUSTER_CREATION: true
SKIP_IMAGE_CREATION: true
run: | run: |
kind get kubeconfig > $HOME/.kube/kind-config-kind kind get kubeconfig > $HOME/.kube/kind-config-kind
make kind-e2e-chart-tests make kind-e2e-chart-tests

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
kind: DaemonSet kind: DaemonSet
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
addHeaders: addHeaders:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
metrics: metrics:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
metrics: metrics:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: true enabled: true
service: service:

View file

@ -1,5 +1,9 @@
controller: controller:
kind: DaemonSet kind: DaemonSet
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: true enabled: true
service: service:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
autoscaling: autoscaling:
enabled: true enabled: true
admissionWebhooks: admissionWebhooks:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
config: config:
use-proxy-protocol: "true" use-proxy-protocol: "true"
admissionWebhooks: admissionWebhooks:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,4 +1,8 @@
# Left blank to test default values # Left blank to test default values
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
service: service:
type: ClusterIP type: ClusterIP

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
addHeaders: addHeaders:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
metrics: metrics:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
metrics: metrics:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
service: service:
type: ClusterIP type: ClusterIP

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: false enabled: false
service: service:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
service: service:
type: ClusterIP type: ClusterIP

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: true enabled: true
service: service:

View file

@ -1,4 +1,8 @@
controller: controller:
image:
repository: ingress-controller/controller
tag: 1.0.0-dev
digest: null
admissionWebhooks: admissionWebhooks:
enabled: true enabled: true
service: service:

View file

@ -42,8 +42,7 @@ rules:
- list - list
- watch - watch
- apiGroups: - apiGroups:
- extensions - networking.k8s.io
- "networking.k8s.io" # k8s 1.14+
resources: resources:
- ingresses - ingresses
verbs: verbs:
@ -58,14 +57,13 @@ rules:
- create - create
- patch - patch
- apiGroups: - apiGroups:
- extensions - networking.k8s.io
- "networking.k8s.io" # k8s 1.14+
resources: resources:
- ingresses/status - ingresses/status
verbs: verbs:
- update - update
- apiGroups: - apiGroups:
- "networking.k8s.io" # k8s 1.14+ - networking.k8s.io
resources: resources:
- ingressclasses - ingressclasses
verbs: verbs:

View file

@ -80,7 +80,7 @@ spec:
- --publish-service={{ template "ingress-nginx.controller.publishServicePath" . }} - --publish-service={{ template "ingress-nginx.controller.publishServicePath" . }}
{{- end }} {{- end }}
- --election-id={{ .Values.controller.electionID }} - --election-id={{ .Values.controller.electionID }}
- --ingress-class={{ .Values.controller.ingressClass }} - --controller-class={{ .Values.controller.ingressClassResource.controllerValue }}
- --configmap={{ default "$(POD_NAMESPACE)" .Values.controller.configMapNamespace }}/{{ include "ingress-nginx.controller.fullname" . }} - --configmap={{ default "$(POD_NAMESPACE)" .Values.controller.configMapNamespace }}/{{ include "ingress-nginx.controller.fullname" . }}
{{- if .Values.tcp }} {{- if .Values.tcp }}
- --tcp-services-configmap={{ default "$(POD_NAMESPACE)" .Values.controller.tcp.configMapNamespace }}/{{ include "ingress-nginx.fullname" . }}-tcp - --tcp-services-configmap={{ default "$(POD_NAMESPACE)" .Values.controller.tcp.configMapNamespace }}/{{ include "ingress-nginx.fullname" . }}-tcp

View file

@ -84,7 +84,7 @@ spec:
- --publish-service={{ template "ingress-nginx.controller.publishServicePath" . }} - --publish-service={{ template "ingress-nginx.controller.publishServicePath" . }}
{{- end }} {{- end }}
- --election-id={{ .Values.controller.electionID }} - --election-id={{ .Values.controller.electionID }}
- --ingress-class={{ .Values.controller.ingressClass }} - --controller-class={{ .Values.controller.ingressClassResource.controllerValue }}
- --configmap={{ default "$(POD_NAMESPACE)" .Values.controller.configMapNamespace }}/{{ include "ingress-nginx.controller.fullname" . }} - --configmap={{ default "$(POD_NAMESPACE)" .Values.controller.configMapNamespace }}/{{ include "ingress-nginx.controller.fullname" . }}
{{- if .Values.tcp }} {{- if .Values.tcp }}
- --tcp-services-configmap={{ default "$(POD_NAMESPACE)" .Values.controller.tcp.configMapNamespace }}/{{ include "ingress-nginx.fullname" . }}-tcp - --tcp-services-configmap={{ default "$(POD_NAMESPACE)" .Values.controller.tcp.configMapNamespace }}/{{ include "ingress-nginx.fullname" . }}-tcp

View file

@ -1,9 +1,7 @@
{{- if and (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) (.Values.controller.ingressClassResource.enabled) -}} {{- if .Values.controller.ingressClassResource.enabled -}}
{{- if and (semverCompare "=1.18-0" .Capabilities.KubeVersion.GitVersion) }} # We don't support namespaced ingressClass yet
apiVersion: networking.k8s.io/v1beta1 # So a ClusterRole and a ClusterRoleBinding is required
{{- else }}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
{{- end }}
kind: IngressClass kind: IngressClass
metadata: metadata:
labels: labels:
@ -12,12 +10,12 @@ metadata:
{{- with .Values.controller.labels }} {{- with .Values.controller.labels }}
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
name: {{ .Values.controller.ingressClass }} name: {{ .Values.controller.ingressClassResource.name }}
{{- if .Values.controller.ingressClassResource.default }} {{- if .Values.controller.ingressClassResource.default }}
annotations: annotations:
ingressclass.kubernetes.io/is-default-class: "true" ingressclass.kubernetes.io/is-default-class: "true"
{{- end }} {{- end }}
spec: spec:
controller: k8s.io/ingress-nginx controller: {{ .Values.controller.ingressClassResource.controllerValue }}
{{ template "ingressClass.parameters" . }} {{ template "ingressClass.parameters" . }}
{{- end }} {{- end }}

View file

@ -34,8 +34,7 @@ rules:
- list - list
- watch - watch
- apiGroups: - apiGroups:
- extensions - networking.k8s.io
- "networking.k8s.io" # k8s 1.14+
resources: resources:
- ingresses - ingresses
verbs: verbs:
@ -43,14 +42,13 @@ rules:
- list - list
- watch - watch
- apiGroups: - apiGroups:
- extensions - networking.k8s.io
- "networking.k8s.io" # k8s 1.14+
resources: resources:
- ingresses/status - ingresses/status
verbs: verbs:
- update - update
- apiGroups: - apiGroups:
- "networking.k8s.io" # k8s 1.14+ - networking.k8s.io
resources: resources:
- ingressclasses - ingressclasses
verbs: verbs:
@ -62,7 +60,7 @@ rules:
resources: resources:
- configmaps - configmaps
resourceNames: resourceNames:
- {{ .Values.controller.electionID }}-{{ .Values.controller.ingressClass }} - {{ .Values.controller.electionID }}
verbs: verbs:
- get - get
- update - update

View file

@ -76,15 +76,13 @@ controller:
## ##
electionID: ingress-controller-leader electionID: ingress-controller-leader
## Name of the ingress class to route through this controller
##
ingressClass: nginx
# This section refers to the creation of the IngressClass resource # This section refers to the creation of the IngressClass resource
# IngressClass resources are supported since k8s >= 1.18 # IngressClass resources are supported since k8s >= 1.18 and required since k8s >= 1.19
ingressClassResource: ingressClassResource:
enabled: false name: nginx
enabled: true
default: false default: false
controllerValue: "k8s.io/ingress-nginx"
# Parameters is a link to a custom resource containing additional # Parameters is a link to a custom resource containing additional
# configuration for the controller. This is optional if the controller # configuration for the controller. This is optional if the controller

View file

@ -24,10 +24,10 @@ import (
"github.com/spf13/pflag" "github.com/spf13/pflag"
apiv1 "k8s.io/api/core/v1" apiv1 "k8s.io/api/core/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/controller" "k8s.io/ingress-nginx/internal/ingress/controller"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
"k8s.io/ingress-nginx/internal/ingress/status" "k8s.io/ingress-nginx/internal/ingress/status"
ing_net "k8s.io/ingress-nginx/internal/net" ing_net "k8s.io/ingress-nginx/internal/net"
"k8s.io/ingress-nginx/internal/nginx" "k8s.io/ingress-nginx/internal/nginx"
@ -55,10 +55,18 @@ only when the flag --apiserver-host is specified.`)
Takes the form "namespace/name". The controller configures NGINX to forward Takes the form "namespace/name". The controller configures NGINX to forward
requests to the first port of this Service.`) requests to the first port of this Service.`)
ingressClass = flags.String("ingress-class", "", ingressClassAnnotation = flags.String("ingress-class", ingressclass.DefaultAnnotationValue,
`Name of the ingress class this controller satisfies. `[IN DEPRECATION] Name of the ingress class this controller satisfies.
The class of an Ingress object is set using the field IngressClassName in Kubernetes clusters version v1.18.0 or higher or the annotation "kubernetes.io/ingress.class" (deprecated). The class of an Ingress object is set using the annotation "kubernetes.io/ingress.class" (deprecated).
If this parameter is not set, or set to the default value of "nginx", it will handle ingresses with either an empty or "nginx" class name.`) The parameter --controller-class has precedence over this.`)
ingressClassController = flags.String("controller-class", ingressclass.DefaultControllerName,
`Ingress Class Controller value this Ingress satisfies.
The class of an Ingress object is set using the field IngressClassName in Kubernetes clusters version v1.19.0 or higher. The .spec.controller value of the IngressClass
referenced in an Ingress Object should be the same value specified here to make this object be watched.`)
watchWithoutClass = flags.Bool("watch-ingress-without-class", false,
`Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified`)
configMap = flags.String("configmap", "", configMap = flags.String("configmap", "",
`Name of the ConfigMap containing custom global configurations for the controller.`) `Name of the ConfigMap containing custom global configurations for the controller.`)
@ -207,18 +215,6 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g
status.UpdateInterval = *statusUpdateInterval status.UpdateInterval = *statusUpdateInterval
} }
if *ingressClass != "" {
klog.InfoS("Watching for Ingress", "class", *ingressClass)
if *ingressClass != class.DefaultClass {
klog.Warningf("Only Ingresses with class %q will be processed by this Ingress controller", *ingressClass)
} else {
klog.Warning("Ingresses with an empty class will also be processed by this Ingress controller")
}
class.IngressClass = *ingressClass
}
parser.AnnotationsPrefix = *annotationsPrefix parser.AnnotationsPrefix = *annotationsPrefix
// check port collisions // check port collisions
@ -297,6 +293,11 @@ https://blog.maxmind.com/2019/12/18/significant-changes-to-accessing-and-using-g
HTTPS: *httpsPort, HTTPS: *httpsPort,
SSLProxy: *sslProxyPort, SSLProxy: *sslProxyPort,
}, },
IngressClassConfiguration: &ingressclass.IngressClassConfiguration{
Controller: *ingressClassController,
AnnotationValue: *ingressClassAnnotation,
WatchWithoutClass: *watchWithoutClass,
},
DisableCatchAll: *disableCatchAll, DisableCatchAll: *disableCatchAll,
ValidationWebhook: *validationWebhook, ValidationWebhook: *validationWebhook,
ValidationWebhookCertPath: *validationWebhookCert, ValidationWebhookCertPath: *validationWebhookCert,

View file

@ -43,7 +43,6 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
"k8s.io/ingress-nginx/internal/ingress/controller" "k8s.io/ingress-nginx/internal/ingress/controller"
"k8s.io/ingress-nginx/internal/ingress/metric" "k8s.io/ingress-nginx/internal/ingress/metric"
"k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/k8s"
@ -108,25 +107,13 @@ func main() {
klog.Fatalf("ingress-nginx requires Kubernetes v1.19.0 or higher") klog.Fatalf("ingress-nginx requires Kubernetes v1.19.0 or higher")
} }
k8s.IngressClass, err = kubeClient.NetworkingV1().IngressClasses(). _, err = kubeClient.NetworkingV1().IngressClasses().List(context.TODO(), metav1.ListOptions{})
Get(context.TODO(), class.IngressClass, metav1.GetOptions{})
if err != nil { if err != nil {
if !errors.IsNotFound(err) { if !errors.IsNotFound(err) {
if !errors.IsUnauthorized(err) && !errors.IsForbidden(err) { if errors.IsUnauthorized(err) || !errors.IsForbidden(err) {
klog.Fatalf("Error searching IngressClass: %v", err) klog.Fatalf("Error searching IngressClass: Please verify your RBAC and allow Ingress Controller to list and get Ingress Classes: %v", err)
} }
klog.ErrorS(err, "Searching IngressClass", "class", class.IngressClass)
} }
klog.Warningf("No IngressClass resource with name %v found. Only annotation will be used.", class.IngressClass)
// TODO: remove once this is fixed in client-go
k8s.IngressClass = nil
}
if k8s.IngressClass != nil && k8s.IngressClass.Spec.Controller != k8s.IngressNGINXController {
klog.Errorf(`Invalid IngressClass (Spec.Controller) value "%v". Should be "%v"`, k8s.IngressClass.Spec.Controller, k8s.IngressNGINXController)
klog.Fatalf("IngressClass with name %v is not valid for ingress-nginx (invalid Spec.Controller)", class.IngressClass)
} }
conf.Client = kubeClient conf.Client = kubeClient
@ -146,7 +133,7 @@ func main() {
mc := metric.NewDummyCollector() mc := metric.NewDummyCollector()
if conf.EnableMetrics { if conf.EnableMetrics {
mc, err = metric.NewCollector(conf.MetricsPerHost, reg) mc, err = metric.NewCollector(conf.MetricsPerHost, reg, conf.IngressClassConfiguration.Controller)
if err != nil { if err != nil {
klog.Fatalf("Error creating prometheus collector: %v", err) klog.Fatalf("Error creating prometheus collector: %v", err)
} }

View file

@ -1,64 +0,0 @@
/*
Copyright 2015 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 class
import (
networking "k8s.io/api/networking/v1"
"k8s.io/ingress-nginx/internal/k8s"
)
const (
// IngressKey picks a specific "class" for the Ingress.
// The controller only processes Ingresses with this annotation either
// unset, or set to either the configured value or the empty string.
IngressKey = "kubernetes.io/ingress.class"
)
var (
// DefaultClass defines the default class used in the nginx ingress controller
DefaultClass = "nginx"
// IngressClass sets the runtime ingress class to use
// An empty string means accept all ingresses without
// annotation and the ones configured with class nginx
IngressClass = "nginx"
)
// IsValid returns true if the given Ingress specify the ingress.class
// annotation or IngressClassName resource for Kubernetes >= v1.18
func IsValid(ing *networking.Ingress) bool {
// 1. with annotation or IngressClass
ingress, ok := ing.GetAnnotations()[IngressKey]
if !ok && ing.Spec.IngressClassName != nil {
ingress = *ing.Spec.IngressClassName
}
// empty ingress and IngressClass equal default
if len(ingress) == 0 && IngressClass == DefaultClass {
return true
}
// k8s > v1.18.
// Processing may be redundant because k8s.IngressClass is obtained by IngressClass
// 3. without annotation and IngressClass. Check IngressClass
if k8s.IngressClass != nil {
return ingress == k8s.IngressClass.Name
}
// 4. with IngressClass
return ingress == IngressClass
}

View file

@ -1,103 +0,0 @@
/*
Copyright 2017 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 class
import (
"testing"
api "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/k8s"
)
func TestIsValidClass(t *testing.T) {
dc := DefaultClass
ic := IngressClass
k8sic := k8s.IngressClass
v1Ready := k8s.IsIngressV1Ready
// restore original values after the tests
defer func() {
DefaultClass = dc
IngressClass = ic
k8s.IngressClass = k8sic
k8s.IsIngressV1Ready = v1Ready
}()
tests := []struct {
ingress string
controller string
defClass string
annotation bool
ingressClassName bool
k8sClass *networking.IngressClass
v1Ready bool
isValid bool
}{
{"", "", "nginx", true, false, nil, false, true},
{"", "nginx", "nginx", true, false, nil, false, true},
{"nginx", "nginx", "nginx", true, false, nil, false, true},
{"custom", "custom", "nginx", true, false, nil, false, true},
{"", "killer", "nginx", true, false, nil, false, false},
{"custom", "nginx", "nginx", true, false, nil, false, false},
{"nginx", "nginx", "nginx", false, true, nil, false, true},
{"custom", "nginx", "nginx", false, true, nil, true, false},
{"nginx", "nginx", "nginx", false, true, nil, true, true},
{"", "custom", "nginx", false, false,
&networking.IngressClass{
ObjectMeta: meta_v1.ObjectMeta{
Name: "custom",
},
},
false, false},
{"", "custom", "nginx", false, false,
&networking.IngressClass{
ObjectMeta: meta_v1.ObjectMeta{
Name: "custom",
},
},
true, false},
}
for _, test := range tests {
ing := &networking.Ingress{
ObjectMeta: meta_v1.ObjectMeta{
Name: "foo",
Namespace: api.NamespaceDefault,
},
}
data := map[string]string{}
ing.SetAnnotations(data)
if test.annotation {
ing.Annotations[IngressKey] = test.ingress
}
if test.ingressClassName {
ing.Spec.IngressClassName = &[]string{test.ingress}[0]
}
IngressClass = test.controller
DefaultClass = test.defClass
k8s.IngressClass = test.k8sClass
k8s.IsIngressV1Ready = test.v1Ready
b := IsValid(ing)
if b != test.isValid {
t.Errorf("test %v - expected %v but %v was returned", test, test.isValid, b)
}
}
}

View file

@ -33,11 +33,11 @@ import (
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations" "k8s.io/ingress-nginx/internal/ingress/annotations"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
"k8s.io/ingress-nginx/internal/ingress/annotations/log" "k8s.io/ingress-nginx/internal/ingress/annotations/log"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/annotations/proxy" "k8s.io/ingress-nginx/internal/ingress/annotations/proxy"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
"k8s.io/ingress-nginx/internal/ingress/controller/store" "k8s.io/ingress-nginx/internal/ingress/controller/store"
"k8s.io/ingress-nginx/internal/ingress/errors" "k8s.io/ingress-nginx/internal/ingress/errors"
"k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/k8s"
@ -100,6 +100,8 @@ type Configuration struct {
DisableCatchAll bool DisableCatchAll bool
IngressClassConfiguration *ingressclass.IngressClassConfiguration
ValidationWebhook string ValidationWebhook string
ValidationWebhookCertPath string ValidationWebhookCertPath string
ValidationWebhookKeyPath string ValidationWebhookKeyPath string
@ -221,11 +223,6 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
return nil return nil
} }
if !class.IsValid(ing) {
klog.Warningf("ignoring ingress %v in %v based on annotation %v", ing.Name, ing.ObjectMeta.Namespace, class.IngressKey)
return nil
}
if n.cfg.Namespace != "" && ing.ObjectMeta.Namespace != n.cfg.Namespace { if n.cfg.Namespace != "" && ing.ObjectMeta.Namespace != n.cfg.Namespace {
klog.Warningf("ignoring ingress %v in namespace %v different from the namespace watched %s", ing.Name, ing.ObjectMeta.Namespace, n.cfg.Namespace) klog.Warningf("ignoring ingress %v in namespace %v different from the namespace watched %s", ing.Name, ing.ObjectMeta.Namespace, n.cfg.Namespace)
return nil return nil

View file

@ -45,6 +45,7 @@ import (
"k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl" "k8s.io/ingress-nginx/internal/ingress/annotations/proxyssl"
"k8s.io/ingress-nginx/internal/ingress/controller/config" "k8s.io/ingress-nginx/internal/ingress/controller/config"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
"k8s.io/ingress-nginx/internal/ingress/controller/store" "k8s.io/ingress-nginx/internal/ingress/controller/store"
"k8s.io/ingress-nginx/internal/ingress/defaults" "k8s.io/ingress-nginx/internal/ingress/defaults"
"k8s.io/ingress-nginx/internal/ingress/metric" "k8s.io/ingress-nginx/internal/ingress/metric"
@ -188,18 +189,6 @@ func TestCheckIngress(t *testing.T) {
}, },
}, },
} }
t.Run("When the ingress class differs from nginx", func(t *testing.T) {
ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "different"
nginx.command = testNginxTestCommand{
t: t,
err: fmt.Errorf("test error"),
}
if nginx.CheckIngress(ing) != nil {
t.Errorf("with a different ingress class, no error should be returned")
}
})
t.Run("when the class is the nginx one", func(t *testing.T) { t.Run("when the class is the nginx one", func(t *testing.T) {
ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "nginx" ing.ObjectMeta.Annotations["kubernetes.io/ingress.class"] = "nginx"
nginx.command = testNginxTestCommand{ nginx.command = testNginxTestCommand{
@ -1899,7 +1888,12 @@ func newNGINXController(t *testing.T) *NGINXController {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
channels.NewRingChannel(10), channels.NewRingChannel(10),
false) false,
&ingressclass.IngressClassConfiguration{
Controller: "k8s.io/ingress-nginx",
AnnotationValue: "nginx",
},
)
sslCert := ssl.GetFakeSSLCert() sslCert := ssl.GetFakeSSLCert()
config := &Configuration{ config := &Configuration{
@ -1957,7 +1951,11 @@ func newDynamicNginxController(t *testing.T, setConfigMap func(string) *v1.Confi
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
channels.NewRingChannel(10), channels.NewRingChannel(10),
false) false,
&ingressclass.IngressClassConfiguration{
Controller: "k8s.io/ingress-nginx",
AnnotationValue: "nginx",
})
sslCert := ssl.GetFakeSSLCert() sslCert := ssl.GetFakeSSLCert()
config := &Configuration{ config := &Configuration{

View file

@ -0,0 +1,45 @@
/*
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 ingressclass
const (
// IngressKey picks a specific "class" for the Ingress.
// The controller only processes Ingresses with this annotation either
// unset, or set to either the configured value or the empty string.
IngressKey = "kubernetes.io/ingress.class"
// DefaultControllerName defines the default controller name for Ingress NGINX
DefaultControllerName = "k8s.io/ingress-nginx"
// DefaultAnnotationValue defines the default annotation value for the ingress-nginx controller
DefaultAnnotationValue = "nginx"
)
// IngressClassConfiguration defines the various aspects of IngressClass parsing
// and how the controller should behave in each case
type IngressClassConfiguration struct {
// Controller defines the controller value this daemon watch to.
// Defaults to "k8s.io/ingress-nginx" defined in flags
Controller string
// AnnotationValue defines the annotation value this Controller watch to, in case of the
// ingressSpecName is not found but the annotation is.
// The Annotation is deprecated and should not be used in future releases
AnnotationValue string
// WatchWithoutClass defines if Controller should watch to Ingress Objects that does
// not contain an IngressClass configuration
WatchWithoutClass bool
}

View file

@ -50,7 +50,6 @@ import (
adm_controller "k8s.io/ingress-nginx/internal/admission/controller" adm_controller "k8s.io/ingress-nginx/internal/admission/controller"
"k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/process" "k8s.io/ingress-nginx/internal/ingress/controller/process"
"k8s.io/ingress-nginx/internal/ingress/controller/store" "k8s.io/ingress-nginx/internal/ingress/controller/store"
@ -131,7 +130,8 @@ func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXContro
config.ResyncPeriod, config.ResyncPeriod,
config.Client, config.Client,
n.updateCh, n.updateCh,
config.DisableCatchAll) config.DisableCatchAll,
config.IngressClassConfiguration)
n.syncQueue = task.NewTaskQueue(n.syncIngress) n.syncQueue = task.NewTaskQueue(n.syncIngress)
@ -257,10 +257,10 @@ func (n *NGINXController) Start() {
// we need to use the defined ingress class to allow multiple leaders // we need to use the defined ingress class to allow multiple leaders
// in order to update information about ingress status // in order to update information about ingress status
electionID := fmt.Sprintf("%v-%v", n.cfg.ElectionID, class.DefaultClass) // TODO: For now, as the the IngressClass logics has changed, is up to the
if class.IngressClass != "" { // cluster admin to create different Leader Election IDs.
electionID = fmt.Sprintf("%v-%v", n.cfg.ElectionID, class.IngressClass) // Should revisit this in a future
} electionID := n.cfg.ElectionID
setupLeaderElection(&leaderElectionConfig{ setupLeaderElection(&leaderElectionConfig{
Client: n.cfg.Client, Client: n.cfg.Client,

View file

@ -0,0 +1,39 @@
/*
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 store
import (
networking "k8s.io/api/networking/v1"
"k8s.io/client-go/tools/cache"
)
// IngressClassLister makes a Store that lists IngressClass.
type IngressClassLister struct {
cache.Store
}
// ByKey returns the Ingress matching key in the local Ingress Store.
func (il IngressClassLister) ByKey(key string) (*networking.IngressClass, error) {
i, exists, err := il.GetByKey(key)
if err != nil {
return nil, err
}
if !exists {
return nil, NotExistsError(key)
}
return i.(*networking.IngressClass), nil
}

View file

@ -41,14 +41,13 @@ import (
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/utils/pointer"
"k8s.io/ingress-nginx/internal/file" "k8s.io/ingress-nginx/internal/file"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations" "k8s.io/ingress-nginx/internal/ingress/annotations"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config" ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
ngx_template "k8s.io/ingress-nginx/internal/ingress/controller/template" ngx_template "k8s.io/ingress-nginx/internal/ingress/controller/template"
"k8s.io/ingress-nginx/internal/ingress/defaults" "k8s.io/ingress-nginx/internal/ingress/defaults"
"k8s.io/ingress-nginx/internal/ingress/errors" "k8s.io/ingress-nginx/internal/ingress/errors"
@ -122,6 +121,7 @@ type Event struct {
// Informer defines the required SharedIndexInformers that interact with the API server. // Informer defines the required SharedIndexInformers that interact with the API server.
type Informer struct { type Informer struct {
Ingress cache.SharedIndexInformer Ingress cache.SharedIndexInformer
IngressClass cache.SharedIndexInformer
Endpoint cache.SharedIndexInformer Endpoint cache.SharedIndexInformer
Service cache.SharedIndexInformer Service cache.SharedIndexInformer
Secret cache.SharedIndexInformer Secret cache.SharedIndexInformer
@ -131,6 +131,7 @@ type Informer struct {
// Lister contains object listers (stores). // Lister contains object listers (stores).
type Lister struct { type Lister struct {
Ingress IngressLister Ingress IngressLister
IngressClass IngressClassLister
Service ServiceLister Service ServiceLister
Endpoint EndpointLister Endpoint EndpointLister
Secret SecretLister Secret SecretLister
@ -150,6 +151,7 @@ func (e NotExistsError) Error() string {
func (i *Informer) Run(stopCh chan struct{}) { func (i *Informer) Run(stopCh chan struct{}) {
go i.Secret.Run(stopCh) go i.Secret.Run(stopCh)
go i.Endpoint.Run(stopCh) go i.Endpoint.Run(stopCh)
go i.IngressClass.Run(stopCh)
go i.Service.Run(stopCh) go i.Service.Run(stopCh)
go i.ConfigMap.Run(stopCh) go i.ConfigMap.Run(stopCh)
@ -157,6 +159,7 @@ func (i *Informer) Run(stopCh chan struct{}) {
// from the queue // from the queue
if !cache.WaitForCacheSync(stopCh, if !cache.WaitForCacheSync(stopCh,
i.Endpoint.HasSynced, i.Endpoint.HasSynced,
i.IngressClass.HasSynced,
i.Service.HasSynced, i.Service.HasSynced,
i.Secret.HasSynced, i.Secret.HasSynced,
i.ConfigMap.HasSynced, i.ConfigMap.HasSynced,
@ -221,7 +224,8 @@ func New(
resyncPeriod time.Duration, resyncPeriod time.Duration,
client clientset.Interface, client clientset.Interface,
updateCh *channels.RingChannel, updateCh *channels.RingChannel,
disableCatchAll bool) Storer { disableCatchAll bool,
icConfig *ingressclass.IngressClassConfiguration) Storer {
store := &k8sStore{ store := &k8sStore{
informers: &Informer{}, informers: &Informer{},
@ -296,6 +300,9 @@ func New(
store.informers.Ingress = infFactory.Networking().V1().Ingresses().Informer() store.informers.Ingress = infFactory.Networking().V1().Ingresses().Informer()
store.listers.Ingress.Store = store.informers.Ingress.GetStore() store.listers.Ingress.Store = store.informers.Ingress.GetStore()
store.informers.IngressClass = infFactory.Networking().V1().IngressClasses().Informer()
store.listers.IngressClass.Store = cache.NewStore(cache.MetaNamespaceKeyFunc)
store.informers.Endpoint = infFactory.Core().V1().Endpoints().Informer() store.informers.Endpoint = infFactory.Core().V1().Endpoints().Informer()
store.listers.Endpoint.Store = store.informers.Endpoint.GetStore() store.listers.Endpoint.Store = store.informers.Endpoint.GetStore()
@ -324,7 +331,9 @@ func New(
} }
} }
if !class.IsValid(ing) { _, err := store.GetIngressClass(ing, icConfig)
if err != nil {
klog.InfoS("Ignoring ingress because of error while validating ingress class", "ingress", klog.KObj(ing), "error", err)
return return
} }
@ -347,12 +356,14 @@ func New(
ingEventHandler := cache.ResourceEventHandlerFuncs{ ingEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { AddFunc: func(obj interface{}) {
ing, _ := toIngress(obj) ing, _ := toIngress(obj)
if !class.IsValid(ing) { ic, err := store.GetIngressClass(ing, icConfig)
ingressClass, _ := parser.GetStringAnnotation(class.IngressKey, ing) if err != nil {
klog.InfoS("Ignoring ingress", "ingress", klog.KObj(ing), "kubernetes.io/ingress.class", ingressClass, "ingressClassName", pointer.StringPtrDerefOr(ing.Spec.IngressClassName, "")) klog.InfoS("Ignoring ingress because of error while validating ingress class", "ingress", klog.KObj(ing), "error", err)
return return
} }
klog.InfoS("Found valid IngressClass", "ingress", klog.KObj(ing), "ingressclass", ic)
if hasCatchAllIngressRule(ing.Spec) && disableCatchAll { if hasCatchAllIngressRule(ing.Spec) && disableCatchAll {
klog.InfoS("Ignoring add for catch-all ingress because of --disable-catch-all", "ingress", klog.KObj(ing)) klog.InfoS("Ignoring add for catch-all ingress because of --disable-catch-all", "ingress", klog.KObj(ing))
return return
@ -374,21 +385,21 @@ func New(
oldIng, _ := toIngress(old) oldIng, _ := toIngress(old)
curIng, _ := toIngress(cur) curIng, _ := toIngress(cur)
validOld := class.IsValid(oldIng) _, errOld := store.GetIngressClass(oldIng, icConfig)
validCur := class.IsValid(curIng) classCur, errCur := store.GetIngressClass(curIng, icConfig)
if !validOld && validCur { if errOld != nil && errCur == nil {
if hasCatchAllIngressRule(curIng.Spec) && disableCatchAll { if hasCatchAllIngressRule(curIng.Spec) && disableCatchAll {
klog.InfoS("ignoring update for catch-all ingress because of --disable-catch-all", "ingress", klog.KObj(curIng)) klog.InfoS("ignoring update for catch-all ingress because of --disable-catch-all", "ingress", klog.KObj(curIng))
return return
} }
klog.InfoS("creating ingress", "ingress", klog.KObj(curIng), "class", class.IngressKey) klog.InfoS("creating ingress", "ingress", klog.KObj(curIng), "ingressclass", classCur)
recorder.Eventf(curIng, corev1.EventTypeNormal, "Sync", "Scheduled for sync") recorder.Eventf(curIng, corev1.EventTypeNormal, "Sync", "Scheduled for sync")
} else if validOld && !validCur { } else if errOld == nil && errCur != nil {
klog.InfoS("removing ingress", "ingress", klog.KObj(curIng), "class", class.IngressKey) klog.InfoS("removing ingress because of unknown ingressclass", "ingress", klog.KObj(curIng))
ingDeleteHandler(old) ingDeleteHandler(old)
return return
} else if validCur && !reflect.DeepEqual(old, cur) { } else if errCur == nil && !reflect.DeepEqual(old, cur) {
if hasCatchAllIngressRule(curIng.Spec) && disableCatchAll { if hasCatchAllIngressRule(curIng.Spec) && disableCatchAll {
klog.InfoS("ignoring update for catch-all ingress and delete old one because of --disable-catch-all", "ingress", klog.KObj(curIng)) klog.InfoS("ignoring update for catch-all ingress and delete old one because of --disable-catch-all", "ingress", klog.KObj(curIng))
ingDeleteHandler(old) ingDeleteHandler(old)
@ -412,6 +423,63 @@ func New(
}, },
} }
ingressClassEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
ingressclass := obj.(*networkingv1.IngressClass)
if ingressclass.Spec.Controller != icConfig.Controller {
klog.InfoS("ignoring ingressclass as the spec.controller is not the same of this ingress", "ingressclass", klog.KObj(ingressclass))
return
}
err := store.listers.IngressClass.Add(ingressclass)
if err != nil {
klog.InfoS("error adding ingressclass to store", "ingressclass", klog.KObj(ingressclass), "error", err)
return
}
updateCh.In() <- Event{
Type: CreateEvent,
Obj: obj,
}
},
DeleteFunc: func(obj interface{}) {
ingressclass := obj.(*networkingv1.IngressClass)
if ingressclass.Spec.Controller != icConfig.Controller {
klog.InfoS("ignoring ingressclass as the spec.controller is not the same of this ingress", "ingressclass", klog.KObj(ingressclass))
return
}
err := store.listers.IngressClass.Delete(ingressclass)
if err != nil {
klog.InfoS("error removing ingressclass from store", "ingressclass", klog.KObj(ingressclass), "error", err)
return
}
updateCh.In() <- Event{
Type: DeleteEvent,
Obj: obj,
}
},
UpdateFunc: func(old, cur interface{}) {
oic := old.(*networkingv1.IngressClass)
cic := cur.(*networkingv1.IngressClass)
if cic.Spec.Controller != icConfig.Controller {
klog.InfoS("ignoring ingressclass as the spec.controller is not the same of this ingress", "ingressclass", klog.KObj(cic))
return
}
// TODO: In a future we might be interested in parse parameters and use as
// current IngressClass for this case, crossing with configmap
if !reflect.DeepEqual(cic.Spec.Parameters, oic.Spec.Parameters) {
err := store.listers.IngressClass.Update(cic)
if err != nil {
klog.InfoS("error updating ingressclass in store", "ingressclass", klog.KObj(cic), "error", err)
return
}
updateCh.In() <- Event{
Type: UpdateEvent,
Obj: cur,
}
}
},
}
secrEventHandler := cache.ResourceEventHandlerFuncs{ secrEventHandler := cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { AddFunc: func(obj interface{}) {
sec := obj.(*corev1.Secret) sec := obj.(*corev1.Secret)
@ -608,6 +676,7 @@ func New(
} }
store.informers.Ingress.AddEventHandler(ingEventHandler) store.informers.Ingress.AddEventHandler(ingEventHandler)
store.informers.IngressClass.AddEventHandler(ingressClassEventHandler)
store.informers.Endpoint.AddEventHandler(epEventHandler) store.informers.Endpoint.AddEventHandler(epEventHandler)
store.informers.Secret.AddEventHandler(secrEventHandler) store.informers.Secret.AddEventHandler(secrEventHandler)
store.informers.ConfigMap.AddEventHandler(cmEventHandler) store.informers.ConfigMap.AddEventHandler(cmEventHandler)
@ -758,6 +827,32 @@ func (s *k8sStore) GetService(key string) (*corev1.Service, error) {
return s.listers.Service.ByKey(key) return s.listers.Service.ByKey(key)
} }
func (s *k8sStore) GetIngressClass(ing *networkingv1.Ingress, icConfig *ingressclass.IngressClassConfiguration) (string, error) {
// First we try ingressClassName
if ing.Spec.IngressClassName != nil {
iclass, err := s.listers.IngressClass.ByKey(*ing.Spec.IngressClassName)
if err != nil {
return "", err
}
return iclass.Name, nil
}
// Then we try annotation
if ingressclass, ok := ing.GetAnnotations()[ingressclass.IngressKey]; ok {
if ingressclass != icConfig.AnnotationValue {
return "", fmt.Errorf("ingress class annotation is not equal to the expected by Ingress Controller")
}
return ingressclass, nil
}
// Then we accept if the WithoutClass is enabled
if icConfig.WatchWithoutClass {
// Reserving "_" as a "wildcard" name
return "_", nil
}
return "", fmt.Errorf("ingress does not contain a valid IngressClass")
}
// getIngress returns the Ingress matching key. // getIngress returns the Ingress matching key.
func (s *k8sStore) getIngress(key string) (*networkingv1.Ingress, error) { func (s *k8sStore) getIngress(key string) (*networkingv1.Ingress, error) {
ing, err := s.listers.IngressWithAnnotation.ByKey(key) ing, err := s.listers.IngressWithAnnotation.ByKey(key)

View file

@ -37,13 +37,47 @@ import (
"sigs.k8s.io/controller-runtime/pkg/envtest" "sigs.k8s.io/controller-runtime/pkg/envtest"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
"k8s.io/ingress-nginx/internal/ingress/annotations/parser" "k8s.io/ingress-nginx/internal/ingress/annotations/parser"
"k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
) )
var pathPrefix networking.PathType = networking.PathTypePrefix var pathPrefix networking.PathType = networking.PathTypePrefix
var DefaultClassConfig = &ingressclass.IngressClassConfiguration{
Controller: ingressclass.DefaultControllerName,
AnnotationValue: ingressclass.DefaultAnnotationValue,
WatchWithoutClass: false,
}
var (
commonIngressSpec = networking.IngressSpec{
Rules: []networking.IngressRule{
{
Host: "dummy",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
Service: &networking.IngressServiceBackend{
Name: "http-svc",
Port: networking.ServiceBackendPort{
Number: 80,
},
},
},
},
},
},
},
},
},
}
)
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
//TODO: move env definition to docker image? //TODO: move env definition to docker image?
os.Setenv("KUBEBUILDER_ASSETS", "/usr/local/bin") os.Setenv("KUBEBUILDER_ASSETS", "/usr/local/bin")
@ -86,7 +120,8 @@ func TestStore(t *testing.T) {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
updateCh, updateCh,
false) false,
DefaultClassConfig)
storer.Run(stopCh) storer.Run(stopCh)
@ -116,9 +151,10 @@ func TestStore(t *testing.T) {
} }
}) })
t.Run("should return one event for add, update and delete of ingress", func(t *testing.T) { t.Run("should return no event for add, update and delete of ingress as the existing ingressclass is not the expected", func(t *testing.T) {
ns := createNamespace(clientSet, t) ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t) defer deleteNamespace(ns, clientSet, t)
createConfigMap(clientSet, ns, t) createConfigMap(clientSet, ns, t)
stopCh := make(chan struct{}) stopCh := make(chan struct{})
@ -163,40 +199,20 @@ func TestStore(t *testing.T) {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
updateCh, updateCh,
false) false,
DefaultClassConfig)
storer.Run(stopCh) storer.Run(stopCh)
ic := createIngressClass(clientSet, t, "not-k8s.io/not-ingress-nginx")
defer deleteIngressClass(ic, clientSet, t)
validSpec := commonIngressSpec
validSpec.IngressClassName = &ic
ing := ensureIngress(&networking.Ingress{ ing := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "dummy", Name: "dummy-no-class",
Namespace: ns, Namespace: ns,
}, },
Spec: networking.IngressSpec{ Spec: validSpec,
Rules: []networking.IngressRule{
{
Host: "dummy",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
Service: &networking.IngressServiceBackend{
Name: "http-svc",
Port: networking.ServiceBackendPort{
Number: 80,
},
},
},
},
},
},
},
},
},
},
}, clientSet, t) }, clientSet, t)
err := framework.WaitForIngressInNamespace(clientSet, ns, ing.Name) err := framework.WaitForIngressInNamespace(clientSet, ns, ing.Name)
@ -205,40 +221,113 @@ func TestStore(t *testing.T) {
} }
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
// create an invalid ingress (different class) ni := ing.DeepCopy()
ni.Spec.Rules[0].Host = "update-dummy"
_ = ensureIngress(ni, clientSet, t)
if err != nil {
t.Errorf("error creating ingress: %v", err)
}
// Secret takes a bit to update
time.Sleep(3 * time.Second)
err = clientSet.NetworkingV1().Ingresses(ni.Namespace).Delete(context.TODO(), ni.Name, metav1.DeleteOptions{})
if err != nil {
t.Errorf("error deleting ingress: %v", err)
}
err = framework.WaitForNoIngressInNamespace(clientSet, ni.Namespace, ni.Name)
if err != nil {
t.Errorf("error waiting for secret: %v", err)
}
time.Sleep(1 * time.Second)
if atomic.LoadUint64(&add) != 0 {
t.Errorf("expected 0 event of type Create but %v occurred", add)
}
if atomic.LoadUint64(&upd) != 0 {
t.Errorf("expected 0 event of type Update but %v occurred", upd)
}
if atomic.LoadUint64(&del) != 0 {
t.Errorf("expected 0 event of type Delete but %v occurred", del)
}
})
t.Run("should return one event for add, update and delete of ingress", func(t *testing.T) {
ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t)
ic := createIngressClass(clientSet, t, ingressclass.DefaultControllerName)
defer deleteIngressClass(ic, clientSet, t)
createConfigMap(clientSet, ns, t)
stopCh := make(chan struct{})
updateCh := channels.NewRingChannel(1024)
var add uint64
var upd uint64
var del uint64
go func(ch *channels.RingChannel) {
for {
evt, ok := <-ch.Out()
if !ok {
return
}
e := evt.(Event)
if e.Obj == nil {
continue
}
if _, ok := e.Obj.(*networking.Ingress); !ok {
continue
}
switch e.Type {
case CreateEvent:
atomic.AddUint64(&add, 1)
case UpdateEvent:
atomic.AddUint64(&upd, 1)
case DeleteEvent:
atomic.AddUint64(&del, 1)
}
}
}(updateCh)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns),
fmt.Sprintf("%v/udp", ns),
"",
10*time.Minute,
clientSet,
updateCh,
false,
DefaultClassConfig)
storer.Run(stopCh)
validSpec := commonIngressSpec
validSpec.IngressClassName = &ic
ing := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "dummy-class",
Namespace: ns,
},
Spec: validSpec,
}, clientSet, t)
err := framework.WaitForIngressInNamespace(clientSet, ns, ing.Name)
if err != nil {
t.Errorf("error waiting for secret: %v", err)
}
time.Sleep(1 * time.Second)
// create an invalid ingress (no ingress class and no watchWithoutClass config)
invalidIngress := ensureIngress(&networking.Ingress{ invalidIngress := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "custom-class", Name: "no-class",
Namespace: ns, Namespace: ns,
Annotations: map[string]string{
class.IngressKey: "something",
},
},
Spec: networking.IngressSpec{
Rules: []networking.IngressRule{
{
Host: "dummy",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
Service: &networking.IngressServiceBackend{
Name: "http-svc",
Port: networking.ServiceBackendPort{
Number: 80,
},
},
},
},
},
},
},
},
},
}, },
Spec: commonIngressSpec,
}, clientSet, t) }, clientSet, t)
defer deleteIngress(invalidIngress, clientSet, t) defer deleteIngress(invalidIngress, clientSet, t)
@ -253,7 +342,7 @@ func TestStore(t *testing.T) {
err = clientSet.NetworkingV1().Ingresses(ni.Namespace).Delete(context.TODO(), ni.Name, metav1.DeleteOptions{}) err = clientSet.NetworkingV1().Ingresses(ni.Namespace).Delete(context.TODO(), ni.Name, metav1.DeleteOptions{})
if err != nil { if err != nil {
t.Errorf("error creating ingress: %v", err) t.Errorf("error deleting ingress: %v", err)
} }
err = framework.WaitForNoIngressInNamespace(clientSet, ni.Namespace, ni.Name) err = framework.WaitForNoIngressInNamespace(clientSet, ni.Namespace, ni.Name)
@ -273,7 +362,7 @@ func TestStore(t *testing.T) {
} }
}) })
t.Run("should not receive updates for ingress with invalid class", func(t *testing.T) { t.Run("should return two events for add and delete and one for update of ingress and watch-without-class", func(t *testing.T) {
ns := createNamespace(clientSet, t) ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t) defer deleteNamespace(ns, clientSet, t)
createConfigMap(clientSet, ns, t) createConfigMap(clientSet, ns, t)
@ -311,6 +400,224 @@ func TestStore(t *testing.T) {
} }
}(updateCh) }(updateCh)
ingressClassconfig := &ingressclass.IngressClassConfiguration{
Controller: ingressclass.DefaultControllerName,
AnnotationValue: ingressclass.DefaultAnnotationValue,
WatchWithoutClass: true,
}
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns),
fmt.Sprintf("%v/udp", ns),
"",
10*time.Minute,
clientSet,
updateCh,
false,
ingressClassconfig)
storer.Run(stopCh)
validIngress1 := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "ing1",
Namespace: ns,
},
Spec: commonIngressSpec,
}, clientSet, t)
err := framework.WaitForIngressInNamespace(clientSet, ns, validIngress1.Name)
if err != nil {
t.Errorf("error waiting for ingress: %v", err)
}
otherIngress := commonIngressSpec
otherIngress.Rules[0].Host = "other-ingress"
validIngress2 := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "ing2",
Namespace: ns,
},
Spec: otherIngress,
}, clientSet, t)
err = framework.WaitForIngressInNamespace(clientSet, ns, validIngress2.Name)
if err != nil {
t.Errorf("error waiting for ingress: %v", err)
}
time.Sleep(1 * time.Second)
validIngressUpdated := validIngress1.DeepCopy()
validIngressUpdated.Spec.Rules[0].Host = "update-dummy"
_ = ensureIngress(validIngressUpdated, clientSet, t)
if err != nil {
t.Errorf("error updating ingress: %v", err)
}
// Secret takes a bit to update
time.Sleep(3 * time.Second)
err = clientSet.NetworkingV1().Ingresses(validIngressUpdated.Namespace).Delete(context.TODO(), validIngressUpdated.Name, metav1.DeleteOptions{})
if err != nil {
t.Errorf("error deleting ingress: %v", err)
}
err = clientSet.NetworkingV1().Ingresses(validIngress2.Namespace).Delete(context.TODO(), validIngress2.Name, metav1.DeleteOptions{})
if err != nil {
t.Errorf("error deleting ingress: %v", err)
}
err = framework.WaitForNoIngressInNamespace(clientSet, validIngressUpdated.Namespace, validIngressUpdated.Name)
if err != nil {
t.Errorf("error waiting for ingress deletion: %v", err)
}
err = framework.WaitForNoIngressInNamespace(clientSet, validIngress2.Namespace, validIngress2.Name)
if err != nil {
t.Errorf("error waiting for ingress deletion: %v", err)
}
time.Sleep(1 * time.Second)
if atomic.LoadUint64(&add) != 2 {
t.Errorf("expected 0 event of type Create but %v occurred", add)
}
if atomic.LoadUint64(&upd) != 1 {
t.Errorf("expected 0 event of type Update but %v occurred", upd)
}
if atomic.LoadUint64(&del) != 2 {
t.Errorf("expected 0 event of type Delete but %v occurred", del)
}
})
t.Run("should not receive updates for ingress with invalid class annotation", func(t *testing.T) {
ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t)
createConfigMap(clientSet, ns, t)
stopCh := make(chan struct{})
updateCh := channels.NewRingChannel(1024)
var add uint64
var upd uint64
var del uint64
// TODO: This repeats a lot, transform in a local function
go func(ch *channels.RingChannel) {
for {
evt, ok := <-ch.Out()
if !ok {
return
}
e := evt.(Event)
if e.Obj == nil {
continue
}
if _, ok := e.Obj.(*networking.Ingress); !ok {
continue
}
switch e.Type {
case CreateEvent:
atomic.AddUint64(&add, 1)
case UpdateEvent:
atomic.AddUint64(&upd, 1)
case DeleteEvent:
atomic.AddUint64(&del, 1)
}
}
}(updateCh)
storer := New(
ns,
fmt.Sprintf("%v/config", ns),
fmt.Sprintf("%v/tcp", ns),
fmt.Sprintf("%v/udp", ns),
"",
10*time.Minute,
clientSet,
updateCh,
false,
DefaultClassConfig)
storer.Run(stopCh)
// create an invalid ingress (different class)
invalidIngress := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "custom-class",
Namespace: ns,
Annotations: map[string]string{
ingressclass.IngressKey: "something",
},
},
Spec: commonIngressSpec,
}, clientSet, t)
err := framework.WaitForIngressInNamespace(clientSet, ns, invalidIngress.Name)
if err != nil {
t.Errorf("error waiting for ingress: %v", err)
}
time.Sleep(1 * time.Second)
invalidIngressUpdated := invalidIngress.DeepCopy()
invalidIngressUpdated.Spec.Rules[0].Host = "update-dummy"
_ = ensureIngress(invalidIngressUpdated, clientSet, t)
if err != nil {
t.Errorf("error creating ingress: %v", err)
}
// Secret takes a bit to update
time.Sleep(3 * time.Second)
if atomic.LoadUint64(&add) != 0 {
t.Errorf("expected 0 event of type Create but %v occurred", add)
}
if atomic.LoadUint64(&upd) != 0 {
t.Errorf("expected 0 event of type Update but %v occurred", upd)
}
if atomic.LoadUint64(&del) != 0 {
t.Errorf("expected 0 event of type Delete but %v occurred", del)
}
})
t.Run("should not receive updates for ingress with invalid class specification", func(t *testing.T) {
ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t)
ic := createIngressClass(clientSet, t, ingressclass.DefaultControllerName)
defer deleteIngressClass(ic, clientSet, t)
createConfigMap(clientSet, ns, t)
stopCh := make(chan struct{})
updateCh := channels.NewRingChannel(1024)
var add uint64
var upd uint64
var del uint64
go func(ch *channels.RingChannel) {
for {
evt, ok := <-ch.Out()
if !ok {
return
}
e := evt.(Event)
if e.Obj == nil {
continue
}
if _, ok := e.Obj.(*networking.Ingress); !ok {
continue
}
switch e.Type {
case CreateEvent:
atomic.AddUint64(&add, 1)
case UpdateEvent:
atomic.AddUint64(&upd, 1)
case DeleteEvent:
atomic.AddUint64(&del, 1)
}
}
}(updateCh)
storer := New( storer := New(
ns, ns,
fmt.Sprintf("%v/config", ns), fmt.Sprintf("%v/config", ns),
@ -320,44 +627,20 @@ func TestStore(t *testing.T) {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
updateCh, updateCh,
false) false,
DefaultClassConfig)
storer.Run(stopCh) storer.Run(stopCh)
invalidSpec := commonIngressSpec
invalidClassName := "blo123"
invalidSpec.IngressClassName = &invalidClassName
// create an invalid ingress (different class) // create an invalid ingress (different class)
invalidIngress := ensureIngress(&networking.Ingress{ invalidIngress := ensureIngress(&networking.Ingress{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "custom-class", Name: "custom-class",
Namespace: ns, Namespace: ns,
Annotations: map[string]string{
class.IngressKey: "something",
},
},
Spec: networking.IngressSpec{
Rules: []networking.IngressRule{
{
Host: "dummy",
IngressRuleValue: networking.IngressRuleValue{
HTTP: &networking.HTTPIngressRuleValue{
Paths: []networking.HTTPIngressPath{
{
Path: "/",
PathType: &pathPrefix,
Backend: networking.IngressBackend{
Service: &networking.IngressServiceBackend{
Name: "http-svc",
Port: networking.ServiceBackendPort{
Number: 80,
},
},
},
},
},
},
},
},
},
}, },
Spec: invalidSpec,
}, clientSet, t) }, clientSet, t)
err := framework.WaitForIngressInNamespace(clientSet, ns, invalidIngress.Name) err := framework.WaitForIngressInNamespace(clientSet, ns, invalidIngress.Name)
if err != nil { if err != nil {
@ -428,7 +711,8 @@ func TestStore(t *testing.T) {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
updateCh, updateCh,
false) false,
DefaultClassConfig)
storer.Run(stopCh) storer.Run(stopCh)
@ -474,6 +758,8 @@ func TestStore(t *testing.T) {
t.Run("should receive events from secret referenced from ingress", func(t *testing.T) { t.Run("should receive events from secret referenced from ingress", func(t *testing.T) {
ns := createNamespace(clientSet, t) ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t) defer deleteNamespace(ns, clientSet, t)
ic := createIngressClass(clientSet, t, ingressclass.DefaultControllerName)
defer deleteIngressClass(ic, clientSet, t)
createConfigMap(clientSet, ns, t) createConfigMap(clientSet, ns, t)
stopCh := make(chan struct{}) stopCh := make(chan struct{})
@ -494,6 +780,11 @@ func TestStore(t *testing.T) {
if e.Obj == nil { if e.Obj == nil {
continue continue
} }
// We should skip IngressClass events
if _, ok := e.Obj.(*networking.IngressClass); ok {
continue
}
switch e.Type { switch e.Type {
case CreateEvent: case CreateEvent:
atomic.AddUint64(&add, 1) atomic.AddUint64(&add, 1)
@ -514,7 +805,8 @@ func TestStore(t *testing.T) {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
updateCh, updateCh,
false) false,
DefaultClassConfig)
storer.Run(stopCh) storer.Run(stopCh)
@ -527,6 +819,7 @@ func TestStore(t *testing.T) {
Namespace: ns, Namespace: ns,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &ic,
TLS: []networking.IngressTLS{ TLS: []networking.IngressTLS{
{ {
SecretName: secretName, SecretName: secretName,
@ -586,6 +879,8 @@ func TestStore(t *testing.T) {
t.Run("should create an ingress with a secret which does not exist", func(t *testing.T) { t.Run("should create an ingress with a secret which does not exist", func(t *testing.T) {
ns := createNamespace(clientSet, t) ns := createNamespace(clientSet, t)
defer deleteNamespace(ns, clientSet, t) defer deleteNamespace(ns, clientSet, t)
ic := createIngressClass(clientSet, t, ingressclass.DefaultControllerName)
defer deleteIngressClass(ic, clientSet, t)
createConfigMap(clientSet, ns, t) createConfigMap(clientSet, ns, t)
stopCh := make(chan struct{}) stopCh := make(chan struct{})
@ -606,6 +901,12 @@ func TestStore(t *testing.T) {
if e.Obj == nil { if e.Obj == nil {
continue continue
} }
// We should skip IngressClass objects here
if _, ok := e.Obj.(*networking.IngressClass); ok {
continue
}
switch e.Type { switch e.Type {
case CreateEvent: case CreateEvent:
atomic.AddUint64(&add, 1) atomic.AddUint64(&add, 1)
@ -626,7 +927,8 @@ func TestStore(t *testing.T) {
10*time.Minute, 10*time.Minute,
clientSet, clientSet,
updateCh, updateCh,
false) false,
DefaultClassConfig)
storer.Run(stopCh) storer.Run(stopCh)
@ -639,6 +941,7 @@ func TestStore(t *testing.T) {
Namespace: ns, Namespace: ns,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &ic,
TLS: []networking.IngressTLS{ TLS: []networking.IngressTLS{
{ {
Hosts: secretHosts, Hosts: secretHosts,
@ -733,6 +1036,33 @@ func deleteNamespace(ns string, clientSet kubernetes.Interface, t *testing.T) {
} }
} }
func createIngressClass(clientSet kubernetes.Interface, t *testing.T, controller string) string {
t.Helper()
ingressclass := &networking.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("ingress-nginx-%v", time.Now().Unix()),
//Namespace: "xpto" // TODO: We don't support namespaced ingress-class yet
},
Spec: networking.IngressClassSpec{
Controller: controller,
},
}
ic, err := clientSet.NetworkingV1().IngressClasses().Create(context.TODO(), ingressclass, metav1.CreateOptions{})
if err != nil {
t.Errorf("error creating ingress class: %v", err)
}
return ic.Name
}
func deleteIngressClass(ic string, clientSet kubernetes.Interface, t *testing.T) {
t.Helper()
err := clientSet.NetworkingV1().IngressClasses().Delete(context.TODO(), ic, metav1.DeleteOptions{})
if err != nil {
t.Errorf("error deleting the ingress class: %v", err)
}
}
func createConfigMap(clientSet kubernetes.Interface, ns string, t *testing.T) string { func createConfigMap(clientSet kubernetes.Interface, ns string, t *testing.T) string {
t.Helper() t.Helper()
@ -790,6 +1120,7 @@ func newStore(t *testing.T) *k8sStore {
return &k8sStore{ return &k8sStore{
listers: &Lister{ listers: &Lister{
// add more listers if needed // add more listers if needed
IngressClass: IngressClassLister{cache.NewStore(cache.MetaNamespaceKeyFunc)},
Ingress: IngressLister{cache.NewStore(cache.MetaNamespaceKeyFunc)}, Ingress: IngressLister{cache.NewStore(cache.MetaNamespaceKeyFunc)},
IngressWithAnnotation: IngressWithAnnotationsLister{cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)}, IngressWithAnnotation: IngressWithAnnotationsLister{cache.NewStore(cache.DeletionHandlingMetaNamespaceKeyFunc)},
}, },
@ -867,18 +1198,18 @@ func TestUpdateSecretIngressMap(t *testing.T) {
func TestListIngresses(t *testing.T) { func TestListIngresses(t *testing.T) {
s := newStore(t) s := newStore(t)
invalidIngressClass := "something"
validIngressClass := ingressclass.DefaultControllerName
ingressToIgnore := &ingress.Ingress{ ingressToIgnore := &ingress.Ingress{
Ingress: networking.Ingress{ Ingress: networking.Ingress{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test-2", Name: "test-2",
Namespace: "testns", Namespace: "testns",
Annotations: map[string]string{
class.IngressKey: "something",
},
CreationTimestamp: metav1.NewTime(time.Now()), CreationTimestamp: metav1.NewTime(time.Now()),
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &invalidIngressClass,
DefaultBackend: &networking.IngressBackend{ DefaultBackend: &networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
Name: "demo", Name: "demo",
@ -900,6 +1231,7 @@ func TestListIngresses(t *testing.T) {
CreationTimestamp: metav1.NewTime(time.Now()), CreationTimestamp: metav1.NewTime(time.Now()),
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &validIngressClass,
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: "foo.bar", Host: "foo.bar",
@ -926,13 +1258,13 @@ func TestListIngresses(t *testing.T) {
} }
s.listers.IngressWithAnnotation.Add(ingressWithoutPath) s.listers.IngressWithAnnotation.Add(ingressWithoutPath)
ingressWithNginxClass := &ingress.Ingress{ ingressWithNginxClassAnnotation := &ingress.Ingress{
Ingress: networking.Ingress{ Ingress: networking.Ingress{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "test-4", Name: "test-4",
Namespace: "testns", Namespace: "testns",
Annotations: map[string]string{ Annotations: map[string]string{
class.IngressKey: "nginx", ingressclass.IngressKey: ingressclass.DefaultAnnotationValue,
}, },
CreationTimestamp: metav1.NewTime(time.Now()), CreationTimestamp: metav1.NewTime(time.Now()),
}, },
@ -963,7 +1295,7 @@ func TestListIngresses(t *testing.T) {
}, },
}, },
} }
s.listers.IngressWithAnnotation.Add(ingressWithNginxClass) s.listers.IngressWithAnnotation.Add(ingressWithNginxClassAnnotation)
ingresses := s.ListIngresses() ingresses := s.ListIngresses()

View file

@ -26,7 +26,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
"k8s.io/ingress-nginx/internal/ingress/metric/collectors" "k8s.io/ingress-nginx/internal/ingress/metric/collectors"
) )
@ -66,7 +65,7 @@ type collector struct {
} }
// NewCollector creates a new metric collector the for ingress controller // NewCollector creates a new metric collector the for ingress controller
func NewCollector(metricsPerHost bool, registry *prometheus.Registry) (Collector, error) { func NewCollector(metricsPerHost bool, registry *prometheus.Registry, ingressclass string) (Collector, error) {
podNamespace := os.Getenv("POD_NAMESPACE") podNamespace := os.Getenv("POD_NAMESPACE")
if podNamespace == "" { if podNamespace == "" {
podNamespace = "default" podNamespace = "default"
@ -74,22 +73,22 @@ func NewCollector(metricsPerHost bool, registry *prometheus.Registry) (Collector
podName := os.Getenv("POD_NAME") podName := os.Getenv("POD_NAME")
nc, err := collectors.NewNGINXStatus(podName, podNamespace, class.IngressClass) nc, err := collectors.NewNGINXStatus(podName, podNamespace, ingressclass)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pc, err := collectors.NewNGINXProcess(podName, podNamespace, class.IngressClass) pc, err := collectors.NewNGINXProcess(podName, podNamespace, ingressclass)
if err != nil { if err != nil {
return nil, err return nil, err
} }
s, err := collectors.NewSocketCollector(podName, podNamespace, class.IngressClass, metricsPerHost) s, err := collectors.NewSocketCollector(podName, podNamespace, ingressclass, metricsPerHost)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ic := collectors.NewController(podName, podNamespace, class.IngressClass) ic := collectors.NewController(podName, podNamespace, ingressclass)
return Collector(&collector{ return Collector(&collector{
nginxStatus: nc, nginxStatus: nc,

View file

@ -29,7 +29,7 @@ import (
testclient "k8s.io/client-go/kubernetes/fake" testclient "k8s.io/client-go/kubernetes/fake"
"k8s.io/ingress-nginx/internal/ingress" "k8s.io/ingress-nginx/internal/ingress"
"k8s.io/ingress-nginx/internal/ingress/annotations/class" "k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
"k8s.io/ingress-nginx/internal/k8s" "k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/internal/task" "k8s.io/ingress-nginx/internal/task"
) )
@ -214,7 +214,7 @@ func buildExtensionsIngresses() []networking.Ingress {
Name: "foo_ingress_different_class", Name: "foo_ingress_different_class",
Namespace: metav1.NamespaceDefault, Namespace: metav1.NamespaceDefault,
Annotations: map[string]string{ Annotations: map[string]string{
class.IngressKey: "no-nginx", ingressclass.IngressKey: "no-nginx",
}, },
}, },
Status: networking.IngressStatus{ Status: networking.IngressStatus{

View file

@ -124,9 +124,6 @@ func MetaNamespaceKey(obj interface{}) string {
// IsIngressV1Ready indicates if the running Kubernetes version is at least v1.19.0 // IsIngressV1Ready indicates if the running Kubernetes version is at least v1.19.0
var IsIngressV1Ready bool var IsIngressV1Ready bool
// IngressClass indicates the class of the Ingress to use as filter
var IngressClass *networkingv1.IngressClass
// IngressNGINXController defines the valid value of IngressClass // IngressNGINXController defines the valid value of IngressClass
// Controller field for ingress-nginx // Controller field for ingress-nginx
const IngressNGINXController = "k8s.io/ingress-nginx" const IngressNGINXController = "k8s.io/ingress-nginx"

View file

@ -14,7 +14,9 @@ controller:
https-port: "1443" https-port: "1443"
# e2e tests do not require information about ingress status # e2e tests do not require information about ingress status
update-status: "false" update-status: "false"
ingressClassResource:
# We will create and remove each IC/ClusterRole/ClusterRoleBinding per test so there's no conflict
enabled: false
scope: scope:
enabled: true enabled: true

View file

@ -121,11 +121,7 @@ var _ = framework.IngressNginxDescribe("[Serial] admission controller", func() {
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid configuration should return an error") assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid configuration should return an error")
}) })
ginkgo.It("should not return an error if the Ingress V1 definition is valid", func() { ginkgo.It("should not return an error if the Ingress V1 definition is valid with Ingress Class", func() {
if !f.IsIngressV1Ready {
ginkgo.Skip("Test requires Kubernetes v1.19 or higher")
}
err := createIngress(f.Namespace, validV1Ingress) err := createIngress(f.Namespace, validV1Ingress)
assert.Nil(ginkgo.GinkgoT(), err, "creating an ingress using kubectl") assert.Nil(ginkgo.GinkgoT(), err, "creating an ingress using kubectl")
@ -140,15 +136,26 @@ var _ = framework.IngressNginxDescribe("[Serial] admission controller", func() {
Status(http.StatusOK) Status(http.StatusOK)
}) })
ginkgo.It("should return an error if the Ingress V1 definition contains invalid annotations", func() { ginkgo.It("should not return an error if the Ingress V1 definition is valid with IngressClass annotation", func() {
if !f.IsIngressV1Ready { err := createIngress(f.Namespace, validV1IngressAnnotation)
ginkgo.Skip("Test requires Kubernetes v1.19 or higher") assert.Nil(ginkgo.GinkgoT(), err, "creating an ingress using kubectl")
}
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "extensions-class")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", "extensions-class").
Expect().
Status(http.StatusOK)
})
ginkgo.It("should return an error if the Ingress V1 definition contains invalid annotations", func() {
err := createIngress(f.Namespace, invalidV1Ingress) err := createIngress(f.Namespace, invalidV1Ingress)
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress using kubectl") assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress using kubectl")
_, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), "extensions", metav1.GetOptions{}) _, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), "extensions-invalid", metav1.GetOptions{})
if !apierrors.IsNotFound(err) { if !apierrors.IsNotFound(err) {
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid configuration should return an error") assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid configuration should return an error")
} }
@ -172,6 +179,7 @@ kind: Ingress
metadata: metadata:
name: extensions name: extensions
spec: spec:
ingressClassName: nginx
rules: rules:
- host: extensions - host: extensions
http: http:
@ -184,6 +192,28 @@ spec:
port: port:
number: 80 number: 80
---
`
validV1IngressAnnotation = `
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: extensions-class
annotations:
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: extensions-class
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: echo
port:
number: 80
--- ---
` `
@ -191,13 +221,14 @@ spec:
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: extensions name: extensions-invalid
annotations: annotations:
nginx.ingress.kubernetes.io/configuration-snippet: | nginx.ingress.kubernetes.io/configuration-snippet: |
invalid directive invalid directive
spec: spec:
ingressClassName: nginx
rules: rules:
- host: extensions - host: extensions-invalid
http: http:
paths: paths:
- path: / - path: /

View file

@ -131,6 +131,7 @@ var _ = framework.DescribeAnnotation("affinity session-cookie-name", func() {
Annotations: annotations, Annotations: annotations,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &f.IngressClass,
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: host, Host: host,

View file

@ -22,9 +22,7 @@ import (
"strings" "strings"
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"
networking "k8s.io/api/networking/v1"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
) )
@ -32,7 +30,7 @@ const (
canaryService = "echo-canary" canaryService = "echo-canary"
) )
var _ = framework.DescribeAnnotation("canary-*", func() { var _ = framework.DescribeAnnotation("canary", func() {
f := framework.NewDefaultFramework("canary") f := framework.NewDefaultFramework("canary")
ginkgo.BeforeEach(func() { ginkgo.BeforeEach(func() {
@ -327,15 +325,25 @@ var _ = framework.DescribeAnnotation("canary-*", func() {
f.Namespace, canaryService, 80, canaryAnnotations) f.Namespace, canaryService, 80, canaryAnnotations)
f.EnsureIngress(canaryIng) f.EnsureIngress(canaryIng)
err := framework.UpdateIngress(f.KubeClientSet, f.Namespace, canaryIngName, f.WaitForNginxServer(host,
func(ingress *networking.Ingress) error { func(server string) bool {
ingress.ObjectMeta.Annotations = map[string]string{ return strings.Contains(server, "server_name foo")
})
newAnnotations := map[string]string{
"nginx.ingress.kubernetes.io/canary": "true", "nginx.ingress.kubernetes.io/canary": "true",
"nginx.ingress.kubernetes.io/canary-by-header": "CanaryByHeader2", "nginx.ingress.kubernetes.io/canary-by-header": "CanaryByHeader2",
} }
return nil
modIng := framework.NewSingleIngress(canaryIngName, "/", host,
f.Namespace, canaryService, 80, newAnnotations)
f.UpdateIngress(modIng)
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, "server_name foo")
}) })
assert.Nil(ginkgo.GinkgoT(), err)
ginkgo.By("routing requests destined for the mainline ingress to the mainline upstream") ginkgo.By("routing requests destined for the mainline ingress to the mainline upstream")
f.HTTPTestClient(). f.HTTPTestClient().
@ -707,6 +715,11 @@ var _ = framework.DescribeAnnotation("canary-*", func() {
f.Namespace, canaryService, 80, canaryAnnotations) f.Namespace, canaryService, 80, canaryAnnotations)
f.EnsureIngress(canaryIng) f.EnsureIngress(canaryIng)
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, "server_name foo")
})
ginkgo.By("returning requests from the mainline only when weight is equal to 0") ginkgo.By("returning requests from the mainline only when weight is equal to 0")
f.HTTPTestClient(). f.HTTPTestClient().
GET("/"). GET("/").
@ -719,15 +732,20 @@ var _ = framework.DescribeAnnotation("canary-*", func() {
ginkgo.By("returning requests from the canary only when weight is equal to 100") ginkgo.By("returning requests from the canary only when weight is equal to 100")
err := framework.UpdateIngress(f.KubeClientSet, f.Namespace, canaryIngName, newAnnotations := map[string]string{
func(ingress *networking.Ingress) error {
ingress.ObjectMeta.Annotations = map[string]string{
"nginx.ingress.kubernetes.io/canary": "true", "nginx.ingress.kubernetes.io/canary": "true",
"nginx.ingress.kubernetes.io/canary-weight": "100", "nginx.ingress.kubernetes.io/canary-weight": "100",
} }
return nil
modIng := framework.NewSingleIngress(canaryIngName, "/", host,
f.Namespace, canaryService, 80, newAnnotations)
f.UpdateIngress(modIng)
f.WaitForNginxServer(host,
func(server string) bool {
return strings.Contains(server, "server_name foo")
}) })
assert.Nil(ginkgo.GinkgoT(), err)
f.HTTPTestClient(). f.HTTPTestClient().
GET("/"). GET("/").

View file

@ -47,6 +47,7 @@ var _ = framework.IngressNginxDescribe("[Default Backend] change default setting
Annotations: annotations, Annotations: annotations,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &f.IngressClass,
DefaultBackend: &networking.IngressBackend{ DefaultBackend: &networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
Name: framework.EchoService, Name: framework.EchoService,

View file

@ -69,6 +69,7 @@ type Framework struct {
APIExtensionsClientSet apiextcs.Interface APIExtensionsClientSet apiextcs.Interface
Namespace string Namespace string
IngressClass string
pod *corev1.Pod pod *corev1.Pod
} }
@ -108,6 +109,9 @@ func (f *Framework) BeforeEach() {
f.Namespace, err = CreateKubeNamespace(f.BaseName, f.KubeClientSet) f.Namespace, err = CreateKubeNamespace(f.BaseName, f.KubeClientSet)
assert.Nil(ginkgo.GinkgoT(), err, "creating namespace") assert.Nil(ginkgo.GinkgoT(), err, "creating namespace")
f.IngressClass, err = CreateIngressClass(f.Namespace, f.KubeClientSet)
assert.Nil(ginkgo.GinkgoT(), err, "creating IngressClass")
err = f.newIngressController(f.Namespace, f.BaseName) err = f.newIngressController(f.Namespace, f.BaseName)
assert.Nil(ginkgo.GinkgoT(), err, "deploying the ingress controller") assert.Nil(ginkgo.GinkgoT(), err, "deploying the ingress controller")
@ -127,6 +131,14 @@ func (f *Framework) AfterEach() {
}() }()
}(f.KubeClientSet, f.Namespace) }(f.KubeClientSet, f.Namespace)
defer func(kubeClient kubernetes.Interface, ingressclass string) {
go func() {
defer ginkgo.GinkgoRecover()
err := deleteIngressClass(kubeClient, ingressclass)
assert.Nil(ginkgo.GinkgoT(), err, "deleting IngressClass")
}()
}(f.KubeClientSet, f.IngressClass)
if !ginkgo.CurrentGinkgoTestDescription().Failed { if !ginkgo.CurrentGinkgoTestDescription().Failed {
return return
} }
@ -580,6 +592,7 @@ func NewSingleIngress(name, path, host, ns, service string, port int, annotation
func NewSingleIngressWithMultiplePaths(name string, paths []string, host, ns, service string, port int, annotations map[string]string) *networking.Ingress { func NewSingleIngressWithMultiplePaths(name string, paths []string, host, ns, service string, port int, annotations map[string]string) *networking.Ingress {
pathtype := networking.PathTypePrefix pathtype := networking.PathTypePrefix
spec := networking.IngressSpec{ spec := networking.IngressSpec{
IngressClassName: GetIngressClassName(ns),
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: host, Host: host,
@ -611,6 +624,7 @@ func NewSingleIngressWithMultiplePaths(name string, paths []string, host, ns, se
func newSingleIngressWithRules(name, path, host, ns, service string, port int, annotations map[string]string, tlsHosts []string) *networking.Ingress { func newSingleIngressWithRules(name, path, host, ns, service string, port int, annotations map[string]string, tlsHosts []string) *networking.Ingress {
pathtype := networking.PathTypePrefix pathtype := networking.PathTypePrefix
spec := networking.IngressSpec{ spec := networking.IngressSpec{
IngressClassName: GetIngressClassName(ns),
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
IngressRuleValue: networking.IngressRuleValue{ IngressRuleValue: networking.IngressRuleValue{
@ -656,6 +670,7 @@ func newSingleIngressWithRules(name, path, host, ns, service string, port int, a
func NewSingleIngressWithBackendAndRules(name, path, host, ns, defaultService string, defaultPort int, service string, port int, annotations map[string]string) *networking.Ingress { func NewSingleIngressWithBackendAndRules(name, path, host, ns, defaultService string, defaultPort int, service string, port int, annotations map[string]string) *networking.Ingress {
pathtype := networking.PathTypePrefix pathtype := networking.PathTypePrefix
spec := networking.IngressSpec{ spec := networking.IngressSpec{
IngressClassName: GetIngressClassName(ns),
DefaultBackend: &networking.IngressBackend{ DefaultBackend: &networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
Name: defaultService, Name: defaultService,
@ -695,6 +710,7 @@ func NewSingleIngressWithBackendAndRules(name, path, host, ns, defaultService st
// NewSingleCatchAllIngress creates a simple ingress with a catch-all backend // NewSingleCatchAllIngress creates a simple ingress with a catch-all backend
func NewSingleCatchAllIngress(name, ns, service string, port int, annotations map[string]string) *networking.Ingress { func NewSingleCatchAllIngress(name, ns, service string, port int, annotations map[string]string) *networking.Ingress {
spec := networking.IngressSpec{ spec := networking.IngressSpec{
IngressClassName: GetIngressClassName(ns),
DefaultBackend: &networking.IngressBackend{ DefaultBackend: &networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
Name: service, Name: service,

View file

@ -24,6 +24,8 @@ import (
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid" "k8s.io/apimachinery/pkg/util/uuid"
@ -31,6 +33,8 @@ import (
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/ingress-nginx/internal/k8s"
) )
const ( const (
@ -117,6 +121,93 @@ func deleteKubeNamespace(c kubernetes.Interface, namespace string) error {
}) })
} }
// CreateIngressClass creates a new IngressClass related to a test/namespace and also
// the required ClusterRole/ClusterRoleBinding
func CreateIngressClass(namespace string, c kubernetes.Interface) (string, error) {
icname := fmt.Sprintf("ic-%s", namespace)
var err error
ic, err := c.NetworkingV1().IngressClasses().
Create(context.TODO(), &networkingv1.IngressClass{
ObjectMeta: metav1.ObjectMeta{
Name: icname,
},
Spec: networkingv1.IngressClassSpec{
Controller: k8s.IngressNGINXController,
},
}, metav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("Unexpected error creating IngressClass %s: %v", icname, err)
}
_, err = c.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: icname},
Rules: []rbacv1.PolicyRule{{
APIGroups: []string{"networking.k8s.io"},
Resources: []string{"ingressclasses"},
Verbs: []string{"get", "list", "watch"},
}},
}, metav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("Unexpected error creating IngressClass ClusterRole %s: %v", icname, err)
}
_, err = c.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: icname,
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: icname,
},
Subjects: []rbacv1.Subject{
{
APIGroup: "",
Kind: "ServiceAccount",
Namespace: namespace,
Name: "nginx-ingress",
},
},
}, metav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("Unexpected error creating IngressClass ClusterRoleBinding %s: %v", icname, err)
}
return ic.Name, nil
}
//deleteIngressClass deletes an IngressClass and its related ClusterRole* objects
func deleteIngressClass(c kubernetes.Interface, ingressclass string) error {
var err error
grace := int64(0)
pb := metav1.DeletePropagationBackground
deleteOptions := metav1.DeleteOptions{
GracePeriodSeconds: &grace,
PropagationPolicy: &pb,
}
err = c.NetworkingV1().IngressClasses().Delete(context.TODO(), ingressclass, deleteOptions)
if err != nil {
return fmt.Errorf("Unexpected error deleting IngressClass %s: %v", ingressclass, err)
}
err = c.RbacV1().ClusterRoleBindings().Delete(context.TODO(), ingressclass, deleteOptions)
if err != nil {
return fmt.Errorf("Unexpected error deleting IngressClass ClusterRoleBinding %s: %v", ingressclass, err)
}
err = c.RbacV1().ClusterRoles().Delete(context.TODO(), ingressclass, deleteOptions)
if err != nil {
return fmt.Errorf("Unexpected error deleting IngressClass ClusterRole %s: %v", ingressclass, err)
}
return nil
}
//GetIngressClassName returns the default IngressClassName given a namespace
func GetIngressClassName(namespace string) *string {
icname := fmt.Sprintf("ic-%s", namespace)
return &icname
}
// WaitForKubeNamespaceNotExist waits until a namespaces is not present in the cluster // WaitForKubeNamespaceNotExist waits until a namespaces is not present in the cluster
func WaitForKubeNamespaceNotExist(c kubernetes.Interface, namespace string) error { func WaitForKubeNamespaceNotExist(c kubernetes.Interface, namespace string) error {
return wait.Poll(Poll, DefaultTimeout, namespaceNotExist(c, namespace)) return wait.Poll(Poll, DefaultTimeout, namespaceNotExist(c, namespace))

View file

@ -61,6 +61,7 @@ var _ = framework.IngressNginxDescribe("[Ingress] definition without host", func
Namespace: f.Namespace, Namespace: f.Namespace,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &f.IngressClass,
DefaultBackend: &networking.IngressBackend{ DefaultBackend: &networking.IngressBackend{
Service: &networking.IngressServiceBackend{ Service: &networking.IngressServiceBackend{
Name: framework.EchoService, Name: framework.EchoService,

View file

@ -36,6 +36,8 @@ cleanup() {
trap cleanup EXIT trap cleanup EXIT
export KIND_CLUSTER_NAME=${KIND_CLUSTER_NAME:-ingress-nginx-dev}
if ! command -v kind --version &> /dev/null; then if ! command -v kind --version &> /dev/null; then
echo "kind is not installed. Use the package manager or visit the official site https://kind.sigs.k8s.io/" echo "kind is not installed. Use the package manager or visit the official site https://kind.sigs.k8s.io/"
exit 1 exit 1
@ -43,14 +45,17 @@ fi
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
export KIND_CLUSTER_NAME=${KIND_CLUSTER_NAME:-ingress-nginx-dev} # Use 1.0.0-dev to make sure we use the latest configuration in the helm template
export TAG=1.0.0-dev
export ARCH=${ARCH:-amd64}
export REGISTRY=ingress-controller
export KUBECONFIG="${KUBECONFIG:-$HOME/.kube/kind-config-$KIND_CLUSTER_NAME}" export KUBECONFIG="${KUBECONFIG:-$HOME/.kube/kind-config-$KIND_CLUSTER_NAME}"
if [ "${SKIP_CLUSTER_CREATION:-false}" = "false" ]; then if [ "${SKIP_CLUSTER_CREATION:-false}" = "false" ]; then
echo "[dev-env] creating Kubernetes cluster with kind" echo "[dev-env] creating Kubernetes cluster with kind"
export K8S_VERSION=${K8S_VERSION:-v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab} export K8S_VERSION=${K8S_VERSION:-v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6}
kind create cluster \ kind create cluster \
--verbosity=${KIND_LOG_LEVEL} \ --verbosity=${KIND_LOG_LEVEL} \
@ -61,8 +66,23 @@ if [ "${SKIP_CLUSTER_CREATION:-false}" = "false" ]; then
echo "Kubernetes cluster:" echo "Kubernetes cluster:"
kubectl get nodes -o wide kubectl get nodes -o wide
fi fi
if [ "${SKIP_IMAGE_CREATION:-false}" = "false" ]; then
if ! command -v ginkgo &> /dev/null; then
go get github.com/onsi/ginkgo/ginkgo@v1.16.4
fi
echo "[dev-env] building image"
make -C ${DIR}/../../ clean-image build image
fi
KIND_WORKERS=$(kind get nodes --name="${KIND_CLUSTER_NAME}" | awk '{printf (NR>1?",":"") $1}')
echo "[dev-env] copying docker images to cluster..."
kind load docker-image --name="${KIND_CLUSTER_NAME}" --nodes=${KIND_WORKERS} ${REGISTRY}/controller:${TAG}
echo "[dev-env] running helm chart e2e tests..." echo "[dev-env] running helm chart e2e tests..."
# Uses a custom chart-testing image to avoid timeouts waiting for namespace deletion. # Uses a custom chart-testing image to avoid timeouts waiting for namespace deletion.
# The changes can be found here: https://github.com/aledbf/chart-testing/commit/41fe0ae0733d0c9a538099fb3cec522e888e3d82 # The changes can be found here: https://github.com/aledbf/chart-testing/commit/41fe0ae0733d0c9a538099fb3cec522e888e3d82

View file

@ -58,7 +58,7 @@ export KUBECONFIG="${KUBECONFIG:-$HOME/.kube/kind-config-$KIND_CLUSTER_NAME}"
if [ "${SKIP_CLUSTER_CREATION:-false}" = "false" ]; then if [ "${SKIP_CLUSTER_CREATION:-false}" = "false" ]; then
echo "[dev-env] creating Kubernetes cluster with kind" echo "[dev-env] creating Kubernetes cluster with kind"
export K8S_VERSION=${K8S_VERSION:-v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab} export K8S_VERSION=${K8S_VERSION:-v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6}
kind create cluster \ kind create cluster \
--verbosity=${KIND_LOG_LEVEL} \ --verbosity=${KIND_LOG_LEVEL} \
@ -73,7 +73,7 @@ fi
if [ "${SKIP_IMAGE_CREATION:-false}" = "false" ]; then if [ "${SKIP_IMAGE_CREATION:-false}" = "false" ]; then
if ! command -v ginkgo &> /dev/null; then if ! command -v ginkgo &> /dev/null; then
go get github.com/onsi/ginkgo/ginkgo go get github.com/onsi/ginkgo/ginkgo@v1.16.4
fi fi
echo "[dev-env] building image" echo "[dev-env] building image"

View file

@ -80,6 +80,7 @@ func buildIngressWithNonexistentService(host, namespace, path string) *networkin
Namespace: namespace, Namespace: namespace,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: framework.GetIngressClassName(namespace),
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: host, Host: host,
@ -115,6 +116,7 @@ func buildIngressWithUnavailableServiceEndpoints(host, namespace, path string) (
Namespace: namespace, Namespace: namespace,
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: framework.GetIngressClassName(namespace),
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: host, Host: host,

View file

@ -25,7 +25,6 @@ import (
"github.com/gavv/httpexpect/v2" "github.com/gavv/httpexpect/v2"
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
core "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1" networking "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -43,7 +42,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,
@ -74,7 +73,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
ginkgo.It("should return 200 for service type=ExternalName without a port defined", func() { ginkgo.It("should return 200 for service type=ExternalName without a port defined", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,
@ -108,7 +107,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
ginkgo.It("should return 200 for service type=ExternalName with a port defined", func() { ginkgo.It("should return 200 for service type=ExternalName with a port defined", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,
@ -149,7 +148,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
ginkgo.It("should return status 502 for service type=ExternalName with an invalid host", func() { ginkgo.It("should return status 502 for service type=ExternalName with an invalid host", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,
@ -180,7 +179,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
ginkgo.It("should return 200 for service type=ExternalName using a port name", func() { ginkgo.It("should return 200 for service type=ExternalName using a port name", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,
@ -230,7 +229,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
ginkgo.It("should return 200 for service type=ExternalName using FQDN with trailing dot", func() { ginkgo.It("should return 200 for service type=ExternalName using FQDN with trailing dot", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,
@ -261,7 +260,7 @@ var _ = framework.IngressNginxDescribe("[Service] Type ExternalName", func() {
ginkgo.It("should update the external name after a service update", func() { ginkgo.It("should update the external name after a service update", func() {
host := "echo" host := "echo"
svc := &core.Service{ svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: framework.HTTPBinService, Name: framework.HTTPBinService,
Namespace: f.Namespace, Namespace: f.Namespace,

0
test/e2e/settings/global_external_auth.go Executable file → Normal file
View file

View file

@ -18,7 +18,6 @@ package settings
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@ -26,13 +25,11 @@ import (
"github.com/onsi/ginkgo" "github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
networking "k8s.io/api/networking/v1" networkingv1 "k8s.io/api/networking/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/ingress-nginx/internal/ingress/annotations/class" "k8s.io/ingress-nginx/internal/ingress/controller/ingressclass"
"k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/test/e2e/framework" "k8s.io/ingress-nginx/test/e2e/framework"
) )
@ -41,39 +38,20 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
var doOnce sync.Once var doOnce sync.Once
testIngressClassName := "test-new-ingress-class" otherIngressClassName := "test-new-ingress-class"
otherController := "k8s.io/other-class"
ginkgo.BeforeEach(func() { ginkgo.BeforeEach(func() {
f.NewEchoDeploymentWithReplicas(1) f.NewEchoDeploymentWithReplicas(1)
doOnce.Do(func() { doOnce.Do(func() {
f.KubeClientSet.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: "ingress-nginx-class"},
Rules: []rbacv1.PolicyRule{{
APIGroups: []string{"networking.k8s.io"},
Resources: []string{"ingressclasses"},
Verbs: []string{"get", "list", "watch"},
}},
}, metav1.CreateOptions{})
f.KubeClientSet.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "ingress-nginx-class",
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "ingress-nginx-class",
},
}, metav1.CreateOptions{})
_, err := f.KubeClientSet.NetworkingV1().IngressClasses(). _, err := f.KubeClientSet.NetworkingV1().IngressClasses().
Create(context.TODO(), &networking.IngressClass{ Create(context.TODO(), &networkingv1.IngressClass{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: testIngressClassName, Name: otherIngressClassName,
}, },
Spec: networking.IngressClassSpec{ Spec: networkingv1.IngressClassSpec{
Controller: k8s.IngressNGINXController, Controller: otherController,
}, },
}, metav1.CreateOptions{}) }, metav1.CreateOptions{})
@ -83,17 +61,23 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
}) })
}) })
ginkgo.Context("Without a specific ingress-class", func() { ginkgo.Context("With default ingress class config", func() {
ginkgo.It("should ignore Ingress with class", func() { ginkgo.It("should ignore Ingress with a different class annotation", func() {
invalidHost := "foo" invalidHost := "foo"
annotations := map[string]string{ annotations := map[string]string{
class.IngressKey: "testclass", ingressclass.IngressKey: "testclass",
} }
ing := framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, annotations) ing := framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, annotations)
// We should drop the ingressClassName here as we just want to rely on the annotation in this test
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing) f.EnsureIngress(ing)
validHost := "bar" validHost := "bar"
ing = framework.NewSingleIngress(validHost, "/", validHost, f.Namespace, framework.EchoService, 80, nil) annotationClass := map[string]string{
ingressclass.IngressKey: ingressclass.DefaultAnnotationValue,
}
ing = framework.NewSingleIngress(validHost, "/", validHost, f.Namespace, framework.EchoService, 80, annotationClass)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing) f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool { f.WaitForNginxConfiguration(func(cfg string) bool {
@ -113,14 +97,300 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
Expect(). Expect().
Status(http.StatusOK) Status(http.StatusOK)
}) })
ginkgo.It("should ignore Ingress with different controller class", func() {
invalidHost := "foo-1"
ing := framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = &otherIngressClassName
f.EnsureIngress(ing)
validHost := "bar-1"
ing = framework.NewSingleIngress(validHost, "/", validHost, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name foo-1") &&
strings.Contains(cfg, "server_name bar-1")
}) })
ginkgo.Context("With a specific ingress-class", func() { f.HTTPTestClient().
GET("/").
WithHeader("Host", invalidHost).
Expect().
Status(http.StatusNotFound)
f.HTTPTestClient().
GET("/").
WithHeader("Host", validHost).
Expect().
Status(http.StatusOK)
})
ginkgo.It("should accept both Ingresses with default IngressClassName and IngressClass annotation", func() {
validHostAnnotation := "foo-ok"
annotationClass := map[string]string{
ingressclass.IngressKey: ingressclass.DefaultAnnotationValue,
}
ing := framework.NewSingleIngress(validHostAnnotation, "/", validHostAnnotation, f.Namespace, framework.EchoService, 80, annotationClass)
// We need to drop the Class here as we just want the annotation
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
validHostClass := "bar-ok"
ing = framework.NewSingleIngress(validHostClass, "/", validHostClass, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name foo-ok") &&
strings.Contains(cfg, "server_name bar-ok")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", validHostAnnotation).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", validHostClass).
Expect().
Status(http.StatusOK)
})
ginkgo.It("should ignore Ingress without IngressClass configuration", func() {
invalidHost := "foo-invalid"
ing := framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
validHostClass := "bar-valid"
ing = framework.NewSingleIngress(validHostClass, "/", validHostClass, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name foo-invalid") &&
strings.Contains(cfg, "server_name bar-valid")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", invalidHost).
Expect().
Status(http.StatusNotFound)
f.HTTPTestClient().
GET("/").
WithHeader("Host", validHostClass).
Expect().
Status(http.StatusOK)
})
ginkgo.It("should delete Ingress when class is removed", func() {
hostAnnotation := "foo-annotation"
annotations := map[string]string{
ingressclass.IngressKey: ingressclass.DefaultAnnotationValue,
}
ing := framework.NewSingleIngress(hostAnnotation, "/", hostAnnotation, f.Namespace, framework.EchoService, 80, annotations)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
hostClass := "foo-class"
ing = framework.NewSingleIngress(hostClass, "/", hostClass, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name foo-annotation") &&
strings.Contains(cfg, "server_name foo-class")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostAnnotation).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostClass).
Expect().
Status(http.StatusOK)
ing, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), hostAnnotation, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
delete(ing.Annotations, ingressclass.IngressKey)
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ing.Namespace).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ingWithClass, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), hostClass, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ingWithClass.Spec.IngressClassName = nil
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ingWithClass.Namespace).Update(context.TODO(), ingWithClass, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
framework.Sleep()
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name foo-annotation") &&
!strings.Contains(cfg, "server_name foo-class")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostAnnotation).
Expect().
Status(http.StatusNotFound)
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostClass).
Expect().
Status(http.StatusNotFound)
})
ginkgo.It("should serve Ingress when class is added", func() {
hostNoAnnotation := "foo-no-annotation"
ing := framework.NewSingleIngress(hostNoAnnotation, "/", hostNoAnnotation, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
hostNoClass := "foo-no-class"
ing = framework.NewSingleIngress(hostNoClass, "/", hostNoClass, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name foo-no-nnotation") &&
!strings.Contains(cfg, "server_name foo-no-class")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostNoAnnotation).
Expect().
Status(http.StatusNotFound)
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostNoClass).
Expect().
Status(http.StatusNotFound)
ing, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), hostNoAnnotation, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
annotation := map[string]string{
ingressclass.IngressKey: ingressclass.DefaultAnnotationValue,
}
ing.Annotations = annotation
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ing.Namespace).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ingWithClass, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), hostNoClass, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ingWithClass.Spec.IngressClassName = framework.GetIngressClassName(f.Namespace)
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ingWithClass.Namespace).Update(context.TODO(), ingWithClass, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
framework.Sleep()
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name foo-no-annotation") &&
strings.Contains(cfg, "server_name foo-no-class")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostNoAnnotation).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostNoClass).
Expect().
Status(http.StatusOK)
})
ginkgo.It("should serve Ingress when class is updated between annotation and ingressClassName", func() {
hostAnnotation2class := "foo-annotation2class"
annotationClass := map[string]string{
ingressclass.IngressKey: ingressclass.DefaultAnnotationValue,
}
ing := framework.NewSingleIngress(hostAnnotation2class, "/", hostAnnotation2class, f.Namespace, framework.EchoService, 80, annotationClass)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
hostClass2Annotation := "foo-class2annotation"
ing = framework.NewSingleIngress(hostClass2Annotation, "/", hostClass2Annotation, f.Namespace, framework.EchoService, 80, nil)
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name foo-annotation2class") &&
strings.Contains(cfg, "server_name foo-class2annotation")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostAnnotation2class).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostClass2Annotation).
Expect().
Status(http.StatusOK)
ingAnnotation2Class, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), hostAnnotation2class, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
delete(ingAnnotation2Class.Annotations, ingressclass.IngressKey)
ingAnnotation2Class.Spec.IngressClassName = framework.GetIngressClassName(ingAnnotation2Class.Namespace)
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ingAnnotation2Class.Namespace).Update(context.TODO(), ingAnnotation2Class, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ingClass2Annotation, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), hostClass2Annotation, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ingClass2Annotation.Spec.IngressClassName = nil
ingClass2Annotation.Annotations = annotationClass
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ingClass2Annotation.Namespace).Update(context.TODO(), ingClass2Annotation, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
framework.Sleep()
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name foo-annotation2class") &&
strings.Contains(cfg, "server_name foo-class2annotation")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostAnnotation2class).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", hostClass2Annotation).
Expect().
Status(http.StatusOK)
})
})
ginkgo.Context("With specific ingress-class flags", func() {
ginkgo.BeforeEach(func() { ginkgo.BeforeEach(func() {
err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error { err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error {
args := []string{} args := []string{}
for _, v := range deployment.Spec.Template.Spec.Containers[0].Args { for _, v := range deployment.Spec.Template.Spec.Containers[0].Args {
if strings.Contains(v, "--ingress-class") { if strings.Contains(v, "--ingress-class") && strings.Contains(v, "--controller-class") {
continue continue
} }
@ -128,6 +398,7 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
} }
args = append(args, "--ingress-class=testclass") args = append(args, "--ingress-class=testclass")
args = append(args, "--controller-class=k8s.io/other-class")
deployment.Spec.Template.Spec.Containers[0].Args = args deployment.Spec.Template.Spec.Containers[0].Args = args
_, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{}) _, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{})
@ -136,25 +407,93 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags") assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags")
}) })
ginkgo.It("should ignore Ingress with no class", func() { ginkgo.It("should ignore Ingress with no class and accept the correctly configured Ingresses", func() {
invalidHost := "bar" invalidHost := "bar"
ing := framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, nil) ing := framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing) f.EnsureIngress(ing)
validHost := "foo" validHost := "foo"
annotations := map[string]string{ annotations := map[string]string{
class.IngressKey: "testclass", ingressclass.IngressKey: "testclass",
} }
ing = framework.NewSingleIngress(validHost, "/", validHost, f.Namespace, framework.EchoService, 80, annotations) ing = framework.NewSingleIngress(validHost, "/", validHost, f.Namespace, framework.EchoService, 80, annotations)
// Delete the IngressClass as we want just the annotation here
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing) f.EnsureIngress(ing)
f.WaitForNginxServer(validHost, func(cfg string) bool { validHostClass := "foobar123"
return strings.Contains(cfg, "server_name foo") ing = framework.NewSingleIngress(validHostClass, "/", validHostClass, f.Namespace, framework.EchoService, 80, nil)
}) ing.Spec.IngressClassName = &otherIngressClassName
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool { f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name bar") return !strings.Contains(cfg, "server_name bar") &&
strings.Contains(cfg, "server_name foo") &&
strings.Contains(cfg, "server_name foobar123")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", validHost).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", validHostClass).
Expect().
Status(http.StatusOK)
f.HTTPTestClient().
GET("/").
WithHeader("Host", invalidHost).
Expect().
Status(http.StatusNotFound)
})
})
ginkgo.Context("With watch-ingress-without-class flag", func() {
ginkgo.BeforeEach(func() {
err := f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error {
args := []string{}
for _, v := range deployment.Spec.Template.Spec.Containers[0].Args {
if strings.Contains(v, "--watch-ingress-without-class") && strings.Contains(v, "--controller-class") {
continue
}
args = append(args, v)
}
args = append(args, "--watch-ingress-without-class")
deployment.Spec.Template.Spec.Containers[0].Args = args
_, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{})
return err
})
assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags")
})
ginkgo.It("should watch Ingress with no class and ignore ingress with a different class", func() {
validHost := "bar"
ing := framework.NewSingleIngress(validHost, "/", validHost, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
invalidHost := "foo"
annotations := map[string]string{
ingressclass.IngressKey: "testclass123",
}
ing = framework.NewSingleIngress(invalidHost, "/", invalidHost, f.Namespace, framework.EchoService, 80, annotations)
ing.Spec.IngressClassName = nil
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, "server_name bar") &&
!strings.Contains(cfg, "server_name foo")
}) })
f.HTTPTestClient(). f.HTTPTestClient().
@ -170,165 +509,5 @@ var _ = framework.IngressNginxDescribe("[Flag] ingress-class", func() {
Status(http.StatusNotFound) Status(http.StatusNotFound)
}) })
ginkgo.It("should delete Ingress when class is removed", func() {
host := "foo"
annotations := map[string]string{
class.IngressKey: "testclass",
}
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
f.EnsureIngress(ing)
f.WaitForNginxServer(host, func(cfg string) bool {
return strings.Contains(cfg, "server_name foo")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK)
ing, err := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), host, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
delete(ing.Annotations, class.IngressKey)
_, err = f.KubeClientSet.NetworkingV1().Ingresses(ing.Namespace).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
framework.Sleep()
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, "server_name foo")
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusNotFound)
})
})
ginkgo.It("check scenarios for IngressClass and ingress.class annotation", func() {
pod := f.GetIngressNGINXPod()
crb, err := f.KubeClientSet.RbacV1().ClusterRoleBindings().Get(context.Background(), "ingress-nginx-class", metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "searching cluster role binding")
// add service of current namespace
crb.Subjects = append(crb.Subjects, rbacv1.Subject{
APIGroup: "",
Kind: "ServiceAccount",
Name: pod.Spec.ServiceAccountName,
Namespace: f.Namespace,
})
_, err = f.KubeClientSet.RbacV1().ClusterRoleBindings().Update(context.Background(), crb, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "searching cluster role binding")
err = f.UpdateIngressControllerDeployment(func(deployment *appsv1.Deployment) error {
args := []string{}
for _, v := range deployment.Spec.Template.Spec.Containers[0].Args {
if strings.Contains(v, "--ingress-class") {
continue
}
args = append(args, v)
}
args = append(args, fmt.Sprintf("--ingress-class=%v", testIngressClassName))
deployment.Spec.Template.Spec.Containers[0].Args = args
_, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), deployment, metav1.UpdateOptions{})
return err
})
assert.Nil(ginkgo.GinkgoT(), err, "updating ingress controller deployment flags")
host := "ingress.class"
ginkgo.By("only having IngressClassName")
ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, nil)
ing.Spec.IngressClassName = &testIngressClassName
f.EnsureIngress(ing)
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, fmt.Sprintf("server_name %v", host))
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK)
ginkgo.By("only having ingress.class annotation")
ing, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), host, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ing.Annotations = map[string]string{
class.IngressKey: testIngressClassName,
}
ing.Spec.IngressClassName = nil
_, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
f.WaitForNginxConfiguration(func(cfg string) bool {
return strings.Contains(cfg, fmt.Sprintf("server_name %v", host))
})
framework.Sleep()
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusOK)
ginkgo.By("having an invalid ingress.class annotation and no IngressClassName")
ing, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), host, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ing.Annotations = map[string]string{
class.IngressKey: "invalid",
}
ing.Spec.IngressClassName = nil
_, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
framework.Sleep()
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, fmt.Sprintf("server_name %v", host))
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusNotFound)
ginkgo.By("not having ingress.class annotation and invalid IngressClassName")
ing, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Get(context.TODO(), host, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
ing.Annotations = map[string]string{}
invalidClassName := "invalidclass"
ing.Spec.IngressClassName = &invalidClassName
_, err = f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace).Update(context.TODO(), ing, metav1.UpdateOptions{})
assert.Nil(ginkgo.GinkgoT(), err)
framework.Sleep()
f.WaitForNginxConfiguration(func(cfg string) bool {
return !strings.Contains(cfg, fmt.Sprintf("server_name %v", host))
})
f.HTTPTestClient().
GET("/").
WithHeader("Host", host).
Expect().
Status(http.StatusNotFound)
}) })
}) })

View file

@ -106,6 +106,7 @@ func buildBasicAuthIngressWithSecondPath(host, namespace, secretName, pathName s
}, },
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: framework.GetIngressClassName(namespace),
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: host, Host: host,

View file

@ -57,6 +57,7 @@ var _ = framework.DescribeSetting("server-tokens", func() {
Annotations: map[string]string{}, Annotations: map[string]string{},
}, },
Spec: networking.IngressSpec{ Spec: networking.IngressSpec{
IngressClassName: &f.IngressClass,
Rules: []networking.IngressRule{ Rules: []networking.IngressRule{
{ {
Host: serverTokens, Host: serverTokens,

View file

@ -94,7 +94,7 @@ var _ = framework.IngressNginxDescribe("[Status] status update", func() {
err = f.KubeClientSet.CoreV1(). err = f.KubeClientSet.CoreV1().
ConfigMaps(f.Namespace). ConfigMaps(f.Namespace).
Delete(context.TODO(), "ingress-controller-leader-nginx", metav1.DeleteOptions{}) Delete(context.TODO(), "ingress-controller-leader", metav1.DeleteOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "unexpected error deleting leader election configmap") assert.Nil(ginkgo.GinkgoT(), err, "unexpected error deleting leader election configmap")
_, cmd, err = f.KubectlProxy(port) _, cmd, err = f.KubectlProxy(port)

View file

@ -73,6 +73,10 @@ controller:
periodSeconds: 1 periodSeconds: 1
service: service:
type: NodePort type: NodePort
electionID: ingress-controller-leader
ingressClassResource:
# We will create and remove each IC/ClusterRole/ClusterRoleBinding per test so there's no conflict
enabled: false
extraArgs: extraArgs:
tcp-services-configmap: $NAMESPACE/tcp-services tcp-services-configmap: $NAMESPACE/tcp-services
# e2e tests do not require information about ingress status # e2e tests do not require information about ingress status