WIP: Avoid reloads implementing Equals in structs

This commit is contained in:
Manuel de Brito Fontes 2017-06-14 17:33:12 -04:00
parent e9871ffaad
commit 75a4a61254
12 changed files with 701 additions and 11 deletions

View file

@ -53,6 +53,29 @@ type BasicDigest struct {
Secured bool `json:"secured"`
}
func (bd1 *BasicDigest) Equal(bd2 *BasicDigest) bool {
if bd1 == bd2 {
return true
}
if bd1 == nil || bd2 == nil {
return false
}
if bd1.Type != bd2.Type {
return false
}
if bd1.Realm != bd2.Realm {
return false
}
if bd1.File != bd2.File {
return false
}
if bd1.Secured != bd2.Secured {
return false
}
return true
}
type auth struct {
secretResolver resolver.Secret
authDirectory string

View file

@ -47,6 +47,48 @@ type External struct {
ResponseHeaders []string `json:"responseHeaders"`
}
func (e1 *External) Equal(e2 *External) bool {
if e1 == e2 {
return true
}
if e1 == nil || e2 == nil {
return false
}
if e1.URL != e2.URL {
return false
}
if e1.Host != e2.Host {
return false
}
if e1.SigninURL != e2.SigninURL {
return false
}
if e1.Method != e2.Method {
return false
}
if e1.SendBody != e2.SendBody {
return false
}
if e1.Method != e2.Method {
return false
}
for _, ep1 := range e1.ResponseHeaders {
found := false
for _, ep2 := range e2.ResponseHeaders {
if ep1 == ep2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
var (
methods = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "CONNECT", "OPTIONS", "TRACE"}
headerRegexp = regexp.MustCompile(`^[a-zA-Z\d\-_]+$`)

View file

@ -40,6 +40,23 @@ type AuthSSLConfig struct {
ValidationDepth int `json:"validationDepth"`
}
func (assl1 *AuthSSLConfig) Equal(assl2 *AuthSSLConfig) bool {
if assl1 == assl2 {
return true
}
if assl1 == nil || assl2 == nil {
return false
}
if (&assl1.AuthSSLCert).Equal(&assl2.AuthSSLCert) {
return false
}
if assl1.ValidationDepth != assl2.ValidationDepth {
return false
}
return true
}
// NewParser creates a new TLS authentication annotation parser
func NewParser(resolver resolver.AuthCertificate) parser.IngressAnnotation {
return authTLS{resolver}

View file

@ -39,6 +39,30 @@ type SourceRange struct {
CIDR []string `json:"cidr"`
}
func (sr1 *SourceRange) Equal(sr2 *SourceRange) bool {
if sr1 == sr2 {
return true
}
if sr1 == nil || sr2 == nil {
return false
}
for _, s1l := range sr1.CIDR {
found := false
for _, sl2 := range sr2.CIDR {
if s1l == sl2 {
found = true
break
}
}
if !found {
return false
}
}
return true
}
type ipwhitelist struct {
backendResolver resolver.DefaultBackend
}

View file

@ -44,6 +44,38 @@ type Configuration struct {
CookiePath string `json:"cookiePath"`
}
func (l1 *Configuration) Equal(l2 *Configuration) bool {
if l1 == l2 {
return true
}
if l1 == nil || l2 == nil {
return false
}
if l1.BodySize != l2.BodySize {
return false
}
if l1.ConnectTimeout != l2.ConnectTimeout {
return false
}
if l1.SendTimeout != l2.SendTimeout {
return false
}
if l1.ReadTimeout != l2.ReadTimeout {
return false
}
if l1.BufferSize != l2.BufferSize {
return false
}
if l1.CookieDomain != l2.CookieDomain {
return false
}
if l1.CookiePath != l2.CookiePath {
return false
}
return true
}
type proxy struct {
backendResolver resolver.DefaultBackend
}

View file

@ -47,6 +47,23 @@ type RateLimit struct {
RPS Zone `json:"rps"`
}
func (rt1 *RateLimit) Equal(rt2 *RateLimit) bool {
if rt1 == rt2 {
return true
}
if rt1 == nil || rt2 == nil {
return false
}
if (&rt1.Connections).Equal(&rt2.Connections) {
return false
}
if (&rt1.RPS).Equal(&rt2.RPS) {
return false
}
return true
}
// Zone returns information about the NGINX rate limit (limit_req_zone)
// http://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone
type Zone struct {
@ -57,6 +74,29 @@ type Zone struct {
SharedSize int `json:"sharedSize"`
}
func (z1 *Zone) Equal(z2 *Zone) bool {
if z1 == z2 {
return true
}
if z1 == nil || z2 == nil {
return false
}
if z1.Name != z2.Name {
return false
}
if z1.Limit != z2.Limit {
return false
}
if z1.Burst != z2.Burst {
return false
}
if z1.SharedSize != z2.SharedSize {
return false
}
return true
}
type ratelimit struct {
}

View file

@ -46,6 +46,32 @@ type Redirect struct {
AppRoot string `json:"appRoot"`
}
func (r1 *Redirect) Equal(r2 *Redirect) bool {
if r1 == r2 {
return true
}
if r1 == nil || r2 == nil {
return false
}
if r1.Target != r2.Target {
return false
}
if r1.AddBaseURL != r2.AddBaseURL {
return false
}
if r1.SSLRedirect != r2.SSLRedirect {
return false
}
if r1.ForceSSLRedirect != r2.ForceSSLRedirect {
return false
}
if r1.AppRoot != r2.AppRoot {
return false
}
return true
}
type rewrite struct {
backendResolver resolver.DefaultBackend
}

View file

@ -18,6 +18,7 @@ package controller
import (
"fmt"
"math/rand"
"os"
"reflect"
"sort"
@ -108,6 +109,8 @@ type GenericController struct {
stopLock *sync.Mutex
stopCh chan struct{}
runningConfig *ingress.Configuration
}
// Configuration contains all the settings required by an Ingress controller
@ -400,14 +403,22 @@ func (ic *GenericController) syncIngress(key interface{}) error {
}
}
err := ic.cfg.Backend.OnUpdate(ingress.Configuration{
pcfg := ingress.Configuration{
Backends: upstreams,
Servers: servers,
TCPEndpoints: ic.getStreamServices(ic.cfg.TCPConfigMapName, api.ProtocolTCP),
UDPEndpoints: ic.getStreamServices(ic.cfg.UDPConfigMapName, api.ProtocolUDP),
PassthroughBackends: passUpstreams,
})
}
if ic.runningConfig != nil && ic.runningConfig.Equal(&pcfg) {
glog.Infof("skipping backend reload (no changes detected)")
return nil
}
glog.Infof("backend reload required")
err := ic.cfg.Backend.OnUpdate(pcfg)
if err != nil {
incReloadErrorCount()
glog.Errorf("unexpected failure restarting the backend: \n%v", err)
@ -418,6 +429,8 @@ func (ic *GenericController) syncIngress(key interface{}) error {
incReloadCount()
setSSLExpireTime(servers)
ic.runningConfig = &pcfg
return nil
}
@ -679,9 +692,6 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
}
}
// TODO: find a way to make this more readable
// The structs must be ordered to always generate the same file
// if the content does not change.
aUpstreams := make([]*ingress.Backend, 0, len(upstreams))
for _, value := range upstreams {
if len(value.Endpoints) == 0 {
@ -690,7 +700,6 @@ func (ic *GenericController) getBackendServers() ([]*ingress.Backend, []*ingress
}
aUpstreams = append(aUpstreams, value)
}
sort.Sort(ingress.BackendByNameServers(aUpstreams))
aServers := make([]*ingress.Server, 0, len(servers))
for _, value := range servers {
@ -847,12 +856,17 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
glog.Warningf("service %v does not have any active endpoints", svcKey)
}
sort.Sort(ingress.EndpointByAddrPort(endps))
upstreams = append(upstreams, endps...)
break
}
}
rand.Seed(time.Now().UnixNano())
for i := range upstreams {
j := rand.Intn(i + 1)
upstreams[i], upstreams[j] = upstreams[j], upstreams[i]
}
return upstreams, nil
}

View file

@ -51,3 +51,17 @@ type AuthSSLCert struct {
// PemSHA contains the SHA1 hash of the 'tls.crt' value
PemSHA string `json:"pemSha"`
}
func (asslc1 *AuthSSLCert) Equal(assl2 *AuthSSLCert) bool {
if asslc1.Secret != assl2.Secret {
return false
}
if asslc1.CAFileName != assl2.CAFileName {
return false
}
if asslc1.PemSHA != assl2.PemSHA {
return false
}
return true
}

View file

@ -17,9 +17,10 @@ limitations under the License.
package ingress
import (
"time"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"time"
)
// BackendByNameServers sorts upstreams by name

View file

@ -17,6 +17,8 @@ limitations under the License.
package ingress
import (
"time"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/util/intstr"
@ -33,7 +35,6 @@ import (
"k8s.io/ingress/core/pkg/ingress/defaults"
"k8s.io/ingress/core/pkg/ingress/resolver"
"k8s.io/ingress/core/pkg/ingress/store"
"time"
)
var (
@ -73,8 +74,6 @@ type Controller interface {
// https://k8s.io/ingress/core/blob/master/pkg/ingress/types.go#L83
// The backend returns an error if was not possible to update the configuration.
//
// The returned configuration is then passed to test, and then to reload
// if there is no errors.
OnUpdate(Configuration) error
// ConfigMap content of --configmap
SetConfig(*api.ConfigMap)

View file

@ -0,0 +1,458 @@
/*
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
func (bi1 *BackendInfo) Equal(bi2 *BackendInfo) bool {
if bi1 == bi2 {
return true
}
if bi1 == nil || bi2 == nil {
return false
}
if bi1.Name != bi2.Name {
return false
}
if bi1.Release != bi2.Release {
return false
}
if bi1.Build != bi2.Build {
return false
}
if bi1.Repository != bi2.Repository {
return false
}
return true
}
func (c1 *Configuration) Equal(c2 *Configuration) bool {
if c1 == c2 {
return true
}
if c1 == nil || c2 == nil {
return false
}
if len(c1.Backends) != len(c2.Backends) {
return false
}
for _, c1b := range c1.Backends {
found := false
for _, c2b := range c2.Backends {
if (c1b).Equal(c2b) {
found = true
break
}
}
if !found {
return false
}
}
for _, c1s := range c1.Servers {
found := false
for _, c2s := range c2.Servers {
if (c1s).Equal(c2s) {
found = true
break
}
}
if !found {
return false
}
}
if len(c1.TCPEndpoints) != len(c2.TCPEndpoints) {
return false
}
for _, tcp1 := range c1.TCPEndpoints {
found := false
for _, tcp2 := range c2.TCPEndpoints {
if (&tcp1).Equal(&tcp2) {
found = true
break
}
}
if !found {
return false
}
}
if len(c1.UDPEndpoints) != len(c2.UDPEndpoints) {
return false
}
for _, udp1 := range c1.UDPEndpoints {
found := false
for _, udp2 := range c2.UDPEndpoints {
if (&udp1).Equal(&udp2) {
found = true
break
}
}
if !found {
return false
}
}
if len(c1.PassthroughBackends) != len(c2.PassthroughBackends) {
return false
}
for _, ptb1 := range c1.PassthroughBackends {
found := false
for _, ptb2 := range c2.PassthroughBackends {
if ptb1.Equal(ptb2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (b1 *Backend) Equal(b2 *Backend) bool {
if b1 == b2 {
return true
}
if b1 == nil || b2 == nil {
return false
}
if b1.Name != b2.Name {
return false
}
if (b1.Service == nil && b2.Service != nil) ||
(b1.Service != nil && b2.Service == nil) {
return false
}
if b1.Service != nil && b2.Service != nil {
if b1.Service.GetNamespace() != b2.Service.GetNamespace() {
return false
}
if b1.Service.GetName() != b2.Service.GetName() {
return false
}
if b1.Service.GetResourceVersion() != b2.Service.GetResourceVersion() {
return false
}
}
if b1.Port != b2.Port {
return false
}
if b1.Secure != b2.Secure {
return false
}
if !(&b1.SecureCACert).Equal(&b2.SecureCACert) {
return false
}
if b1.SSLPassthrough != b2.SSLPassthrough {
return false
}
if !(&b1.SessionAffinity).Equal(&b2.SessionAffinity) {
return false
}
if len(b1.Endpoints) != len(b2.Endpoints) {
return false
}
for _, udp1 := range b1.Endpoints {
found := false
for _, udp2 := range b2.Endpoints {
if (&udp1).Equal(&udp2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (sac1 *SessionAffinityConfig) Equal(sac2 *SessionAffinityConfig) bool {
if sac1 == sac2 {
return true
}
if sac1 == nil || sac2 == nil {
return false
}
if sac1.AffinityType != sac2.AffinityType {
return false
}
if !(&sac1.CookieSessionAffinity).Equal(&sac2.CookieSessionAffinity) {
return false
}
return true
}
func (csa1 *CookieSessionAffinity) Equal(csa2 *CookieSessionAffinity) bool {
if csa1 == csa2 {
return true
}
if csa1 == nil || csa2 == nil {
return false
}
if csa1.Name != csa2.Name {
return false
}
if csa1.Hash != csa2.Hash {
return false
}
return true
}
// Equal checks the equality against an Endpoint
func (e1 *Endpoint) Equal(e2 *Endpoint) bool {
if e1 == e2 {
return true
}
if e1 == nil || e2 == nil {
return false
}
if e1.Address != e2.Address {
return false
}
if e1.Port != e2.Port {
return false
}
if e1.MaxFails != e2.MaxFails {
return false
}
if e1.FailTimeout != e2.FailTimeout {
return false
}
return true
}
func (s1 *Server) Equal(s2 *Server) bool {
if s1 == s2 {
return true
}
if s1 == nil || s2 == nil {
return false
}
if s1.Hostname != s2.Hostname {
return false
}
if s1.SSLPassthrough != s2.SSLPassthrough {
return false
}
if s1.SSLCertificate != s2.SSLCertificate {
return false
}
if s1.SSLPemChecksum != s2.SSLPemChecksum {
return false
}
if len(s1.Locations) != len(s2.Locations) {
return false
}
for _, s1l := range s1.Locations {
found := false
for _, sl2 := range s2.Locations {
if (s1l).Equal(sl2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (l1 *Location) Equal(l2 *Location) bool {
if l1 == l2 {
return true
}
if l1 == nil || l2 == nil {
return false
}
if l1.Path != l2.Path {
return false
}
if l1.IsDefBackend != l2.IsDefBackend {
return false
}
if l1.Backend != l2.Backend {
return false
}
if (l1.Service == nil && l2.Service != nil) ||
(l1.Service != nil && l2.Service == nil) {
return false
}
if l1.Service != nil && l2.Service != nil {
if l1.Service.GetNamespace() != l2.Service.GetNamespace() {
return false
}
if l1.Service.GetName() != l2.Service.GetName() {
return false
}
if l1.Service.GetResourceVersion() != l2.Service.GetResourceVersion() {
return false
}
}
if l1.Port != l2.Port {
return false
}
if (&l1.BasicDigestAuth).Equal(&l2.BasicDigestAuth) {
return false
}
if l1.Denied != l2.Denied {
return false
}
if l1.EnableCORS != l2.EnableCORS {
return false
}
if (&l1.ExternalAuth).Equal(&l2.ExternalAuth) {
return false
}
if (&l1.RateLimit).Equal(&l2.RateLimit) {
return false
}
if (&l1.Redirect).Equal(&l2.Redirect) {
return false
}
if (&l1.Whitelist).Equal(&l2.Whitelist) {
return false
}
if (&l1.Proxy).Equal(&l2.Proxy) {
return false
}
if (&l1.CertificateAuth).Equal(&l2.CertificateAuth) {
return false
}
if l1.UsePortInRedirects != l2.UsePortInRedirects {
return false
}
if l1.ConfigurationSnippet != l2.ConfigurationSnippet {
return false
}
return true
}
func (ptb1 *SSLPassthroughBackend) Equal(ptb2 *SSLPassthroughBackend) bool {
if ptb1 == ptb2 {
return true
}
if ptb1 == nil || ptb2 == nil {
return false
}
if ptb1.Backend != ptb2.Backend {
return false
}
if ptb1.Hostname != ptb2.Hostname {
return false
}
if ptb1.Port != ptb2.Port {
return false
}
if (ptb1.Service == nil && ptb2.Service != nil) ||
(ptb1.Service != nil && ptb2.Service == nil) {
return false
}
if ptb1.Service != nil && ptb2.Service != nil {
if ptb1.Service.GetNamespace() != ptb2.Service.GetNamespace() {
return false
}
if ptb1.Service.GetName() != ptb2.Service.GetName() {
return false
}
if ptb1.Service.GetResourceVersion() != ptb2.Service.GetResourceVersion() {
return false
}
}
return true
}
func (e1 *L4Service) Equal(e2 *L4Service) bool {
if e1 == e2 {
return true
}
if e1 == nil || e2 == nil {
return false
}
if e1.Port != e2.Port {
return false
}
if !(&e1.Backend).Equal(&e2.Backend) {
return false
}
if len(e1.Endpoints) != len(e2.Endpoints) {
return false
}
for _, ep1 := range e1.Endpoints {
found := false
for _, ep2 := range e2.Endpoints {
if (&ep1).Equal(&ep2) {
found = true
break
}
}
if !found {
return false
}
}
return true
}
func (l4b1 *L4Backend) Equal(l4b2 *L4Backend) bool {
if l4b1 == l4b2 {
return true
}
if l4b1 == nil || l4b2 == nil {
return false
}
if l4b1.Port != l4b2.Port {
return false
}
if l4b1.Name != l4b2.Name {
return false
}
if l4b1.Namespace != l4b2.Namespace {
return false
}
if l4b1.Protocol != l4b2.Protocol {
return false
}
return true
}