Add admission controller e2e test
This commit is contained in:
parent
4e3e5ebb94
commit
7722fa38aa
15 changed files with 295 additions and 53 deletions
|
@ -24,7 +24,7 @@ webhooks:
|
||||||
failurePolicy: Fail
|
failurePolicy: Fail
|
||||||
sideEffects: None
|
sideEffects: None
|
||||||
admissionReviewVersions:
|
admissionReviewVersions:
|
||||||
- v1beta1
|
- v1
|
||||||
clientConfig:
|
clientConfig:
|
||||||
service:
|
service:
|
||||||
namespace: {{ .Release.Namespace }}
|
namespace: {{ .Release.Namespace }}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package controller
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
networking "k8s.io/api/networking/v1beta1"
|
networking "k8s.io/api/networking/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
@ -56,9 +56,9 @@ var (
|
||||||
// HandleAdmission populates the admission Response
|
// HandleAdmission populates the admission Response
|
||||||
// with Allowed=false if the Object is an ingress that would prevent nginx to reload the configuration
|
// with Allowed=false if the Object is an ingress that would prevent nginx to reload the configuration
|
||||||
// with Allowed=true otherwise
|
// with Allowed=true otherwise
|
||||||
func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) {
|
func (ia *IngressAdmission) HandleAdmission(ar *admissionv1.AdmissionReview) {
|
||||||
if ar.Request == nil {
|
if ar.Request == nil {
|
||||||
ar.Response = &v1beta1.AdmissionResponse{
|
ar.Response = &admissionv1.AdmissionResponse{
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) {
|
||||||
if ar.Request.Resource != networkingV1Beta1Resource && ar.Request.Resource != networkingV1Resource {
|
if ar.Request.Resource != networkingV1Beta1Resource && ar.Request.Resource != networkingV1Resource {
|
||||||
err := fmt.Errorf("rejecting admission review because the request does not contains an Ingress resource but %s with name %s in namespace %s",
|
err := fmt.Errorf("rejecting admission review because the request does not contains an Ingress resource but %s with name %s in namespace %s",
|
||||||
ar.Request.Resource.String(), ar.Request.Name, ar.Request.Namespace)
|
ar.Request.Resource.String(), ar.Request.Name, ar.Request.Namespace)
|
||||||
ar.Response = &v1beta1.AdmissionResponse{
|
ar.Response = &admissionv1.AdmissionResponse{
|
||||||
UID: ar.Request.UID,
|
UID: ar.Request.UID,
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
Result: &metav1.Status{Message: err.Error()},
|
Result: &metav1.Status{Message: err.Error()},
|
||||||
|
@ -83,7 +83,7 @@ func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) {
|
||||||
klog.Errorf("failed to decode ingress %s in namespace %s: %s, refusing it",
|
klog.Errorf("failed to decode ingress %s in namespace %s: %s, refusing it",
|
||||||
ar.Request.Name, ar.Request.Namespace, err.Error())
|
ar.Request.Name, ar.Request.Namespace, err.Error())
|
||||||
|
|
||||||
ar.Response = &v1beta1.AdmissionResponse{
|
ar.Response = &admissionv1.AdmissionResponse{
|
||||||
UID: ar.Request.UID,
|
UID: ar.Request.UID,
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) {
|
||||||
if err := ia.Checker.CheckIngress(&ingress); err != nil {
|
if err := ia.Checker.CheckIngress(&ingress); err != nil {
|
||||||
klog.Errorf("failed to generate configuration for ingress %s in namespace %s: %s, refusing it",
|
klog.Errorf("failed to generate configuration for ingress %s in namespace %s: %s, refusing it",
|
||||||
ar.Request.Name, ar.Request.Namespace, err.Error())
|
ar.Request.Name, ar.Request.Namespace, err.Error())
|
||||||
ar.Response = &v1beta1.AdmissionResponse{
|
ar.Response = &admissionv1.AdmissionResponse{
|
||||||
UID: ar.Request.UID,
|
UID: ar.Request.UID,
|
||||||
Allowed: false,
|
Allowed: false,
|
||||||
Result: &metav1.Status{Message: err.Error()},
|
Result: &metav1.Status{Message: err.Error()},
|
||||||
|
@ -113,7 +113,7 @@ func (ia *IngressAdmission) HandleAdmission(ar *v1beta1.AdmissionReview) {
|
||||||
|
|
||||||
klog.Infof("successfully validated configuration, accepting ingress %s in namespace %s",
|
klog.Infof("successfully validated configuration, accepting ingress %s in namespace %s",
|
||||||
ar.Request.Name, ar.Request.Namespace)
|
ar.Request.Name, ar.Request.Namespace)
|
||||||
ar.Response = &v1beta1.AdmissionResponse{
|
ar.Response = &admissionv1.AdmissionResponse{
|
||||||
UID: ar.Request.UID,
|
UID: ar.Request.UID,
|
||||||
Allowed: true,
|
Allowed: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
networking "k8s.io/api/networking/v1beta1"
|
networking "k8s.io/api/networking/v1beta1"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
|
@ -53,8 +53,8 @@ func TestHandleAdmission(t *testing.T) {
|
||||||
adm := &IngressAdmission{
|
adm := &IngressAdmission{
|
||||||
Checker: failTestChecker{t: t},
|
Checker: failTestChecker{t: t},
|
||||||
}
|
}
|
||||||
review := &v1beta1.AdmissionReview{
|
review := &admissionv1.AdmissionReview{
|
||||||
Request: &v1beta1.AdmissionRequest{
|
Request: &admissionv1.AdmissionRequest{
|
||||||
Resource: v1.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"},
|
Resource: v1.GroupVersionResource{Group: "", Version: "v1", Resource: "pod"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/util/json"
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
|
@ -36,7 +36,7 @@ var (
|
||||||
// AdmissionController checks if an object
|
// AdmissionController checks if an object
|
||||||
// is allowed in the cluster
|
// is allowed in the cluster
|
||||||
type AdmissionController interface {
|
type AdmissionController interface {
|
||||||
HandleAdmission(*v1beta1.AdmissionReview)
|
HandleAdmission(*admissionv1.AdmissionReview)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdmissionControllerServer implements an HTTP server
|
// AdmissionControllerServer implements an HTTP server
|
||||||
|
@ -71,8 +71,8 @@ func (acs *AdmissionControllerServer) ServeHTTP(w http.ResponseWriter, r *http.R
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdmissionReview(decoder runtime.Decoder, r io.Reader) (*v1beta1.AdmissionReview, error) {
|
func parseAdmissionReview(decoder runtime.Decoder, r io.Reader) (*admissionv1.AdmissionReview, error) {
|
||||||
review := &v1beta1.AdmissionReview{}
|
review := &admissionv1.AdmissionReview{}
|
||||||
data, err := ioutil.ReadAll(r)
|
data, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -81,7 +81,7 @@ func parseAdmissionReview(decoder runtime.Decoder, r io.Reader) (*v1beta1.Admiss
|
||||||
return review, err
|
return review, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAdmissionReview(w io.Writer, ar *v1beta1.AdmissionReview) error {
|
func writeAdmissionReview(w io.Writer, ar *admissionv1.AdmissionReview) error {
|
||||||
e := json.NewEncoder(w)
|
e := json.NewEncoder(w)
|
||||||
return e.Encode(ar)
|
return e.Encode(ar)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/admission/v1beta1"
|
admissionv1 "k8s.io/api/admission/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testAdmissionHandler struct{}
|
type testAdmissionHandler struct{}
|
||||||
|
|
||||||
func (testAdmissionHandler) HandleAdmission(ar *v1beta1.AdmissionReview) {
|
func (testAdmissionHandler) HandleAdmission(ar *admissionv1.AdmissionReview) {
|
||||||
ar.Response = &v1beta1.AdmissionResponse{
|
ar.Response = &admissionv1.AdmissionResponse{
|
||||||
Allowed: true,
|
Allowed: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func (errorWriter) WriteHeader(statusCode int) {}
|
||||||
func TestServer(t *testing.T) {
|
func TestServer(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
b := bytes.NewBuffer(nil)
|
b := bytes.NewBuffer(nil)
|
||||||
writeAdmissionReview(b, &v1beta1.AdmissionReview{})
|
writeAdmissionReview(b, &admissionv1.AdmissionReview{})
|
||||||
|
|
||||||
// Happy path
|
// Happy path
|
||||||
r := httptest.NewRequest("GET", "http://test.ns.svc", b)
|
r := httptest.NewRequest("GET", "http://test.ns.svc", b)
|
||||||
|
|
|
@ -38,6 +38,8 @@ import (
|
||||||
"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/store"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress/errors"
|
||||||
"k8s.io/ingress-nginx/internal/k8s"
|
"k8s.io/ingress-nginx/internal/k8s"
|
||||||
"k8s.io/ingress-nginx/internal/nginx"
|
"k8s.io/ingress-nginx/internal/nginx"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
@ -126,7 +128,7 @@ func (n *NGINXController) syncIngress(interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ings := n.store.ListIngresses(nil)
|
ings := n.store.ListIngresses()
|
||||||
hosts, servers, pcfg := n.getConfiguration(ings)
|
hosts, servers, pcfg := n.getConfiguration(ings)
|
||||||
|
|
||||||
n.metricCollector.SetSSLExpireTime(servers)
|
n.metricCollector.SetSSLExpireTime(servers)
|
||||||
|
@ -225,24 +227,31 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
k8s.SetDefaultNGINXPathType(ing)
|
||||||
|
|
||||||
|
allIngresses := n.store.ListIngresses()
|
||||||
|
|
||||||
filter := func(toCheck *ingress.Ingress) bool {
|
filter := func(toCheck *ingress.Ingress) bool {
|
||||||
return toCheck.ObjectMeta.Namespace == ing.ObjectMeta.Namespace &&
|
return toCheck.ObjectMeta.Namespace == ing.ObjectMeta.Namespace &&
|
||||||
toCheck.ObjectMeta.Name == ing.ObjectMeta.Name
|
toCheck.ObjectMeta.Name == ing.ObjectMeta.Name
|
||||||
}
|
}
|
||||||
|
ings := store.FilterIngresses(allIngresses, filter)
|
||||||
k8s.SetDefaultNGINXPathType(ing)
|
|
||||||
|
|
||||||
ings := n.store.ListIngresses(filter)
|
|
||||||
ings = append(ings, &ingress.Ingress{
|
ings = append(ings, &ingress.Ingress{
|
||||||
Ingress: *ing,
|
Ingress: *ing,
|
||||||
ParsedAnnotations: annotations.NewAnnotationExtractor(n.store).Extract(ing),
|
ParsedAnnotations: annotations.NewAnnotationExtractor(n.store).Extract(ing),
|
||||||
})
|
})
|
||||||
|
|
||||||
_, _, pcfg := n.getConfiguration(ings)
|
|
||||||
|
|
||||||
cfg := n.store.GetBackendConfiguration()
|
cfg := n.store.GetBackendConfiguration()
|
||||||
cfg.Resolver = n.resolver
|
cfg.Resolver = n.resolver
|
||||||
|
|
||||||
|
_, servers, pcfg := n.getConfiguration(ings)
|
||||||
|
|
||||||
|
err := checkOverlap(ing, allIngresses, servers)
|
||||||
|
if err != nil {
|
||||||
|
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
content, err := n.generateTemplate(cfg, *pcfg)
|
content, err := n.generateTemplate(cfg, *pcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
|
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
|
||||||
|
@ -252,11 +261,11 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
|
||||||
err = n.testTemplate(content)
|
err = n.testTemplate(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
|
n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
|
||||||
} else {
|
return err
|
||||||
n.metricCollector.IncCheckCount(ing.ObjectMeta.Namespace, ing.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
n.metricCollector.IncCheckCount(ing.ObjectMeta.Namespace, ing.Name)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service {
|
func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service {
|
||||||
|
@ -1519,3 +1528,79 @@ func externalNamePorts(name string, svc *apiv1.Service) *apiv1.ServicePort {
|
||||||
TargetPort: intstr.FromInt(port),
|
TargetPort: intstr.FromInt(port),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkOverlap(ing *networking.Ingress, ingresses []*ingress.Ingress, servers []*ingress.Server) error {
|
||||||
|
for _, rule := range ing.Spec.Rules {
|
||||||
|
if rule.HTTP == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.Host == "" {
|
||||||
|
rule.Host = defServerName
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range rule.HTTP.Paths {
|
||||||
|
if path.Path == "" {
|
||||||
|
path.Path = rootLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
existingIngresses := ingressForHostPath(rule.Host, path.Path, servers)
|
||||||
|
|
||||||
|
// no previous ingress
|
||||||
|
if len(existingIngresses) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// same ingress
|
||||||
|
skipValidation := false
|
||||||
|
for _, existing := range existingIngresses {
|
||||||
|
if existing.ObjectMeta.Namespace == ing.ObjectMeta.Namespace && existing.ObjectMeta.Name == ing.ObjectMeta.Name {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if skipValidation {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// path overlap. Check if one of the ingresses has a canary annotation
|
||||||
|
isCanaryEnabled, annotationErr := parser.GetBoolAnnotation("canary", ing)
|
||||||
|
for _, existing := range existingIngresses {
|
||||||
|
isExistingCanaryEnabled, existingAnnotationErr := parser.GetBoolAnnotation("canary", existing)
|
||||||
|
|
||||||
|
if isCanaryEnabled && isExistingCanaryEnabled {
|
||||||
|
return fmt.Errorf(`host "%s" and path "%s" is already defined in ingress %s/%s`, rule.Host, path.Path, existing.Namespace, existing.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if annotationErr == errors.ErrMissingAnnotations && existingAnnotationErr == existingAnnotationErr {
|
||||||
|
return fmt.Errorf(`host "%s" and path "%s" is already defined in ingress %s/%s`, rule.Host, path.Path, existing.Namespace, existing.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no overlap
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ingressForHostPath(hostname, path string, servers []*ingress.Server) []*networking.Ingress {
|
||||||
|
ingresses := make([]*networking.Ingress, 0)
|
||||||
|
|
||||||
|
for _, server := range servers {
|
||||||
|
if hostname != server.Hostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range server.Locations {
|
||||||
|
if location.Path != path {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ingresses = append(ingresses, &location.Ingress.Ingress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ingresses
|
||||||
|
}
|
||||||
|
|
|
@ -78,10 +78,14 @@ func (fakeIngressStore) GetServiceEndpoints(key string) (*corev1.Endpoints, erro
|
||||||
return nil, fmt.Errorf("test error")
|
return nil, fmt.Errorf("test error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fis fakeIngressStore) ListIngresses(store.IngressFilterFunc) []*ingress.Ingress {
|
func (fis fakeIngressStore) ListIngresses() []*ingress.Ingress {
|
||||||
return fis.ingresses
|
return fis.ingresses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fis fakeIngressStore) FilterIngresses(ingresses []*ingress.Ingress, filterFunc store.IngressFilterFunc) []*ingress.Ingress {
|
||||||
|
return ingresses
|
||||||
|
}
|
||||||
|
|
||||||
func (fakeIngressStore) GetRunningControllerPodsCount() int {
|
func (fakeIngressStore) GetRunningControllerPodsCount() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ package store
|
||||||
import (
|
import (
|
||||||
networking "k8s.io/api/networking/v1beta1"
|
networking "k8s.io/api/networking/v1beta1"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/ingress-nginx/internal/ingress"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IngressLister makes a Store that lists Ingress.
|
// IngressLister makes a Store that lists Ingress.
|
||||||
|
@ -37,3 +38,16 @@ func (il IngressLister) ByKey(key string) (*networking.Ingress, error) {
|
||||||
}
|
}
|
||||||
return i.(*networking.Ingress), nil
|
return i.(*networking.Ingress), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilterIngresses returns the list of Ingresses
|
||||||
|
func FilterIngresses(ingresses []*ingress.Ingress, filterFunc IngressFilterFunc) []*ingress.Ingress {
|
||||||
|
afterFilter := make([]*ingress.Ingress, 0)
|
||||||
|
for _, ingress := range ingresses {
|
||||||
|
if !filterFunc(ingress) {
|
||||||
|
afterFilter = append(afterFilter, ingress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortIngressSlice(afterFilter)
|
||||||
|
return afterFilter
|
||||||
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ type Storer interface {
|
||||||
GetServiceEndpoints(key string) (*corev1.Endpoints, error)
|
GetServiceEndpoints(key string) (*corev1.Endpoints, error)
|
||||||
|
|
||||||
// ListIngresses returns a list of all Ingresses in the store.
|
// ListIngresses returns a list of all Ingresses in the store.
|
||||||
ListIngresses(IngressFilterFunc) []*ingress.Ingress
|
ListIngresses() []*ingress.Ingress
|
||||||
|
|
||||||
// GetRunningControllerPodsCount returns the number of Running ingress-nginx controller Pods.
|
// GetRunningControllerPodsCount returns the number of Running ingress-nginx controller Pods.
|
||||||
GetRunningControllerPodsCount() int
|
GetRunningControllerPodsCount() int
|
||||||
|
@ -804,20 +804,7 @@ func (s *k8sStore) getIngress(key string) (*networkingv1beta1.Ingress, error) {
|
||||||
return &ing.Ingress, nil
|
return &ing.Ingress, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListIngresses returns the list of Ingresses
|
func sortIngressSlice(ingresses []*ingress.Ingress) {
|
||||||
func (s *k8sStore) ListIngresses(filter IngressFilterFunc) []*ingress.Ingress {
|
|
||||||
// filter ingress rules
|
|
||||||
ingresses := make([]*ingress.Ingress, 0)
|
|
||||||
for _, item := range s.listers.IngressWithAnnotation.List() {
|
|
||||||
ing := item.(*ingress.Ingress)
|
|
||||||
|
|
||||||
if filter != nil && filter(ing) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ingresses = append(ingresses, ing)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort Ingresses using the CreationTimestamp field
|
// sort Ingresses using the CreationTimestamp field
|
||||||
sort.SliceStable(ingresses, func(i, j int) bool {
|
sort.SliceStable(ingresses, func(i, j int) bool {
|
||||||
ir := ingresses[i].CreationTimestamp
|
ir := ingresses[i].CreationTimestamp
|
||||||
|
@ -830,6 +817,18 @@ func (s *k8sStore) ListIngresses(filter IngressFilterFunc) []*ingress.Ingress {
|
||||||
}
|
}
|
||||||
return ir.Before(&jr)
|
return ir.Before(&jr)
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIngresses returns the list of Ingresses
|
||||||
|
func (s *k8sStore) ListIngresses() []*ingress.Ingress {
|
||||||
|
// filter ingress rules
|
||||||
|
ingresses := make([]*ingress.Ingress, 0)
|
||||||
|
for _, item := range s.listers.IngressWithAnnotation.List() {
|
||||||
|
ing := item.(*ingress.Ingress)
|
||||||
|
ingresses = append(ingresses, ing)
|
||||||
|
}
|
||||||
|
|
||||||
|
sortIngressSlice(ingresses)
|
||||||
|
|
||||||
return ingresses
|
return ingresses
|
||||||
}
|
}
|
||||||
|
|
|
@ -952,7 +952,7 @@ func TestListIngresses(t *testing.T) {
|
||||||
}
|
}
|
||||||
s.listers.IngressWithAnnotation.Add(ingressWithNginxClass)
|
s.listers.IngressWithAnnotation.Add(ingressWithNginxClass)
|
||||||
|
|
||||||
ingresses := s.ListIngresses(nil)
|
ingresses := s.ListIngresses()
|
||||||
|
|
||||||
if s := len(ingresses); s != 3 {
|
if s := len(ingresses); s != 3 {
|
||||||
t.Errorf("Expected 3 Ingresses but got %v", s)
|
t.Errorf("Expected 3 Ingresses but got %v", s)
|
||||||
|
|
|
@ -37,7 +37,6 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
|
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/internal/ingress"
|
"k8s.io/ingress-nginx/internal/ingress"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/controller/store"
|
|
||||||
"k8s.io/ingress-nginx/internal/k8s"
|
"k8s.io/ingress-nginx/internal/k8s"
|
||||||
"k8s.io/ingress-nginx/internal/task"
|
"k8s.io/ingress-nginx/internal/task"
|
||||||
)
|
)
|
||||||
|
@ -55,7 +54,7 @@ type Syncer interface {
|
||||||
|
|
||||||
type ingressLister interface {
|
type ingressLister interface {
|
||||||
// ListIngresses returns the list of Ingresses
|
// ListIngresses returns the list of Ingresses
|
||||||
ListIngresses(store.IngressFilterFunc) []*ingress.Ingress
|
ListIngresses() []*ingress.Ingress
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config ...
|
// Config ...
|
||||||
|
@ -236,7 +235,7 @@ func sliceToStatus(endpoints []string) []apiv1.LoadBalancerIngress {
|
||||||
|
|
||||||
// updateStatus changes the status information of Ingress rules
|
// updateStatus changes the status information of Ingress rules
|
||||||
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
||||||
ings := s.IngressLister.ListIngresses(nil)
|
ings := s.IngressLister.ListIngresses()
|
||||||
|
|
||||||
p := pool.NewLimited(10)
|
p := pool.NewLimited(10)
|
||||||
defer p.Close()
|
defer p.Close()
|
||||||
|
|
|
@ -30,7 +30,6 @@ import (
|
||||||
|
|
||||||
"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/class"
|
||||||
"k8s.io/ingress-nginx/internal/ingress/controller/store"
|
|
||||||
"k8s.io/ingress-nginx/internal/k8s"
|
"k8s.io/ingress-nginx/internal/k8s"
|
||||||
"k8s.io/ingress-nginx/internal/task"
|
"k8s.io/ingress-nginx/internal/task"
|
||||||
)
|
)
|
||||||
|
@ -234,7 +233,7 @@ func buildExtensionsIngresses() []networking.Ingress {
|
||||||
type testIngressLister struct {
|
type testIngressLister struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (til *testIngressLister) ListIngresses(store.IngressFilterFunc) []*ingress.Ingress {
|
func (til *testIngressLister) ListIngresses() []*ingress.Ingress {
|
||||||
var ingresses []*ingress.Ingress
|
var ingresses []*ingress.Ingress
|
||||||
ingresses = append(ingresses, &ingress.Ingress{
|
ingresses = append(ingresses, &ingress.Ingress{
|
||||||
Ingress: networking.Ingress{
|
Ingress: networking.Ingress{
|
||||||
|
|
34
test/e2e-image/namespace-overlays/admission/values.yaml
Normal file
34
test/e2e-image/namespace-overlays/admission/values.yaml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# TODO: remove the need to use fullnameOverride
|
||||||
|
fullnameOverride: nginx-ingress
|
||||||
|
controller:
|
||||||
|
image:
|
||||||
|
repository: ingress-controller/controller
|
||||||
|
tag: 1.0.0-dev
|
||||||
|
digest:
|
||||||
|
containerPort:
|
||||||
|
http: "1080"
|
||||||
|
https: "1443"
|
||||||
|
|
||||||
|
extraArgs:
|
||||||
|
http-port: "1080"
|
||||||
|
https-port: "1443"
|
||||||
|
# e2e tests do not require information about ingress status
|
||||||
|
update-status: "false"
|
||||||
|
|
||||||
|
scope:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
config:
|
||||||
|
worker-processes: "1"
|
||||||
|
service:
|
||||||
|
type: NodePort
|
||||||
|
|
||||||
|
admissionWebhooks:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
defaultBackend:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
rbac:
|
||||||
|
create: true
|
||||||
|
scope: true
|
108
test/e2e/admission/admission.go
Normal file
108
test/e2e/admission/admission.go
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 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 admission
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/onsi/ginkgo"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = framework.IngressNginxDescribe("[Serial] admission controller", func() {
|
||||||
|
f := framework.NewDefaultFramework("admission")
|
||||||
|
|
||||||
|
ginkgo.BeforeEach(func() {
|
||||||
|
f.NewEchoDeployment()
|
||||||
|
f.NewSlowEchoDeployment()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should not allow overlaps of host and paths without canary annotations", func() {
|
||||||
|
host := "admission-test"
|
||||||
|
|
||||||
|
firstIngress := framework.NewSingleIngress("first-ingress", "/", host, f.Namespace, framework.EchoService, 80, nil)
|
||||||
|
_, err := f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace).Create(context.TODO(), firstIngress, metav1.CreateOptions{})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return strings.Contains(server, fmt.Sprintf("server_name %v", host))
|
||||||
|
})
|
||||||
|
|
||||||
|
secondIngress := framework.NewSingleIngress("second-ingress", "/", host, f.Namespace, framework.EchoService, 80, nil)
|
||||||
|
_, err = f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace).Create(context.TODO(), secondIngress, metav1.CreateOptions{})
|
||||||
|
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with the same host and path should return an error")
|
||||||
|
|
||||||
|
err = uninstallChart(f)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "uninstalling helm chart")
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should allow overlaps of host and paths with canary annotation", func() {
|
||||||
|
host := "admission-test"
|
||||||
|
|
||||||
|
firstIngress := framework.NewSingleIngress("first-ingress", "/", host, f.Namespace, framework.EchoService, 80, nil)
|
||||||
|
_, err := f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace).Create(context.TODO(), firstIngress, metav1.CreateOptions{})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
|
||||||
|
|
||||||
|
f.WaitForNginxServer(host,
|
||||||
|
func(server string) bool {
|
||||||
|
return strings.Contains(server, fmt.Sprintf("server_name %v", host))
|
||||||
|
})
|
||||||
|
|
||||||
|
canaryAnnotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/canary": "true",
|
||||||
|
"nginx.ingress.kubernetes.io/canary-by-header": "CanaryByHeader",
|
||||||
|
}
|
||||||
|
secondIngress := framework.NewSingleIngress("second-ingress", "/", host, f.Namespace, framework.SlowEchoService, 80, canaryAnnotations)
|
||||||
|
_, err = f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace).Create(context.TODO(), secondIngress, metav1.CreateOptions{})
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "creating an ingress with the same host and path should not return an error using a canary annotation")
|
||||||
|
|
||||||
|
err = uninstallChart(f)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "uninstalling helm chart")
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("should return an error if there is an error validating the ingress definition", func() {
|
||||||
|
host := "admission-test"
|
||||||
|
|
||||||
|
annotations := map[string]string{
|
||||||
|
"nginx.ingress.kubernetes.io/configuration-snippet": "something invalid",
|
||||||
|
}
|
||||||
|
firstIngress := framework.NewSingleIngress("first-ingress", "/", host, f.Namespace, framework.EchoService, 80, annotations)
|
||||||
|
_, err := f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace).Create(context.TODO(), firstIngress, metav1.CreateOptions{})
|
||||||
|
assert.NotNil(ginkgo.GinkgoT(), err, "creating an ingress with invalid configuration should return an error")
|
||||||
|
|
||||||
|
err = uninstallChart(f)
|
||||||
|
assert.Nil(ginkgo.GinkgoT(), err, "uninstalling helm chart")
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
func uninstallChart(f *framework.Framework) error {
|
||||||
|
cmd := exec.Command("helm", "uninstall", "--namespace", f.Namespace, "nginx-ingress")
|
||||||
|
_, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unexpected error uninstalling ingress-nginx release: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -25,12 +25,12 @@ import (
|
||||||
"k8s.io/component-base/logs"
|
"k8s.io/component-base/logs"
|
||||||
|
|
||||||
// required
|
// required
|
||||||
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/test/e2e/framework"
|
"k8s.io/ingress-nginx/test/e2e/framework"
|
||||||
|
|
||||||
// tests to run
|
// tests to run
|
||||||
|
_ "k8s.io/ingress-nginx/test/e2e/admission"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/annotations"
|
_ "k8s.io/ingress-nginx/test/e2e/annotations"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/dbg"
|
_ "k8s.io/ingress-nginx/test/e2e/dbg"
|
||||||
_ "k8s.io/ingress-nginx/test/e2e/defaultbackend"
|
_ "k8s.io/ingress-nginx/test/e2e/defaultbackend"
|
||||||
|
|
Loading…
Reference in a new issue