From 92eeb7828bd0a8147248876828b003a364eb78ab Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Wed, 14 Jun 2017 19:40:25 -0400 Subject: [PATCH] Implement Equaler --- core/pkg/ingress/annotations/authreq/main.go | 2 +- core/pkg/ingress/annotations/authtls/main.go | 6 +- .../ingress/annotations/ipwhitelist/main.go | 6 +- .../pkg/ingress/annotations/ratelimit/main.go | 4 +- .../annotations/secureupstream/main.go | 5 +- core/pkg/ingress/controller/controller.go | 2 +- core/pkg/ingress/type_equals_test.go | 74 +++++ core/pkg/ingress/types.go | 20 +- core/pkg/ingress/types_equals.go | 24 +- tests/manifests/configuration-a.json | 97 +++++++ tests/manifests/configuration-b.json | 97 +++++++ tests/manifests/configuration-c.json | 254 ++++++++++++++++++ 12 files changed, 560 insertions(+), 31 deletions(-) create mode 100644 core/pkg/ingress/type_equals_test.go create mode 100644 tests/manifests/configuration-a.json create mode 100644 tests/manifests/configuration-b.json create mode 100644 tests/manifests/configuration-c.json diff --git a/core/pkg/ingress/annotations/authreq/main.go b/core/pkg/ingress/annotations/authreq/main.go index 069a14401..5523fbd7f 100644 --- a/core/pkg/ingress/annotations/authreq/main.go +++ b/core/pkg/ingress/annotations/authreq/main.go @@ -44,7 +44,7 @@ type External struct { SigninURL string `json:"signinUrl"` Method string `json:"method"` SendBody bool `json:"sendBody"` - ResponseHeaders []string `json:"responseHeaders"` + ResponseHeaders []string `json:"responseHeaders,omitEmpty"` } func (e1 *External) Equal(e2 *External) bool { diff --git a/core/pkg/ingress/annotations/authtls/main.go b/core/pkg/ingress/annotations/authtls/main.go index de1815ec2..7db7f97c1 100644 --- a/core/pkg/ingress/annotations/authtls/main.go +++ b/core/pkg/ingress/annotations/authtls/main.go @@ -36,8 +36,8 @@ const ( // AuthSSLConfig contains the AuthSSLCert used for muthual autentication // and the configured ValidationDepth type AuthSSLConfig struct { - AuthSSLCert resolver.AuthSSLCert - ValidationDepth int `json:"validationDepth"` + AuthSSLCert resolver.AuthSSLCert `json:"authSSLCert"` + ValidationDepth int `json:"validationDepth"` } func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool { @@ -47,7 +47,7 @@ func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool { if assl1 == nil || assl2 == nil { return false } - if (&assl1.AuthSSLCert).Equal(&assl2.AuthSSLCert) { + if !(&assl1.AuthSSLCert).Equal(&assl2.AuthSSLCert) { return false } if assl1.ValidationDepth != assl2.ValidationDepth { diff --git a/core/pkg/ingress/annotations/ipwhitelist/main.go b/core/pkg/ingress/annotations/ipwhitelist/main.go index a07239cd8..a2b397477 100644 --- a/core/pkg/ingress/annotations/ipwhitelist/main.go +++ b/core/pkg/ingress/annotations/ipwhitelist/main.go @@ -36,7 +36,7 @@ const ( // SourceRange returns the CIDR type SourceRange struct { - CIDR []string `json:"cidr"` + CIDR []string `json:"cidr,omitEmpty"` } func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool { @@ -47,6 +47,10 @@ func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool { return false } + if len(sr1.CIDR) != len(sr2.CIDR) { + return false + } + for _, s1l := range sr1.CIDR { found := false for _, sl2 := range sr2.CIDR { diff --git a/core/pkg/ingress/annotations/ratelimit/main.go b/core/pkg/ingress/annotations/ratelimit/main.go index abeb48156..14034c714 100644 --- a/core/pkg/ingress/annotations/ratelimit/main.go +++ b/core/pkg/ingress/annotations/ratelimit/main.go @@ -54,10 +54,10 @@ func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool { if rt1 == nil || rt2 == nil { return false } - if (&rt1.Connections).Equal(&rt2.Connections) { + if !(&rt1.Connections).Equal(&rt2.Connections) { return false } - if (&rt1.RPS).Equal(&rt2.RPS) { + if !(&rt1.RPS).Equal(&rt2.RPS) { return false } diff --git a/core/pkg/ingress/annotations/secureupstream/main.go b/core/pkg/ingress/annotations/secureupstream/main.go index d07f92994..1bba55f99 100644 --- a/core/pkg/ingress/annotations/secureupstream/main.go +++ b/core/pkg/ingress/annotations/secureupstream/main.go @@ -18,6 +18,7 @@ package secureupstream import ( "fmt" + "github.com/pkg/errors" extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1" @@ -32,8 +33,8 @@ const ( // Secure describes SSL backend configuration type Secure struct { - Secure bool - CACert resolver.AuthSSLCert + Secure bool `json:"secure"` + CACert resolver.AuthSSLCert `json:"caCert"` } type su struct { diff --git a/core/pkg/ingress/controller/controller.go b/core/pkg/ingress/controller/controller.go index c90c795c4..70fb53be1 100644 --- a/core/pkg/ingress/controller/controller.go +++ b/core/pkg/ingress/controller/controller.go @@ -412,7 +412,7 @@ func (ic *GenericController) syncIngress(key interface{}) error { } if ic.runningConfig != nil && ic.runningConfig.Equal(&pcfg) { - glog.Infof("skipping backend reload (no changes detected)") + glog.V(3).Infof("skipping backend reload (no changes detected)") return nil } diff --git a/core/pkg/ingress/type_equals_test.go b/core/pkg/ingress/type_equals_test.go new file mode 100644 index 000000000..de0f403cf --- /dev/null +++ b/core/pkg/ingress/type_equals_test.go @@ -0,0 +1,74 @@ +/* +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 ingress + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" +) + +func TestEqualConfiguration(t *testing.T) { + ap, _ := filepath.Abs("../../../tests/manifests/configuration-a.json") + a, err := readJSON(ap) + if err != nil { + t.Errorf("unexpected error reading JSON file: %v", err) + } + + bp, _ := filepath.Abs("../../../tests/manifests/configuration-b.json") + b, err := readJSON(bp) + if err != nil { + t.Errorf("unexpected error reading JSON file: %v", err) + } + + cp, _ := filepath.Abs("../../../tests/manifests/configuration-c.json") + c, err := readJSON(cp) + if err != nil { + t.Errorf("unexpected error reading JSON file: %v", err) + } + + if !a.Equal(b) { + t.Errorf("expected equal configurations (configuration-a.json and configuration-b.json)") + } + + if !b.Equal(a) { + t.Errorf("expected equal configurations (configuration-a.json and configuration-b.json)") + } + + if a.Equal(c) { + t.Errorf("expected equal configurations (configuration-a.json and configuration-c.json)") + } + +} + +func readJSON(p string) (*Configuration, error) { + f, err := os.Open(p) + if err != nil { + return nil, err + } + + var c Configuration + + d := json.NewDecoder(f) + err = d.Decode(&c) + if err != nil { + return nil, err + } + + return &c, nil +} diff --git a/core/pkg/ingress/types.go b/core/pkg/ingress/types.go index eab300a25..875259a1a 100644 --- a/core/pkg/ingress/types.go +++ b/core/pkg/ingress/types.go @@ -124,9 +124,9 @@ type BackendInfo struct { type Configuration struct { // Backends are a list of backends used by all the Ingress rules in the // ingress controller. This list includes the default backend - Backends []*Backend `json:"namespace"` + Backends []*Backend `json:"backends,omitEmpty"` // Servers - Servers []*Server `json:"servers"` + Servers []*Server `json:"servers,omitEmpty"` // TCPEndpoints contain endpoints for tcp streams handled by this backend // +optional TCPEndpoints []L4Service `json:"tcpEndpoints,omitempty"` @@ -143,7 +143,7 @@ type Configuration struct { type Backend struct { // Name represents an unique api.Service name formatted as -- Name string `json:"name"` - Service *api.Service `json:"service"` + Service *api.Service `json:"service,omitempty"` Port intstr.IntOrString `json:"port"` // This indicates if the communication protocol between the backend and the endpoint is HTTP or HTTPS // Allowing the use of HTTPS @@ -156,9 +156,9 @@ type Backend struct { // SSLPassthrough indicates that Ingress controller will delegate TLS termination to the endpoints. SSLPassthrough bool `json:"sslPassthrough"` // Endpoints contains the list of endpoints currently running - Endpoints []Endpoint `json:"endpoints"` + Endpoints []Endpoint `json:"endpoints,omitempty"` // StickySessionAffinitySession contains the StickyConfig object with stickness configuration - SessionAffinity SessionAffinityConfig + SessionAffinity SessionAffinityConfig `json:"sessionAffinityConfig"` } // SessionAffinityConfig describes different affinity configurations for new sessions. @@ -168,8 +168,8 @@ type Backend struct { // affinity values are incompatible. Once set, the backend makes no guarantees // about honoring updates. type SessionAffinityConfig struct { - AffinityType string `json:"name"` - CookieSessionAffinity CookieSessionAffinity + AffinityType string `json:"name"` + CookieSessionAffinity CookieSessionAffinity `json:"cookieSessionAffinity"` } // CookieSessionAffinity defines the structure used in Affinity configured by Cookies. @@ -253,7 +253,7 @@ type Location struct { BasicDigestAuth auth.BasicDigest `json:"basicDigestAuth,omitempty"` // Denied returns an error when this location cannot not be allowed // Requesting a denied location should return HTTP code 403. - Denied error + Denied error `json:"denied,omitempty"` // EnableCORS indicates if path must support CORS // +optional EnableCORS bool `json:"enableCors,omitempty"` @@ -294,7 +294,7 @@ type Location struct { // The endpoints must provide the TLS termination exposing the required SSL certificate. // The ingress controller only pipes the underlying TCP connection type SSLPassthroughBackend struct { - Service *api.Service `json:"service"` + Service *api.Service `json:"service,omitEmpty"` Port intstr.IntOrString `json:"port"` // Backend describes the endpoints to use. Backend string `json:"namespace,omitempty"` @@ -309,7 +309,7 @@ type L4Service struct { // Backend of the service Backend L4Backend `json:"backend"` // Endpoints active endpoints of the service - Endpoints []Endpoint `json:"endpoins"` + Endpoints []Endpoint `json:"endpoins,omitEmpty"` } // L4Backend describes the kubernetes service behind L4 Ingress service diff --git a/core/pkg/ingress/types_equals.go b/core/pkg/ingress/types_equals.go index cf4b35c60..3d9633d80 100644 --- a/core/pkg/ingress/types_equals.go +++ b/core/pkg/ingress/types_equals.go @@ -54,7 +54,7 @@ func (c1 *Configuration) Equal(c2 *Configuration) bool { for _, c1b := range c1.Backends { found := false for _, c2b := range c2.Backends { - if (c1b).Equal(c2b) { + if c1b.Equal(c2b) { found = true break } @@ -67,7 +67,7 @@ func (c1 *Configuration) Equal(c2 *Configuration) bool { for _, c1s := range c1.Servers { found := false for _, c2s := range c2.Servers { - if (c1s).Equal(c2s) { + if c1s.Equal(c2s) { found = true break } @@ -272,6 +272,7 @@ func (s1 *Server) Equal(s2 *Server) bool { if s1.SSLPemChecksum != s2.SSLPemChecksum { return false } + if len(s1.Locations) != len(s2.Locations) { return false } @@ -279,7 +280,7 @@ func (s1 *Server) Equal(s2 *Server) bool { for _, s1l := range s1.Locations { found := false for _, sl2 := range s2.Locations { - if (s1l).Equal(sl2) { + if s1l.Equal(sl2) { found = true break } @@ -308,6 +309,7 @@ func (l1 *Location) Equal(l2 *Location) bool { if l1.Backend != l2.Backend { return false } + if (l1.Service == nil && l2.Service != nil) || (l1.Service != nil && l2.Service == nil) { return false @@ -325,10 +327,10 @@ func (l1 *Location) Equal(l2 *Location) bool { } } - if l1.Port != l2.Port { + if l1.Port.StrVal != l2.Port.StrVal { return false } - if (&l1.BasicDigestAuth).Equal(&l2.BasicDigestAuth) { + if !(&l1.BasicDigestAuth).Equal(&l2.BasicDigestAuth) { return false } if l1.Denied != l2.Denied { @@ -337,22 +339,22 @@ func (l1 *Location) Equal(l2 *Location) bool { if l1.EnableCORS != l2.EnableCORS { return false } - if (&l1.ExternalAuth).Equal(&l2.ExternalAuth) { + if !(&l1.ExternalAuth).Equal(&l2.ExternalAuth) { return false } - if (&l1.RateLimit).Equal(&l2.RateLimit) { + if !(&l1.RateLimit).Equal(&l2.RateLimit) { return false } - if (&l1.Redirect).Equal(&l2.Redirect) { + if !(&l1.Redirect).Equal(&l2.Redirect) { return false } - if (&l1.Whitelist).Equal(&l2.Whitelist) { + if !(&l1.Whitelist).Equal(&l2.Whitelist) { return false } - if (&l1.Proxy).Equal(&l2.Proxy) { + if !(&l1.Proxy).Equal(&l2.Proxy) { return false } - if (&l1.CertificateAuth).Equal(&l2.CertificateAuth) { + if !(&l1.CertificateAuth).Equal(&l2.CertificateAuth) { return false } if l1.UsePortInRedirects != l2.UsePortInRedirects { diff --git a/tests/manifests/configuration-a.json b/tests/manifests/configuration-a.json new file mode 100644 index 000000000..ba84cc1c6 --- /dev/null +++ b/tests/manifests/configuration-a.json @@ -0,0 +1,97 @@ +{ + "backends": [{ + "name": "upstream-default-backend", + "port": 0, + "secure": false, + "secureCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "sslPassthrough": false, + "endpoints": [{ + "address": "172.17.0.5", + "port": "8080", + "maxFails": 0, + "failTimeout": 0 + }], + "sessionAffinityConfig": { + "name": "", + "cookieSessionAffinity": { + "name": "", + "hash": "" + } + } + }], + "servers": [{ + "hostname": "_", + "sslPassthrough": false, + "sslCertificate": "/ingress-controller/ssl/default-fake-certificate.pem", + "sslExpireTime": "0001-01-01T00:00:00Z", + "sslPemChecksum": "4302f26460e2c49c9a69229449efefb30dc42a9a", + "locations": [{ + "path": "/", + "isDefBackend": true, + "backend": "upstream-default-backend", + "service": null, + "port": 0, + "basicDigestAuth": { + "type": "", + "realm": "", + "file": "", + "secured": false + }, + "externalAuth": { + "url": "", + "host": "", + "signinUrl": "", + "method": "", + "sendBody": false, + "responseHeaders": null + }, + "rateLimit": { + "connections": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + }, + "rps": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + } + }, + "redirect": { + "target": "", + "addBaseUrl": false, + "sslRedirect": false, + "forceSSLRedirect": false, + "appRoot": "" + }, + "whitelist": { + "cidr": null + }, + "proxy": { + "bodySize": "1m", + "conectTimeout": 5, + "sendTimeout": 60, + "readTimeout": 60, + "bufferSize": "4k", + "cookieDomain": "off", + "cookiePath": "off" + }, + "certificateAuth": { + "authSSLCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "validationDepth": 0 + }, + "use-port-in-redirects": false, + "configuration-snippet": "" + }] + }] +} \ No newline at end of file diff --git a/tests/manifests/configuration-b.json b/tests/manifests/configuration-b.json new file mode 100644 index 000000000..ba84cc1c6 --- /dev/null +++ b/tests/manifests/configuration-b.json @@ -0,0 +1,97 @@ +{ + "backends": [{ + "name": "upstream-default-backend", + "port": 0, + "secure": false, + "secureCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "sslPassthrough": false, + "endpoints": [{ + "address": "172.17.0.5", + "port": "8080", + "maxFails": 0, + "failTimeout": 0 + }], + "sessionAffinityConfig": { + "name": "", + "cookieSessionAffinity": { + "name": "", + "hash": "" + } + } + }], + "servers": [{ + "hostname": "_", + "sslPassthrough": false, + "sslCertificate": "/ingress-controller/ssl/default-fake-certificate.pem", + "sslExpireTime": "0001-01-01T00:00:00Z", + "sslPemChecksum": "4302f26460e2c49c9a69229449efefb30dc42a9a", + "locations": [{ + "path": "/", + "isDefBackend": true, + "backend": "upstream-default-backend", + "service": null, + "port": 0, + "basicDigestAuth": { + "type": "", + "realm": "", + "file": "", + "secured": false + }, + "externalAuth": { + "url": "", + "host": "", + "signinUrl": "", + "method": "", + "sendBody": false, + "responseHeaders": null + }, + "rateLimit": { + "connections": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + }, + "rps": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + } + }, + "redirect": { + "target": "", + "addBaseUrl": false, + "sslRedirect": false, + "forceSSLRedirect": false, + "appRoot": "" + }, + "whitelist": { + "cidr": null + }, + "proxy": { + "bodySize": "1m", + "conectTimeout": 5, + "sendTimeout": 60, + "readTimeout": 60, + "bufferSize": "4k", + "cookieDomain": "off", + "cookiePath": "off" + }, + "certificateAuth": { + "authSSLCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "validationDepth": 0 + }, + "use-port-in-redirects": false, + "configuration-snippet": "" + }] + }] +} \ No newline at end of file diff --git a/tests/manifests/configuration-c.json b/tests/manifests/configuration-c.json new file mode 100644 index 000000000..a899e8bbe --- /dev/null +++ b/tests/manifests/configuration-c.json @@ -0,0 +1,254 @@ +{ + "backends": [{ + "name": "upstream-default-backend", + "service": null, + "port": 0, + "secure": false, + "secureCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "sslPassthrough": false, + "endpoints": [{ + "address": "172.17.0.8", + "port": "8080", + "maxFails": 0, + "failTimeout": 0 + }], + "SessionAffinity": { + "name": "", + "CookieSessionAffinity": { + "name": "", + "hash": "" + } + } + }, { + "name": "deis-deis-controller-8000", + "service": { + "metadata": { + "name": "deis-controller", + "namespace": "deis", + "selfLink": "/api/v1/namespaces/deis/services/deis-controller", + "uid": "1cba01a8-50b0-11e7-a384-0800270f5693", + "resourceVersion": "532", + "creationTimestamp": "2017-06-14T03:18:18Z", + "labels": { + "heritage": "deis" + } + }, + "spec": { + "ports": [{ + "name": "http", + "protocol": "TCP", + "port": 8000, + "targetPort": 8000, + "nodePort": 30171 + }], + "selector": { + "app": "deis-controller" + }, + "clusterIP": "10.0.0.198", + "type": "NodePort", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } + }, + "port": 8000, + "secure": false, + "secureCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "sslPassthrough": false, + "endpoints": [{ + "address": "172.17.0.7", + "port": "8000", + "maxFails": 0, + "failTimeout": 0 + }], + "SessionAffinity": { + "name": "", + "CookieSessionAffinity": { + "name": "", + "hash": "" + } + } + }], + "servers": [{ + "hostname": "_", + "sslPassthrough": false, + "sslCertificate": "/ingress-controller/ssl/default-fake-certificate.pem", + "sslExpireTime": "0001-01-01T00:00:00Z", + "sslPemChecksum": "123b44425920a2e4825ae779fba0e6e07fbac03d", + "locations": [{ + "path": "/", + "isDefBackend": true, + "backend": "upstream-default-backend", + "service": null, + "port": 0, + "basicDigestAuth": { + "type": "", + "realm": "", + "file": "", + "secured": false + }, + "Denied": null, + "externalAuth": { + "url": "", + "host": "", + "signinUrl": "", + "method": "", + "sendBody": false, + "responseHeaders": null + }, + "rateLimit": { + "connections": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + }, + "rps": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + } + }, + "redirect": { + "target": "", + "addBaseUrl": false, + "sslRedirect": false, + "forceSSLRedirect": false, + "appRoot": "" + }, + "whitelist": { + "cidr": null + }, + "proxy": { + "bodySize": "1g", + "conectTimeout": 5, + "sendTimeout": 60, + "readTimeout": 60, + "bufferSize": "4k", + "cookieDomain": "off", + "cookiePath": "off" + }, + "certificateAuth": { + "AuthSSLCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "validationDepth": 0 + }, + "use-port-in-redirects": false, + "configuration-snippet": "" + }] + }, { + "hostname": "deis.minikube", + "sslPassthrough": false, + "sslCertificate": "", + "sslExpireTime": "0001-01-01T00:00:00Z", + "sslPemChecksum": "", + "locations": [{ + "path": "/", + "isDefBackend": false, + "backend": "deis-deis-controller-8000", + "service": { + "metadata": { + "name": "deis-controller", + "namespace": "deis", + "selfLink": "/api/v1/namespaces/deis/services/deis-controller", + "uid": "1cba01a8-50b0-11e7-a384-0800270f5693", + "resourceVersion": "532", + "creationTimestamp": "2017-06-14T03:18:18Z", + "labels": { + "heritage": "deis" + } + }, + "spec": { + "ports": [{ + "name": "http", + "protocol": "TCP", + "port": 8000, + "targetPort": 8000, + "nodePort": 30171 + }], + "selector": { + "app": "deis-controller" + }, + "clusterIP": "10.0.0.198", + "type": "NodePort", + "sessionAffinity": "None" + }, + "status": { + "loadBalancer": {} + } + }, + "port": 8000, + "basicDigestAuth": { + "type": "", + "realm": "", + "file": "", + "secured": false + }, + "Denied": null, + "externalAuth": { + "url": "", + "host": "", + "signinUrl": "", + "method": "", + "sendBody": false, + "responseHeaders": null + }, + "rateLimit": { + "connections": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + }, + "rps": { + "name": "", + "limit": 0, + "burst": 0, + "sharedSize": 0 + } + }, + "redirect": { + "target": "", + "addBaseUrl": false, + "sslRedirect": true, + "forceSSLRedirect": false, + "appRoot": "" + }, + "whitelist": { + "cidr": null + }, + "proxy": { + "bodySize": "1g", + "conectTimeout": 5, + "sendTimeout": 60, + "readTimeout": 60, + "bufferSize": "4k", + "cookieDomain": "off", + "cookiePath": "off" + }, + "certificateAuth": { + "AuthSSLCert": { + "secret": "", + "caFilename": "", + "pemSha": "" + }, + "validationDepth": 0 + }, + "use-port-in-redirects": false, + "configuration-snippet": "" + }] + }] +}