Commit c5ff26da authored by Geoff Simmons's avatar Geoff Simmons

Varnish/Ingress may implement and merge Ingresses across namespaces.

An Ingress definition with ingress.class for Varnish can identify
the Varnish Service that implements its rules:

* If it has the ingress.varnish-cache.org/varnish-svc annotation,
  then the values names the Varnish Service, which may be named
  using namespace/name notation. If the namespace is left out,
  then the same namespace as the Ingress is assumed.

* If the Ingress does not have the annotation, then:

  * If there is only one Varnish-as-Ingress Service in the cluster
    (with label app:varnish-ingress), then that Service implements
    the rules.

  * Otherwise if there is only one Varnish Service in the same
    namespace as the Ingress, then that Service implements the
    rules.

  * Otherwise error

A Varnish Service can define Services from different namespaces as
its backends, if it implements Ingresses from those namespaces which
define those Services as Ingress backends. (Ingresses only define
backends from the same namespace.)

Ingresses can be merged under these conditions:

* No host is specified in more than one Ingress.

* There is no more than one default backends in all of the Ingresses
  to be merged.

If either of these rules are violated, then error.

This resolves the question of merging Ingresses (to be documented in
upcoming commits).

Ref #26
Ref #13
parent 642209bc
...@@ -36,8 +36,11 @@ import ( ...@@ -36,8 +36,11 @@ import (
"strconv" "strconv"
vcr_v1alpha1 "code.uplex.de/uplex-varnish/k8s-ingress/pkg/apis/varnishingress/v1alpha1" vcr_v1alpha1 "code.uplex.de/uplex-varnish/k8s-ingress/pkg/apis/varnishingress/v1alpha1"
"code.uplex.de/uplex-varnish/k8s-ingress/pkg/varnish"
"code.uplex.de/uplex-varnish/k8s-ingress/pkg/varnish/vcl" "code.uplex.de/uplex-varnish/k8s-ingress/pkg/varnish/vcl"
"k8s.io/client-go/tools/cache"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
...@@ -57,28 +60,125 @@ const ( ...@@ -57,28 +60,125 @@ const (
func (worker *NamespaceWorker) getVarnishSvcForIng( func (worker *NamespaceWorker) getVarnishSvcForIng(
ing *extensions.Ingress) (*api_v1.Service, error) { ing *extensions.Ingress) (*api_v1.Service, error) {
svcs, err := worker.svc.List(varnishIngressSelector) svcs, err := worker.listers.svc.List(varnishIngressSelector)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if varnishSvc, exists := ing.Annotations[varnishSvcKey]; exists { if varnishSvc, exists := ing.Annotations[varnishSvcKey]; exists {
worker.log.Debugf("Ingress %s/%s has annotation %s:%s",
ing.Namespace, ing.Name, varnishSvcKey, varnishSvc)
targetNs, targetSvc, err :=
cache.SplitMetaNamespaceKey(varnishSvc)
if err != nil {
return nil, err
}
if targetNs == "" {
targetNs = worker.namespace
}
for _, svc := range svcs { for _, svc := range svcs {
if svc.Name == varnishSvc { if svc.Namespace == targetNs && svc.Name == targetSvc {
return svc, nil return svc, nil
} }
} }
worker.log.Debugf("Ingress %s/%s: Varnish Service %s not found",
ing.Namespace, ing.Name, varnishSvc)
return nil, nil return nil, nil
} }
worker.log.Debugf("Ingress %s/%s does not have annotation %s",
ing.Namespace, ing.Name, varnishSvcKey)
if len(svcs) == 1 { if len(svcs) == 1 {
worker.log.Debugf("Exactly one Varnish Ingress Service "+
"cluster-wide: %s", svcs[0])
return svcs[0], nil
}
svcs, err = worker.svc.List(varnishIngressSelector)
if err != nil {
return nil, err
}
if len(svcs) == 1 {
worker.log.Debugf("Exactly one Varnish Ingress Service "+
"in namespace %s: %s", worker.namespace, svcs[0])
return svcs[0], nil return svcs[0], nil
} }
return nil, nil return nil, nil
} }
func (worker *NamespaceWorker) ingBackend2Addrs( func (worker *NamespaceWorker) getIngsForVarnishSvc(
svc *api_v1.Service) ([]*extensions.Ingress, error) {
ings, err := worker.listers.ing.List(labels.Everything())
if err != nil {
return nil, err
}
allVarnishSvcs, err := worker.listers.svc.List(varnishIngressSelector)
if err != nil {
return nil, err
}
nsVarnishSvcs, err := worker.svc.List(varnishIngressSelector)
if err != nil {
return nil, err
}
ings4Svc := make([]*extensions.Ingress, 0)
for _, ing := range ings {
namespace := ing.Namespace
if namespace == "" {
namespace = "default"
}
if ingSvc, exists := ing.Annotations[varnishSvcKey]; exists {
targetNs, targetSvc, err :=
cache.SplitMetaNamespaceKey(ingSvc)
if err != nil {
return nil, err
}
if targetNs == "" {
targetNs = namespace
}
if targetNs == svc.Namespace && targetSvc == svc.Name {
ings4Svc = append(ings4Svc, ing)
}
} else if len(allVarnishSvcs) == 1 {
ings4Svc = append(ings4Svc, ing)
} else if ing.Namespace == svc.Namespace &&
len(nsVarnishSvcs) == 1 {
ings4Svc = append(ings4Svc, ing)
}
}
return ings4Svc, nil
}
func ingMergeError(ings []*extensions.Ingress) error {
host2ing := make(map[string]*extensions.Ingress)
var ingWdefBackend *extensions.Ingress
for _, ing := range ings {
if ing.Spec.Backend != nil {
if ingWdefBackend != nil {
return fmt.Errorf("Default backend configured "+
"in more than one Ingress: %s/%s and "+
"%s/%s", ing.Namespace, ing.Name,
ingWdefBackend.Namespace,
ingWdefBackend.Name)
}
ingWdefBackend = ing
}
for _, rule := range ing.Spec.Rules {
if otherIng, exists := host2ing[rule.Host]; exists {
return fmt.Errorf("Host '%s' named in rules "+
"for more than one Ingress: %s/%s and "+
"%s/%s", rule.Host, otherIng.Namespace,
otherIng.Name, ing.Namespace, ing.Name)
}
host2ing[rule.Host] = ing
}
}
return nil
}
func (worker *NamespaceWorker) ingBackend2Addrs(namespace string,
backend extensions.IngressBackend) (addrs []vcl.Address, err error) { backend extensions.IngressBackend) (addrs []vcl.Address, err error) {
svc, err := worker.svc.Get(backend.ServiceName) nsLister := worker.listers.svc.Services(namespace)
svc, err := nsLister.Get(backend.ServiceName)
if err != nil { if err != nil {
return return
} }
...@@ -142,19 +242,23 @@ func getVCLProbe(probe *vcr_v1alpha1.ProbeSpec) *vcl.Probe { ...@@ -142,19 +242,23 @@ func getVCLProbe(probe *vcr_v1alpha1.ProbeSpec) *vcl.Probe {
return vclProbe return vclProbe
} }
func (worker *NamespaceWorker) getVCLSvc(svcName string, func (worker *NamespaceWorker) getVCLSvc(svcNamespace string, svcName string,
addrs []vcl.Address) (vcl.Service, error) { addrs []vcl.Address) (vcl.Service, *vcr_v1alpha1.BackendConfig, error) {
if svcNamespace == "" {
svcNamespace = "default"
}
vclSvc := vcl.Service{ vclSvc := vcl.Service{
Name: svcName, Name: svcNamespace + "/" + svcName,
Addresses: addrs, Addresses: addrs,
} }
bcfgs, err := worker.bcfg.List(labels.Everything()) nsLister := worker.listers.bcfg.BackendConfigs(svcNamespace)
bcfgs, err := nsLister.List(labels.Everything())
if err != nil { if err != nil {
if errors.IsNotFound(err) { if errors.IsNotFound(err) {
return vclSvc, nil return vclSvc, nil, nil
} }
return vclSvc, err return vclSvc, nil, err
} }
var bcfg *vcr_v1alpha1.BackendConfig var bcfg *vcr_v1alpha1.BackendConfig
BCfgs: BCfgs:
...@@ -168,7 +272,7 @@ BCfgs: ...@@ -168,7 +272,7 @@ BCfgs:
} }
} }
if bcfg == nil { if bcfg == nil {
return vclSvc, nil return vclSvc, nil, nil
} }
if bcfg.Spec.Director != nil { if bcfg.Spec.Director != nil {
vclSvc.Director = &vcl.Director{ vclSvc.Director = &vcl.Director{
...@@ -194,35 +298,50 @@ BCfgs: ...@@ -194,35 +298,50 @@ BCfgs:
if bcfg.Spec.ProxyHeader != nil { if bcfg.Spec.ProxyHeader != nil {
vclSvc.ProxyHeader = uint8(*bcfg.Spec.ProxyHeader) vclSvc.ProxyHeader = uint8(*bcfg.Spec.ProxyHeader)
} }
return vclSvc, nil return vclSvc, bcfg, nil
} }
func (worker *NamespaceWorker) ing2VCLSpec( func (worker *NamespaceWorker) ings2VCLSpec(
ing *extensions.Ingress) (vcl.Spec, error) { ings []*extensions.Ingress) (vcl.Spec,
map[string]*vcr_v1alpha1.BackendConfig, error) {
vclSpec := vcl.Spec{} vclSpec := vcl.Spec{}
vclSpec.AllServices = make(map[string]vcl.Service) vclSpec.AllServices = make(map[string]vcl.Service)
bcfgs := make(map[string]*vcr_v1alpha1.BackendConfig)
for _, ing := range ings {
namespace := ing.Namespace
if namespace == "" {
namespace = "default"
}
if ing.Spec.TLS != nil && len(ing.Spec.TLS) > 0 { if ing.Spec.TLS != nil && len(ing.Spec.TLS) > 0 {
worker.log.Warnf("TLS config currently ignored in Ingress %s", worker.log.Warnf("TLS config currently ignored in "+
ing.ObjectMeta.Name) "Ingress %s/%s", namespace, ing.Name)
} }
if ing.Spec.Backend != nil { if ing.Spec.Backend != nil {
if vclSpec.DefaultService.Name != "" {
panic("More than one Ingress default backend")
}
backend := ing.Spec.Backend backend := ing.Spec.Backend
addrs, err := worker.ingBackend2Addrs(*backend) addrs, err := worker.ingBackend2Addrs(namespace,
*backend)
if err != nil { if err != nil {
return vclSpec, err return vclSpec, bcfgs, err
} }
vclSvc, err := worker.getVCLSvc(backend.ServiceName, addrs) vclSvc, bcfg, err := worker.getVCLSvc(namespace,
backend.ServiceName, addrs)
if err != nil { if err != nil {
return vclSpec, err return vclSpec, bcfgs, err
} }
vclSpec.DefaultService = vclSvc vclSpec.DefaultService = vclSvc
vclSpec.AllServices[backend.ServiceName] = vclSvc vclSpec.AllServices[namespace+"/"+backend.ServiceName] = vclSvc
if bcfg != nil {
bcfgs[vclSvc.Name] = bcfg
}
} }
for _, rule := range ing.Spec.Rules { for _, rule := range ing.Spec.Rules {
// XXX this should not be an error
if rule.Host == "" { if rule.Host == "" {
return vclSpec, fmt.Errorf("Ingress rule contains " + return vclSpec, bcfgs,
"empty Host") fmt.Errorf("Ingress rule contains empty Host")
} }
vclRule := vcl.Rule{Host: rule.Host} vclRule := vcl.Rule{Host: rule.Host}
vclRule.PathMap = make(map[string]vcl.Service) vclRule.PathMap = make(map[string]vcl.Service)
...@@ -231,21 +350,27 @@ func (worker *NamespaceWorker) ing2VCLSpec( ...@@ -231,21 +350,27 @@ func (worker *NamespaceWorker) ing2VCLSpec(
continue continue
} }
for _, path := range rule.IngressRuleValue.HTTP.Paths { for _, path := range rule.IngressRuleValue.HTTP.Paths {
addrs, err := worker.ingBackend2Addrs(path.Backend) addrs, err := worker.ingBackend2Addrs(
namespace, path.Backend)
if err != nil { if err != nil {
return vclSpec, err return vclSpec, bcfgs, err
} }
vclSvc, err := worker.getVCLSvc( vclSvc, bcfg, err := worker.getVCLSvc(namespace,
path.Backend.ServiceName, addrs) path.Backend.ServiceName, addrs)
if err != nil { if err != nil {
return vclSpec, nil return vclSpec, bcfgs, err
} }
vclRule.PathMap[path.Path] = vclSvc vclRule.PathMap[path.Path] = vclSvc
vclSpec.AllServices[path.Backend.ServiceName] = vclSvc vclSpec.AllServices[namespace+"/"+
path.Backend.ServiceName] = vclSvc
if bcfg != nil {
bcfgs[vclSvc.Name] = bcfg
}
} }
vclSpec.Rules = append(vclSpec.Rules, vclRule) vclSpec.Rules = append(vclSpec.Rules, vclRule)
} }
return vclSpec, nil }
return vclSpec, bcfgs, nil
} }
func (worker *NamespaceWorker) configSharding(spec *vcl.Spec, func (worker *NamespaceWorker) configSharding(spec *vcl.Spec,
...@@ -623,15 +748,6 @@ func (worker *NamespaceWorker) configRewrites(spec *vcl.Spec, ...@@ -623,15 +748,6 @@ func (worker *NamespaceWorker) configRewrites(spec *vcl.Spec,
return nil return nil
} }
func (worker *NamespaceWorker) hasIngress(svc *api_v1.Service,
ing *extensions.Ingress, spec vcl.Spec) bool {
svcKey := svc.Namespace + "/" + svc.Name
ingKey := ing.Namespace + "/" + ing.Name
return worker.vController.HasIngress(svcKey, ingKey, string(ing.UID),
spec)
}
func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error { func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error {
ingKey := ing.ObjectMeta.Namespace + "/" + ing.ObjectMeta.Name ingKey := ing.ObjectMeta.Namespace + "/" + ing.ObjectMeta.Name
worker.log.Infof("Adding or Updating Ingress: %s", ingKey) worker.log.Infof("Adding or Updating Ingress: %s", ingKey)
...@@ -646,13 +762,30 @@ func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error { ...@@ -646,13 +762,30 @@ func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error {
ing.Namespace, ing.Name) ing.Namespace, ing.Name)
} }
svcKey := svc.Namespace + "/" + svc.Name svcKey := svc.Namespace + "/" + svc.Name
worker.log.Infof("Ingress %s to be implemented by Varnish Service %s", worker.log.Infof("Ingress %s configured for Varnish Service %s", ingKey,
ingKey, svcKey) svcKey)
vclSpec, err := worker.ing2VCLSpec(ing) ings, err := worker.getIngsForVarnishSvc(svc)
if err != nil {
return nil
}
if len(ings) == 0 {
worker.log.Infof("No Ingresses to be implemented by Varnish "+
"Service %s, setting to not ready", svcKey)
return worker.vController.SetNotReady(svcKey)
}
ingNames := make([]string, len(ings))
for i, ingress := range ings {
ingNames[i] = ingress.Namespace + "/" + ingress.Name
}
worker.log.Infof("Ingresses implemented by Varnish Service %s: %v",
svcKey, ingNames)
vclSpec, bcfgs, err := worker.ings2VCLSpec(ings)
if err != nil { if err != nil {
return err return err
} }
worker.log.Debugf("VCL spec generated from the Ingresses: %v", vclSpec)
var vcfg *vcr_v1alpha1.VarnishConfig var vcfg *vcr_v1alpha1.VarnishConfig
worker.log.Debugf("Listing VarnishConfigs in namespace %s", worker.log.Debugf("Listing VarnishConfigs in namespace %s",
...@@ -693,23 +826,52 @@ func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error { ...@@ -693,23 +826,52 @@ func (worker *NamespaceWorker) addOrUpdateIng(ing *extensions.Ingress) error {
"%s/%s", svc.Namespace, svc.Name) "%s/%s", svc.Namespace, svc.Name)
} }
worker.log.Debugf("Check if Ingress is loaded: key=%s uuid=%s hash=%0x", ingsMeta := make(map[string]varnish.Meta)
ingKey, string(ing.UID), vclSpec.Canonical().DeepHash()) for _, ing := range ings {
if worker.hasIngress(svc, ing, vclSpec) { metaDatum := varnish.Meta{
worker.log.Infof("Ingress %s uid=%s hash=%0x already loaded", Key: ing.Namespace + "/" + ing.Name,
ingKey, ing.UID, vclSpec.Canonical().DeepHash()) UID: string(ing.UID),
Ver: ing.ResourceVersion,
}
ingsMeta[metaDatum.Key] = metaDatum
}
var vcfgMeta varnish.Meta
if vcfg != nil {
vcfgMeta = varnish.Meta{
Key: vcfg.Namespace + "/" + vcfg.Name,
UID: string(vcfg.UID),
Ver: vcfg.ResourceVersion,
}
}
bcfgMeta := make(map[string]varnish.Meta)
for name, bcfg := range bcfgs {
bcfgMeta[name] = varnish.Meta{
Key: bcfg.Namespace + "/" + bcfg.Name,
UID: string(bcfg.UID),
Ver: bcfg.ResourceVersion,
}
}
worker.log.Debugf("Check if config is loaded: hash=%0x "+
"ingressMetaData=%+v vcfgMetaData=%+v bcfgMetaData=%+v",
vclSpec.Canonical().DeepHash(), ingsMeta, vcfgMeta, bcfgMeta)
if worker.vController.HasConfig(svcKey, vclSpec, ingsMeta, vcfgMeta,
bcfgMeta) {
worker.log.Infof("Varnish Service %s: config already "+
"loaded: hash=%0x", svcKey,
vclSpec.Canonical().DeepHash())
return nil return nil
} }
worker.log.Debugf("Update config svc=%s ingressMetaData=%+v "+
worker.log.Debugf("Update Ingress key=%s svc=%s uuid=%s: %+v", ingKey, "vcfgMetaData=%+v bcfgMetaData=%+v: %+v", svcKey, ingsMeta,
svcKey, string(ing.ObjectMeta.UID), vclSpec) vcfgMeta, bcfgMeta, vclSpec)
err = worker.vController.Update(svcKey, ingKey, err = worker.vController.Update(svcKey, vclSpec, ingsMeta, vcfgMeta,
string(ing.ObjectMeta.UID), vclSpec) bcfgMeta)
if err != nil { if err != nil {
return err return err
} }
worker.log.Debugf("Updated Ingress key=%s uuid=%s svc=%s: %+v", ingKey, worker.log.Debugf("Updated config svc=%s ingressMetaData=%+v "+
string(ing.ObjectMeta.UID), svcKey, vclSpec) "vcfgMetaData=%+v bcfgMetaData=%+v: %+v", svcKey, ingsMeta,
vcfgMeta, bcfgMeta, vclSpec)
return nil return nil
} }
...@@ -749,21 +911,8 @@ func (worker *NamespaceWorker) updateIng(key string) error { ...@@ -749,21 +911,8 @@ func (worker *NamespaceWorker) updateIng(key string) error {
func (worker *NamespaceWorker) deleteIng(obj interface{}) error { func (worker *NamespaceWorker) deleteIng(obj interface{}) error {
ing, ok := obj.(*extensions.Ingress) ing, ok := obj.(*extensions.Ingress)
if !ok || ing == nil { if !ok || ing == nil {
// XXX should clean up Varnish config nevertheless
worker.log.Warnf("Delete Ingress: not found: %v", obj) worker.log.Warnf("Delete Ingress: not found: %v", obj)
return nil return nil
} }
svc, err := worker.getVarnishSvcForIng(ing) return worker.addOrUpdateIng(ing)
if err != nil {
return err
}
if svc == nil {
return fmt.Errorf("No Varnish Service found for Ingress %s/%s",
ing.Namespace, ing.Name)
}
ingKey := ing.Namespace + "/" + ing.Name
svcKey := svc.Namespace + "/" + svc.Name
worker.log.Infof("Deleting Ingress %s (Varnish service %s):", ingKey,
svcKey)
return worker.vController.DeleteIngress(svcKey, ingKey)
} }
/*
* Copyright (c) 2019 UPLEX Nils Goroll Systemoptimierung
* All rights reserved
*
* Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
package controller
import (
"testing"
extensions "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var ing1 = &extensions.Ingress{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "ing1",
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-svc2",
},
Rules: []extensions.IngressRule{
extensions.IngressRule{Host: "host1"},
},
},
}
var ing2 = &extensions.Ingress{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "ing2",
},
Spec: extensions.IngressSpec{
Backend: &extensions.IngressBackend{
ServiceName: "default-svc2",
},
Rules: []extensions.IngressRule{
extensions.IngressRule{Host: "host2"},
},
},
}
var ing3 = &extensions.Ingress{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "ing3",
},
Spec: extensions.IngressSpec{
Rules: []extensions.IngressRule{
extensions.IngressRule{Host: "host1"},
extensions.IngressRule{Host: "host2"},
},
},
}
func TestIngressMergeError(t *testing.T) {
ings := []*extensions.Ingress{ing1, ing2}
if err := ingMergeError(ings); err == nil {
t.Errorf("ingMergeError(): no error reported for more than " +
"one default backend")
} else if testing.Verbose() {
t.Logf("ingMergeError() returned as expected: %v", err)
}
ings = []*extensions.Ingress{ing2, ing3}
if err := ingMergeError(ings); err == nil {
t.Errorf("ingMergeError(): no error reported for overlapping " +
"Hosts")
} else if testing.Verbose() {
t.Logf("ingMergeError() returned as expected: %v", err)
}
}
...@@ -59,7 +59,8 @@ var ( ...@@ -59,7 +59,8 @@ var (
func (worker *NamespaceWorker) getServiceEndpoints( func (worker *NamespaceWorker) getServiceEndpoints(
svc *api_v1.Service) (ep *api_v1.Endpoints, err error) { svc *api_v1.Service) (ep *api_v1.Endpoints, err error) {
eps, err := worker.endp.List(labels.Everything()) nsLister := worker.listers.endp.Endpoints(svc.Namespace)
eps, err := nsLister.List(labels.Everything())
if err != nil { if err != nil {
return return
} }
......
...@@ -67,6 +67,7 @@ type NamespaceWorker struct { ...@@ -67,6 +67,7 @@ type NamespaceWorker struct {
vController *varnish.VarnishController vController *varnish.VarnishController
queue workqueue.RateLimitingInterface queue workqueue.RateLimitingInterface
stopChan chan struct{} stopChan chan struct{}
listers *Listers
ing ext_listers.IngressNamespaceLister ing ext_listers.IngressNamespaceLister
svc core_v1_listers.ServiceNamespaceLister svc core_v1_listers.ServiceNamespaceLister
endp core_v1_listers.EndpointsNamespaceLister endp core_v1_listers.EndpointsNamespaceLister
...@@ -341,6 +342,7 @@ func (qs *NamespaceQueues) next() { ...@@ -341,6 +342,7 @@ func (qs *NamespaceQueues) next() {
vController: qs.vController, vController: qs.vController,
queue: q, queue: q,
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
listers: qs.listers,
ing: qs.listers.ing.Ingresses(ns), ing: qs.listers.ing.Ingresses(ns),
svc: qs.listers.svc.Services(ns), svc: qs.listers.svc.Services(ns),
endp: qs.listers.endp.Endpoints(ns), endp: qs.listers.endp.Endpoints(ns),
......
...@@ -105,14 +105,27 @@ func (vadmErrs VarnishAdmErrors) Error() string { ...@@ -105,14 +105,27 @@ func (vadmErrs VarnishAdmErrors) Error() string {
return sb.String() return sb.String()
} }
// Meta encapsulates meta-data for the resource types that enter into
// a Varnish configuration: Ingress, VarnishConfig and BackendConfig.
//
// Key: namespace/name
// UID: UID field from the resource meta-data
// Ver: ResourceVersion field from the resource meta-data
type Meta struct {
Key string
UID string
Ver string
}
type vclSpec struct { type vclSpec struct {
spec vcl.Spec spec vcl.Spec
key string ings map[string]Meta
uid string vcfg Meta
bcfg map[string]Meta
} }
func (spec vclSpec) configName() string { func (spec vclSpec) configName() string {
name := fmt.Sprintf("%s%s_%s_%0x", ingressPrefix, spec.key, spec.uid, name := fmt.Sprintf("%s%0x", ingressPrefix,
spec.spec.Canonical().DeepHash()) spec.spec.Canonical().DeepHash())
return nonAlNum.ReplaceAllLiteralString(name, "_") return nonAlNum.ReplaceAllLiteralString(name, "_")
} }
...@@ -563,57 +576,50 @@ func (vc *VarnishController) updateBeGauges() { ...@@ -563,57 +576,50 @@ func (vc *VarnishController) updateBeGauges() {
beEndpsGauge.Set(float64(nBeEndps)) beEndpsGauge.Set(float64(nBeEndps))
} }
// Update a Varnish Service to implement an Ingress. // Update a Varnish Service to implement an configuration.
// //
// svcKey: namespace/name key for the Service // svcKey: namespace/name key for the Service
// ingKey: namespace/name key for the Ingress // spec: VCL spec corresponding to the configuration
// uid: UID field from the Ingress // ingsMeta: Ingress meta-data
// spec: VCL spec corresponding to the Ingress definition // vcfgMeta: VarnishConfig meta-data
func (vc *VarnishController) Update( // bcfgMeta: BackendConfig meta-data
svcKey, ingKey, uid string, spec vcl.Spec) error { func (vc *VarnishController) Update(svcKey string, spec vcl.Spec,
ingsMeta map[string]Meta, vcfgMeta Meta,
bcfgMeta map[string]Meta) error {
svc, exists := vc.svcs[svcKey] svc, exists := vc.svcs[svcKey]
if !exists { if !exists {
svc = &varnishSvc{instances: make([]*varnishInst, 0)} svc = &varnishSvc{instances: make([]*varnishInst, 0)}
vc.svcs[svcKey] = svc vc.svcs[svcKey] = svc
svcsGauge.Inc() svcsGauge.Inc()
vc.log.Infof("Added Varnish service definition %s for Ingress "+ vc.log.Infof("Added Varnish service definition %s", svcKey)
"%s uid=%s", svcKey, ingKey, uid)
} }
svc.cfgLoaded = false svc.cfgLoaded = false
if svc.spec == nil { if svc.spec == nil {
svc.spec = &vclSpec{} svc.spec = &vclSpec{}
} }
svc.spec.key = ingKey
svc.spec.uid = uid
svc.spec.spec = spec svc.spec.spec = spec
svc.spec.ings = ingsMeta
svc.spec.vcfg = vcfgMeta
svc.spec.bcfg = bcfgMeta
vc.updateBeGauges() vc.updateBeGauges()
if len(svc.instances) == 0 { if len(svc.instances) == 0 {
return fmt.Errorf("Ingress %s uid=%s: Currently no known "+ return fmt.Errorf("Currently no known endpoints for Varnish "+
"endpoints for Varnish service %s", ingKey, uid, svcKey) "service %s", svcKey)
} }
return vc.updateVarnishSvc(svcKey) return vc.updateVarnishSvc(svcKey)
} }
// DeleteIngress is called for the Delete event on an Ingress, and // SetNotReady may be called on the Delete event on an Ingress, if no
// syncs its effect for a Varnish Service. // Ingresses remain that are to be implemented by a Varnish Service.
// // The Service is set to the not ready state, by relabelling VCL so
// svcKey: namespace/name key for the Varnish Service // that readiness checks are not answered with status 200.
// ingKey: namespace/name key for the Ingress func (vc *VarnishController) SetNotReady(svcKey string) error {
//
// We currently only support one Ingress definition at a time for a
// Varnish Service, so deleting the Ingress means that we set Varnish
// instances to the not ready state.
func (vc *VarnishController) DeleteIngress(svcKey, ingKey string) error {
svc, ok := vc.svcs[svcKey] svc, ok := vc.svcs[svcKey]
if !ok { if !ok {
return fmt.Errorf("Delete Ingress %s: no known Varnish service", return fmt.Errorf("Set Varnish Service not ready: %s unknown",
ingKey) svcKey)
}
if svc.spec != nil && svc.spec.key != ingKey {
return fmt.Errorf("Delete Ingress %s: Ingress %s is assigned "+
"to Varnish service %s", ingKey, svc.spec.key, svcKey)
} }
svc.spec = nil svc.spec = nil
...@@ -636,24 +642,57 @@ func (vc *VarnishController) DeleteIngress(svcKey, ingKey string) error { ...@@ -636,24 +642,57 @@ func (vc *VarnishController) DeleteIngress(svcKey, ingKey string) error {
return errs return errs
} }
// HasIngress returns true iff an Ingress definition is already loaded // HasConfig returns true iff a configuration is already loaded for a
// for a Varnish Service (so a new sync attempt is not necessary). // Varnish Service (so a new sync attempt is not necessary).
// //
// svcKey: namespace/name key for the Varnish Service // svcKey: namespace/name key for the Varnish Service
// ingKey: namespace/name key for the Ingress // spec: VCL specification derived from the configuration
// uid: UID field from the Ingress // ingsMeta: Ingress meta-data
// spec: VCL specification derived from the Ingress // vcfgMeta: VarnishConfig meta-data
func (vc *VarnishController) HasIngress(svcKey, ingKey, uid string, // bcfgMeta: BackendConfig meta-data
spec vcl.Spec) bool { func (vc *VarnishController) HasConfig(svcKey string, spec vcl.Spec,
ingsMeta map[string]Meta, vcfgMeta Meta,
bcfgMeta map[string]Meta) bool {
svc, ok := vc.svcs[svcKey] svc, ok := vc.svcs[svcKey]
if !ok { if !ok {
return false return false
} }
return svc.cfgLoaded && if !svc.cfgLoaded {
svc.spec.key == ingKey && return false
svc.spec.uid == uid && }
reflect.DeepEqual(svc.spec.spec.Canonical(), spec.Canonical()) if len(ingsMeta) != len(svc.spec.ings) {
return false
}
if len(bcfgMeta) != len(svc.spec.bcfg) {
return false
}
if vcfgMeta.Key != svc.spec.vcfg.Key ||
vcfgMeta.UID != svc.spec.vcfg.UID ||
vcfgMeta.Ver != svc.spec.vcfg.Ver {
return false
}
for k, v := range ingsMeta {
specIng, exists := svc.spec.ings[k]
if !exists {
return false
}
if specIng.Key != v.Key || specIng.UID != v.UID ||
specIng.Ver != v.Ver {
return false
}
}
for k, v := range bcfgMeta {
specBcfg, exists := svc.spec.bcfg[k]
if !exists {
return false
}
if specBcfg.Key != v.Key || specBcfg.UID != v.UID ||
specBcfg.Ver != v.Ver {
return false
}
}
return reflect.DeepEqual(svc.spec.spec.Canonical(), spec.Canonical())
} }
// SetAdmSecret stores the Secret data identified by the // SetAdmSecret stores the Secret data identified by the
......
...@@ -30,7 +30,10 @@ package varnish ...@@ -30,7 +30,10 @@ package varnish
import ( import (
"fmt" "fmt"
"strings"
"testing" "testing"
"code.uplex.de/uplex-varnish/k8s-ingress/pkg/varnish/vcl"
) )
func TestVarnishAdmError(t *testing.T) { func TestVarnishAdmError(t *testing.T) {
...@@ -62,3 +65,253 @@ func TestVarnishAdmError(t *testing.T) { ...@@ -62,3 +65,253 @@ func TestVarnishAdmError(t *testing.T) {
t.Errorf("VarnishAdmErrors.Error() want=%s got=%s", want, err) t.Errorf("VarnishAdmErrors.Error() want=%s got=%s", want, err)
} }
} }
// Test data for HasConfig()
var teaSvc = vcl.Service{
Name: "tea-svc",
Addresses: []vcl.Address{
{
IP: "192.0.2.1",
Port: 80,
},
{
IP: "192.0.2.2",
Port: 80,
},
{
IP: "192.0.2.3",
Port: 80,
},
},
}
var coffeeSvc = vcl.Service{
Name: "coffee-svc",
Addresses: []vcl.Address{
{
IP: "192.0.2.4",
Port: 80,
},
{
IP: "192.0.2.5",
Port: 80,
},
},
}
var cafeSpec = vcl.Spec{
DefaultService: vcl.Service{},
Rules: []vcl.Rule{{
Host: "cafe.example.com",
PathMap: map[string]vcl.Service{
"/tea": teaSvc,
"/coffee": coffeeSvc,
},
}},
AllServices: map[string]vcl.Service{
"tea-svc": teaSvc,
"coffee-svc": coffeeSvc,
},
}
var teaSvcShuf = vcl.Service{
Name: "tea-svc",
Addresses: []vcl.Address{
{
IP: "192.0.2.3",
Port: 80,
},
{
IP: "192.0.2.1",
Port: 80,
},
{
IP: "192.0.2.2",
Port: 80,
},
},
}
var coffeeSvcShuf = vcl.Service{
Name: "coffee-svc",
Addresses: []vcl.Address{
{
IP: "192.0.2.5",
Port: 80,
},
{
IP: "192.0.2.4",
Port: 80,
},
},
}
var cafeSpecShuf = vcl.Spec{
DefaultService: vcl.Service{},
Rules: []vcl.Rule{{
Host: "cafe.example.com",
PathMap: map[string]vcl.Service{
"/coffee": coffeeSvcShuf,
"/tea": teaSvcShuf,
},
}},
AllServices: map[string]vcl.Service{
"coffee-svc": coffeeSvcShuf,
"tea-svc": teaSvcShuf,
},
}
var ingsMeta = map[string]Meta{
"default/cafe": Meta{
Key: "default/cafe",
UID: "123e4567-e89b-12d3-a456-426655440000",
Ver: "123456",
},
"ns/name": Meta{
Key: "ns/name",
UID: "00112233-4455-6677-8899-aabbccddeeff",
Ver: "654321",
},
"kube-system/ingress": Meta{
Key: "kube-system/ingress",
UID: "6ba7b812-9dad-11d1-80b4-00c04fd430c8",
Ver: "987654",
},
}
var bcfgsMeta = map[string]Meta{
"coffee-svc": Meta{
Key: "default/coffee-svc-cfg",
UID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
Ver: "010101",
},
"tea-svc": Meta{
Key: "ns/tea-svc-cfg",
UID: "6ba7b811-9dad-11d1-80b4-00c04fd430c8",
Ver: "909090",
},
}
var vcfgMeta = Meta{
Key: "default/varnish-cfg",
UID: "6ba7b814-9dad-11d1-80b4-00c04fd430c8",
Ver: "37337",
}
func TestHasConfig(t *testing.T) {
spec := vclSpec{
spec: cafeSpec,
ings: ingsMeta,
vcfg: vcfgMeta,
bcfg: bcfgsMeta,
}
vSvc := varnishSvc{
spec: &spec,
cfgLoaded: true,
}
vc := VarnishController{
svcs: map[string]*varnishSvc{"default/cafe-ingress": &vSvc},
}
svcKey := "default/cafe-ingress"
if !vc.HasConfig(svcKey, cafeSpecShuf, ingsMeta, vcfgMeta, bcfgsMeta) {
t.Errorf("HasConfig() got:false want:true")
}
if vc.HasConfig("ns/name", cafeSpecShuf, ingsMeta, vcfgMeta,
bcfgsMeta) {
t.Errorf("HasConfig(unknown Service) got:true want:false")
}
vSvc.cfgLoaded = false
if vc.HasConfig(svcKey, cafeSpecShuf, ingsMeta, vcfgMeta, bcfgsMeta) {
t.Errorf("HasConfig(cfgLoaded=false) got:true want:false")
}
vSvc.cfgLoaded = true
otherVcfg := vcfgMeta
otherVcfg.Ver = "37338"
if vc.HasConfig(svcKey, cafeSpecShuf, ingsMeta, otherVcfg, bcfgsMeta) {
t.Errorf("HasConfig(changed VarnishConfig) got:true want:false")
}
otherIngs := make(map[string]Meta)
for k, v := range ingsMeta {
otherIngs[k] = v
}
otherIngs["key"] = Meta{}
if vc.HasConfig(svcKey, cafeSpecShuf, otherIngs, vcfgMeta, bcfgsMeta) {
t.Errorf("HasConfig(more Ingresses) got:true want:false")
}
delete(otherIngs, "key")
otherIngs["default/cafe"] = Meta{
Key: "default/cafe",
UID: "123e4567-e89b-12d3-a456-426655440000",
Ver: "123457",
}
if vc.HasConfig(svcKey, cafeSpecShuf, otherIngs, vcfgMeta, bcfgsMeta) {
t.Errorf("HasConfig(changed Ingresses) got:true want:false")
}
delete(otherIngs, "default/cafe")
if vc.HasConfig(svcKey, cafeSpecShuf, otherIngs, vcfgMeta, bcfgsMeta) {
t.Errorf("HasConfig(fewer Ingresses) got:true want:false")
}
otherBcfgs := make(map[string]Meta)
for k, v := range bcfgsMeta {
otherBcfgs[k] = v
}
otherBcfgs["key"] = Meta{}
if vc.HasConfig(svcKey, cafeSpecShuf, ingsMeta, vcfgMeta, otherBcfgs) {
t.Errorf("HasConfig(more BackendConfigs) got:true want:false")
}
delete(otherBcfgs, "key")
otherBcfgs["coffee-svc"] = Meta{
Key: "default/coffee-svc-cfg",
UID: "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
Ver: "010102",
}
if vc.HasConfig(svcKey, cafeSpecShuf, ingsMeta, vcfgMeta, otherBcfgs) {
t.Errorf("HasConfig(changed BackendConfigs) got:true want:false")
}
delete(otherBcfgs, "coffee-svc")
if vc.HasConfig(svcKey, cafeSpecShuf, ingsMeta, vcfgMeta, otherBcfgs) {
t.Errorf("HasConfig(fewer BackendConfigs) got:true want:false")
}
}
func TestConfigName(t *testing.T) {
spec := vclSpec{spec: cafeSpec}
name1 := spec.configName()
if !strings.HasPrefix(name1, ingressPrefix) {
t.Errorf("configName(): name %s does not have prefix %s",
name1, ingressPrefix)
}
spec = vclSpec{spec: cafeSpecShuf}
name2 := spec.configName()
if !strings.HasPrefix(name2, ingressPrefix) {
t.Errorf("configName(): name %s does not have prefix %s",
name2, ingressPrefix)
}
if name1 != name2 {
t.Errorf("configName(): equivalent specs have different names:"+
"'%s' '%s'", name1, name2)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment