Skip to content

Commit

Permalink
support for ingressv1.Spec.IngressClassName which allows for support …
Browse files Browse the repository at this point in the history
…for ingress.class not in an annotation

Signed-off-by: Steve Sloka <[email protected]>
  • Loading branch information
stevesloka committed May 21, 2020
1 parent faf7340 commit e90e82a
Show file tree
Hide file tree
Showing 16 changed files with 413 additions and 59 deletions.
13 changes: 9 additions & 4 deletions cmd/contour/ingressstatus.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"strings"
"sync"

"github.com/projectcontour/contour/internal/annotation"

"github.com/projectcontour/contour/internal/k8s"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -88,10 +90,13 @@ func (isw *loadBalancerStatusWriter) Start(stop <-chan struct{}) error {
factory := isw.clients.NewInformerFactory()
inf := factory.Networking().V1beta1().Ingresses().Informer()
inf.AddEventHandler(&k8s.IngressStatusUpdater{
Client: isw.clients.ClientSet(),
Logger: log,
Status: lbs,
IngressClass: isw.ingressClass,
Client: isw.clients.ClientSet(),
Logger: log,
Status: lbs,
Annotation: annotation.Annotation{
SystemIngressClass: isw.ingressClass,
FieldLogger: log.WithField("context", "Annotation"),
},
})

shutdown = make(chan struct{})
Expand Down
7 changes: 5 additions & 2 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,11 @@ func doServe(log logrus.FieldLogger, ctx *serveContext) error {
Builder: dag.Builder{
Source: dag.KubernetesCache{
RootNamespaces: ctx.ingressRouteRootNamespaces(),
IngressClass: ctx.ingressClass,
FieldLogger: log.WithField("context", "KubernetesCache"),
Annotation: annotation.Annotation{
SystemIngressClass: ctx.ingressClass,
FieldLogger: log.WithField("context", "Annotation"),
},
FieldLogger: log.WithField("context", "KubernetesCache"),
},
DisablePermitInsecure: ctx.DisablePermitInsecure,
},
Expand Down
14 changes: 5 additions & 9 deletions internal/annotation/annotations.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2019 VMware
// Copyright © 2020 VMware
// 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
Expand Down Expand Up @@ -195,8 +195,7 @@ func PerTryTimeout(i *v1beta1.Ingress) time.Duration {
// 1. projectcontour.io/ingress.class
// 2. contour.heptio.com/ingress.class
// 3. kubernetes.io/ingress.class
func IngressClass(o metav1.ObjectMetaAccessor) string {
a := o.GetObjectMeta().GetAnnotations()
func IngressClass(a map[string]string) string {
if class, ok := a["projectcontour.io/ingress.class"]; ok {
return class
}
Expand All @@ -209,11 +208,10 @@ func IngressClass(o metav1.ObjectMetaAccessor) string {
return ""
}

// MatchesIngressClass checks that the passed object has an ingress class that matches
// classesMatch checks that the passed object has an ingress class that matches
// either the passed ingress-class string, or DEFAULT_INGRESS_CLASS if it's empty.
func MatchesIngressClass(o metav1.ObjectMetaAccessor, ic string) bool {

switch IngressClass(o) {
func classesMatch(first string, ic string) bool {
switch first {
case ic:
// Handles ic == "" and ic == "custom".
return true
Expand All @@ -223,9 +221,7 @@ func MatchesIngressClass(o metav1.ObjectMetaAccessor, ic string) bool {
return true
}
}

return false

}

// MinProtoVersion returns the TLS protocol version specified by an ingress annotation
Expand Down
4 changes: 2 additions & 2 deletions internal/annotation/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,12 +559,12 @@ func TestMatchIngressClass(t *testing.T) {
t.Run(name, func(t *testing.T) {
cases := []string{"", DEFAULT_INGRESS_CLASS}
for i := 0; i < len(cases); i++ {
got := MatchesIngressClass(tc.fixture, cases[i])
class := IngressClass(tc.fixture.GetObjectMeta().GetAnnotations())
got := classesMatch(class, cases[i])
if tc.want[i] != got {
t.Errorf("matching %v against ingress class %q: expected %v, got %v", tc.fixture, cases[i], tc.want[i], got)
}
}

})
}
}
Expand Down
70 changes: 70 additions & 0 deletions internal/annotation/ingressclass.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright © 2020 VMware
// 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 annotation

import (
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Annotation struct {
// The ingress class configured.
// If not set, defaults to DEFAULT_INGRESS_CLASS.
SystemIngressClass string

logrus.FieldLogger
}

// MatchesIngressClass returns true if the given Kubernetes object
// belongs to the Ingress class that this cache is using.
func (a *Annotation) MatchesIngressClass(obj metav1.ObjectMetaAccessor, ingressClassName *string) bool {
class := IngressClass(obj.GetObjectMeta().GetAnnotations())

// If a class is defined as an annotation
if len(class) > 0 {
// Ingress.Class annotation takes precedence over the Spec.IngressClassName
if classesMatch(class, a.SystemIngressClass) {
return true
}

// IngressClass did not match, log the annotation message
a.WithField("name", obj.GetObjectMeta().GetName()).
WithField("namespace", obj.GetObjectMeta().GetNamespace()).
WithField("kind", "Ingress").
WithField("ingress.class", class).
Debug("ignoring object with unmatched ingress class")
return false
}

// Annotation does not match, check if IngressClassName matches
if ingressClassName != nil {
if classesMatch(*ingressClassName, a.SystemIngressClass) {
return true
}

// IngressClassName did not match but was configured, log the ingressClassName message
a.WithField("name", obj.GetObjectMeta().GetName()).
WithField("namespace", obj.GetObjectMeta().GetNamespace()).
WithField("kind", "HTTPProxy").
WithField("spec.ingressClassName", *ingressClassName).
Debug("ignoring object with unmatched ingressClassName")
return false
}

// If SystemIngressClass is defined, then the there should not be a match
if len(a.SystemIngressClass) > 0 {
return false
}
return true
}
191 changes: 191 additions & 0 deletions internal/annotation/ingressclass_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright © 2020 VMware
// 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 annotation

import (
"testing"

"github.com/sirupsen/logrus"
"k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utils "k8s.io/utils/pointer"
)

func TestMatchesIngressClass(t *testing.T) {
tests := map[string]struct {
obj *v1beta1.Ingress
systemIngressClass string
want bool
}{
"matches default ingress class annotation": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
Annotations: map[string]string{
"projectcontour.io/ingress.class": DEFAULT_INGRESS_CLASS,
},
},
},
want: true,
},
"does not match specific ingress class annotation": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
Annotations: map[string]string{
"projectcontour.io/ingress.class": "mismatch",
},
},
},
want: false,
},
"matches ingress class annotation with custom ingress class": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
Annotations: map[string]string{
"projectcontour.io/ingress.class": "custom",
},
},
},
systemIngressClass: "custom",
want: true,
},
"does not match ingress class annotation with custom ingress class": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
Annotations: map[string]string{
"projectcontour.io/ingress.class": "contour",
},
},
},
systemIngressClass: "custom",
want: false,
},
"matches default ingress class name": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Spec: v1beta1.IngressSpec{
IngressClassName: utils.StringPtr(DEFAULT_INGRESS_CLASS),
},
},
want: true,
},
"does not match default ingress class name": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Spec: v1beta1.IngressSpec{
IngressClassName: utils.StringPtr("mismatch"),
},
},
want: false,
},
"matches ingress class name with custom ingress class": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Spec: v1beta1.IngressSpec{
IngressClassName: utils.StringPtr("custom"),
},
},
systemIngressClass: "custom",
want: true,
},
"does not match ingress class name with custom ingress class": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
},
Spec: v1beta1.IngressSpec{
IngressClassName: utils.StringPtr("contour"),
},
},
systemIngressClass: "custom",
want: false,
},
"annotation matches before ingressClassName": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
Annotations: map[string]string{
"projectcontour.io/ingress.class": "contour",
},
},
Spec: v1beta1.IngressSpec{
IngressClassName: utils.StringPtr("custom"),
},
},
want: true,
},
"does not match annotation when configured with ingressClassName": {
obj: &v1beta1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: "default",
Annotations: map[string]string{
"projectcontour.io/ingress.class": "custom",
},
},
Spec: v1beta1.IngressSpec{
IngressClassName: utils.StringPtr("contour"),
},
},
want: false,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
a := Annotation{
SystemIngressClass: tc.systemIngressClass,
FieldLogger: testLogger(t),
}

got := a.MatchesIngressClass(tc.obj, tc.obj.Spec.IngressClassName)
if got != tc.want {
t.Fatalf("expected: %v, got %v", tc.want, got)
}
})
}
}

func testLogger(t *testing.T) logrus.FieldLogger {
log := logrus.New()
log.Out = &testWriter{t}
return log
}

type testWriter struct {
*testing.T
}

func (t *testWriter) Write(buf []byte) (int, error) {
t.Logf("%s", buf)
return len(buf), nil
}
5 changes: 5 additions & 0 deletions internal/contour/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package contour
import (
"testing"

"github.com/projectcontour/contour/internal/annotation"

ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1"
projcontour "github.com/projectcontour/contour/apis/projectcontour/v1"
"github.com/projectcontour/contour/internal/assert"
Expand Down Expand Up @@ -43,6 +45,9 @@ func TestIngressRouteMetrics(t *testing.T) {
Source: dag.KubernetesCache{
RootNamespaces: tc.rootNamespaces,
FieldLogger: testLogger(t),
Annotation: annotation.Annotation{
FieldLogger: testLogger(t),
},
},
}

Expand Down
5 changes: 5 additions & 0 deletions internal/dag/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"testing"
"time"

"github.com/projectcontour/contour/internal/annotation"

envoy_api_v2_auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth"
"github.com/google/go-cmp/cmp"
ingressroutev1 "github.com/projectcontour/contour/apis/contour/v1beta1"
Expand Down Expand Up @@ -6698,6 +6700,9 @@ func TestDAGInsert(t *testing.T) {
Namespace: tc.fallbackCertificateNamespace,
},
Source: KubernetesCache{
Annotation: annotation.Annotation{
FieldLogger: testLogger(t),
},
FieldLogger: testLogger(t),
},
}
Expand Down
Loading

0 comments on commit e90e82a

Please sign in to comment.