Skip to content

Commit ab56e15

Browse files
committed
MAJOR: CR: Add core.haproxy.org/v1alpha1.Backend for backend config
A backend CR can be assigned to a service by annotating the latter via "cr-backend" annotation
1 parent ff36238 commit ab56e15

File tree

22 files changed

+1530
-56
lines changed

22 files changed

+1530
-56
lines changed

controller/annotations/models.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,19 @@ import (
99
"github.com/haproxytech/kubernetes-ingress/controller/store"
1010
)
1111

12+
// ModelBackend takes an annotation holding the path of a backend cr and returns corresponding Backend model
13+
func ModelBackend(name, defaultNS string, k store.K8s, annotations ...map[string]string) (backend *models.Backend, err error) {
14+
b, modelErr := model(name, defaultNS, 3, k, annotations...)
15+
if modelErr != nil {
16+
err = modelErr
17+
return
18+
}
19+
if b != nil {
20+
backend = b.(*models.Backend)
21+
}
22+
return
23+
}
24+
1225
// ModelDefaults takes an annotation holding the path of a defaults cr and returns corresponding Defaults model
1326
func ModelDefaults(name, defaultNS string, k store.K8s, annotations ...map[string]string) (defaults *models.Defaults, err error) {
1427
d, modelErr := model(name, defaultNS, 2, k, annotations...)
@@ -84,6 +97,12 @@ func model(name, defaultNS string, crType int, k store.K8s, annotations ...map[s
8497
return nil, fmt.Errorf("annotation %s: custom resource '%s/%s' doest not exist", name, crNS, crName)
8598
}
8699
return defaults, nil
100+
case 3:
101+
backend, backendOk := ns.CRs.Backends[crName]
102+
if !backendOk {
103+
return nil, fmt.Errorf("annotation %s: custom resource '%s/%s' doest not exist", name, crNS, crName)
104+
}
105+
return backend, nil
87106
}
88107
return nil, nil
89108
}

controller/crmanager.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func NewCRManager(s *store.K8s, restCfg *rest.Config, cacheResync time.Duration,
5858
}
5959
manager.RegisterCoreCR(NewGlobalCR())
6060
manager.RegisterCoreCR(NewDefaultsCR())
61+
manager.RegisterCoreCR(NewBackendCR())
6162
return manager
6263
}
6364

controller/crs.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type GlobalCR struct {
2828
type DefaultsCR struct {
2929
}
3030

31+
type BackendCR struct {
32+
}
33+
3134
func NewGlobalCR() GlobalCR {
3235
return GlobalCR{}
3336
}
@@ -36,6 +39,10 @@ func NewDefaultsCR() DefaultsCR {
3639
return DefaultsCR{}
3740
}
3841

42+
func NewBackendCR() BackendCR {
43+
return BackendCR{}
44+
}
45+
3946
func (c GlobalCR) GetKind() string {
4047
return "Global"
4148
}
@@ -129,3 +136,49 @@ func (c DefaultsCR) ProcessEvent(s *store.K8s, job SyncDataEvent) bool {
129136
ns.CRs.Defaults[job.Name] = data.Spec.Config
130137
return true
131138
}
139+
140+
func (c BackendCR) GetKind() string {
141+
return "Backend"
142+
}
143+
144+
func (c BackendCR) GetInformer(eventChan chan SyncDataEvent, factory informers.SharedInformerFactory) cache.SharedIndexInformer {
145+
informer := factory.Core().V1alpha1().Backends().Informer()
146+
147+
sendToChannel := func(eventChan chan SyncDataEvent, object interface{}, status store.Status) {
148+
data := object.(*corev1alpha1.Backend)
149+
logger.Debugf("%s %s: %s", data.GetNamespace(), status, data.GetName())
150+
if status == DELETED {
151+
eventChan <- SyncDataEvent{SyncType: CUSTOM_RESOURCE, CRKind: c.GetKind(), Namespace: data.GetNamespace(), Name: data.GetName(), Data: nil}
152+
return
153+
}
154+
eventChan <- SyncDataEvent{SyncType: CUSTOM_RESOURCE, CRKind: c.GetKind(), Namespace: data.GetNamespace(), Name: data.GetName(), Data: data}
155+
}
156+
157+
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
158+
AddFunc: func(obj interface{}) {
159+
sendToChannel(eventChan, obj, store.ADDED)
160+
},
161+
UpdateFunc: func(oldObj, newObj interface{}) {
162+
sendToChannel(eventChan, newObj, store.MODIFIED)
163+
},
164+
DeleteFunc: func(obj interface{}) {
165+
sendToChannel(eventChan, obj, store.DELETED)
166+
},
167+
})
168+
return informer
169+
}
170+
171+
func (c BackendCR) ProcessEvent(s *store.K8s, job SyncDataEvent) bool {
172+
ns := s.GetNamespace(job.Namespace)
173+
if job.Data == nil {
174+
delete(ns.CRs.Backends, job.Name)
175+
return true
176+
}
177+
data, ok := job.Data.(*corev1alpha1.Backend)
178+
if !ok {
179+
logger.Warning(CoreGroupVersion + ": type mismatch with Backend kind")
180+
return false
181+
}
182+
ns.CRs.Backends[job.Name] = data.Spec.Config
183+
return true
184+
}

controller/ingress.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,25 +69,26 @@ func (c *HAProxyController) handleIngressPath(ingress *store.Ingress, host strin
6969
return
7070
}
7171
// Backend
72-
backend, backendReload, err := svc.HandleBackend(c.Client, c.Store)
72+
backendReload, err := svc.HandleBackend(c.Client, c.Store)
7373
if err != nil {
7474
return
7575
}
76+
backendName, _ := svc.GetBackendName()
7677
// Route
7778
var routeReload bool
7879
ingRoute := route.Route{
7980
Host: host,
8081
Path: path,
8182
HAProxyRules: ruleIDs,
82-
BackendName: backend.Name,
83+
BackendName: backendName,
8384
SSLPassthrough: sslPassthrough,
8485
}
8586

8687
routeACLAnn := annotations.String("route-acl", svc.GetService().Annotations)
8788
if routeACLAnn == "" {
88-
if _, ok := route.CustomRoutes[backend.Name]; ok {
89-
delete(route.CustomRoutes, backend.Name)
90-
logger.Debugf("Custom Route to backend '%s' deleted, reload required", backend.Name)
89+
if _, ok := route.CustomRoutes[backendName]; ok {
90+
delete(route.CustomRoutes, backendName)
91+
logger.Debugf("Custom Route to backend '%s' deleted, reload required", backendName)
9192
routeReload = true
9293
}
9394
err = route.AddHostPathRoute(ingRoute, c.Cfg.MapFiles)
@@ -97,7 +98,7 @@ func (c *HAProxyController) handleIngressPath(ingress *store.Ingress, host strin
9798
if err != nil {
9899
return
99100
}
100-
c.Cfg.ActiveBackends[backend.Name] = struct{}{}
101+
c.Cfg.ActiveBackends[backendName] = struct{}{}
101102
// Endpoints
102103
endpointsReload := svc.HandleEndpoints(c.Client, c.Store)
103104
return backendReload || endpointsReload || routeReload, err
@@ -121,26 +122,27 @@ func (c *HAProxyController) setDefaultService(ingress *store.Ingress, frontends
121122
if svc.GetStatus() == DELETED {
122123
return
123124
}
124-
backend, bdReload, err := svc.HandleBackend(c.Client, c.Store)
125+
bdReload, err := svc.HandleBackend(c.Client, c.Store)
125126
if err != nil {
126127
return
127128
}
128-
if frontend.DefaultBackend != backend.Name {
129+
backendName, _ := svc.GetBackendName()
130+
if frontend.DefaultBackend != backendName {
129131
if frontend.Name == c.Cfg.FrontHTTP {
130-
logger.Infof("Setting http default backend to '%s'", backend.Name)
132+
logger.Infof("Setting http default backend to '%s'", backendName)
131133
}
132134
for _, frontendName := range frontends {
133135
frontend, _ := c.Client.FrontendGet(frontendName)
134-
frontend.DefaultBackend = backend.Name
136+
frontend.DefaultBackend = backendName
135137
err = c.Client.FrontendEdit(frontend)
136138
if err != nil {
137139
return
138140
}
139141
ftReload = true
140-
logger.Debugf("Setting '%s' default backend to '%s'", frontendName, backend.Name)
142+
logger.Debugf("Setting '%s' default backend to '%s'", frontendName, backendName)
141143
}
142144
}
143-
c.Cfg.ActiveBackends[backend.Name] = struct{}{}
145+
c.Cfg.ActiveBackends[backendName] = struct{}{}
144146
endpointsReload := svc.HandleEndpoints(c.Client, c.Store)
145147
reload = bdReload || ftReload || endpointsReload
146148
return reload, err

controller/kubernetes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func (k *K8s) EventsNamespaces(channel chan SyncDataEvent, stop chan struct{}, i
129129
Global: make(map[string]*models.Global),
130130
Defaults: make(map[string]*models.Defaults),
131131
LogTargets: make(map[string]models.LogTargets),
132+
Backends: make(map[string]*models.Backend),
132133
},
133134
Status: status,
134135
}
@@ -152,6 +153,7 @@ func (k *K8s) EventsNamespaces(channel chan SyncDataEvent, stop chan struct{}, i
152153
Global: make(map[string]*models.Global),
153154
Defaults: make(map[string]*models.Defaults),
154155
LogTargets: make(map[string]models.LogTargets),
156+
Backends: make(map[string]*models.Backend),
155157
},
156158
Status: status,
157159
}

controller/service/service.go

Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type SvcContext struct {
3838
service *store.Service
3939
backend *models.Backend
4040
certs *haproxy.Certificates
41+
modeTCP bool
4142
newBackend bool
4243
}
4344

@@ -46,20 +47,13 @@ func NewCtx(k8s store.K8s, ingress *store.Ingress, path *store.IngressPath, cert
4647
if err != nil {
4748
return nil, err
4849
}
49-
backend := models.Backend{
50-
Mode: "http",
51-
DefaultServer: &models.DefaultServer{},
52-
}
53-
if tcpService {
54-
backend.Mode = "tcp"
55-
}
5650
return &SvcContext{
5751
store: k8s,
5852
ingress: ingress,
5953
path: path,
6054
service: service,
61-
backend: &backend,
6255
certs: certs,
56+
modeTCP: tcpService,
6357
}, nil
6458
}
6559

@@ -77,11 +71,12 @@ func (s *SvcContext) GetService() *store.Service {
7771
return s.service
7872
}
7973

80-
// setBackendName checks if servicePort provided in IngressPath exists and construct corresponding backend name
74+
// GetBackendName checks if servicePort provided in IngressPath exists and construct corresponding backend name
8175
// Backend name is in format "ServiceNS-ServiceName-PortName"
82-
func (s *SvcContext) setBackendName() error {
83-
if s.backend.Name != "" {
84-
return nil
76+
func (s *SvcContext) GetBackendName() (name string, err error) {
77+
if s.backend != nil && s.backend.Name != "" {
78+
name = s.backend.Name
79+
return
8580
}
8681
var svcPort store.ServicePort
8782
found := false
@@ -95,61 +90,91 @@ func (s *SvcContext) setBackendName() error {
9590
}
9691
if !found {
9792
if s.path.SvcPortString != "" {
98-
return fmt.Errorf("service %s: no service port matching '%s'", s.service.Name, s.path.SvcPortString)
93+
err = fmt.Errorf("service %s: no service port matching '%s'", s.service.Name, s.path.SvcPortString)
94+
} else {
95+
err = fmt.Errorf("service %s: no service port matching '%d'", s.service.Name, s.path.SvcPortInt)
9996
}
100-
return fmt.Errorf("service %s: no service port matching '%d'", s.service.Name, s.path.SvcPortInt)
97+
return
10198
}
10299
s.path.SvcPortResolved = &svcPort
103100
if svcPort.Name != "" {
104-
s.backend.Name = fmt.Sprintf("%s-%s-%s", s.service.Namespace, s.service.Name, svcPort.Name)
101+
name = fmt.Sprintf("%s-%s-%s", s.service.Namespace, s.service.Name, svcPort.Name)
105102
} else {
106-
s.backend.Name = fmt.Sprintf("%s-%s-%s", s.service.Namespace, s.service.Name, strconv.Itoa(int(svcPort.Port)))
103+
name = fmt.Sprintf("%s-%s-%s", s.service.Namespace, s.service.Name, strconv.Itoa(int(svcPort.Port)))
107104
}
108-
return nil
105+
return
109106
}
110107

111108
// HandleBackend processes a Service Context and creates/updates corresponding backend configuration in HAProxy
112-
func (s *SvcContext) HandleBackend(client api.HAProxyClient, store store.K8s) (backend *models.Backend, reload bool, err error) {
113-
backend = s.backend
114-
if err = s.setBackendName(); err != nil {
109+
func (s *SvcContext) HandleBackend(client api.HAProxyClient, store store.K8s) (reload bool, err error) {
110+
var backend, newBackend *models.Backend
111+
newBackend, err = s.getBackendModel(store)
112+
s.backend = newBackend
113+
if err != nil {
115114
return
116115
}
117116
// Get/Create Backend
118-
if s.service.DNS != "" {
119-
backend.DefaultServer = &models.DefaultServer{InitAddr: "last,libc,none"}
120-
}
121-
var oldBackend *models.Backend
122-
oldBackend, err = client.BackendGet(s.backend.Name)
123-
if err != nil {
124-
if err = client.BackendCreate(*backend); err != nil {
117+
backend, err = client.BackendGet(newBackend.Name)
118+
if err == nil {
119+
// Update Backend
120+
result := deep.Equal(newBackend, backend)
121+
if len(result) != 0 {
122+
if err = client.BackendEdit(*newBackend); err != nil {
123+
return
124+
}
125+
reload = true
126+
logger.Debugf("Ingress '%s/%s': backend '%s' updated: %s\nReload required", s.ingress.Namespace, s.ingress.Name, newBackend.Name, result)
127+
}
128+
} else {
129+
if err = client.BackendCreate(*newBackend); err != nil {
125130
return
126131
}
127132
s.newBackend = true
128133
reload = true
129-
logger.Debugf("Ingress '%s/%s': new backend '%s', reload required", s.ingress.Namespace, s.ingress.Name, backend.Name)
134+
logger.Debugf("Ingress '%s/%s': new backend '%s', reload required", s.ingress.Namespace, s.ingress.Name, newBackend.Name)
135+
}
136+
// config-snippet
137+
change, errSnipp := annotations.UpdateBackendCfgSnippet(client, newBackend.Name)
138+
logger.Error(errSnipp)
139+
if len(change) != 0 {
140+
reload = true
141+
logger.Debugf("Ingress '%s/%s': backend '%s' updated: %s\nReload required", s.ingress.Namespace, s.ingress.Name, newBackend.Name, change)
142+
}
143+
return
144+
}
145+
146+
// getBackendModel checks for a corresponding custom resource before falling back to annoations
147+
func (s *SvcContext) getBackendModel(store store.K8s) (*models.Backend, error) {
148+
var backend *models.Backend
149+
var err error
150+
crInuse := true
151+
backend, err = annotations.ModelBackend("cr-backend", s.service.Namespace, store, s.service.Annotations, s.ingress.Annotations, store.ConfigMaps.Main.Annotations)
152+
logger.Warning(err)
153+
if backend == nil {
154+
backend = &models.Backend{DefaultServer: &models.DefaultServer{}}
155+
crInuse = false
156+
}
157+
if s.modeTCP {
158+
backend.Mode = "tcp"
159+
} else {
160+
backend.Mode = "http"
161+
}
162+
if backend.Name, err = s.GetBackendName(); err != nil {
163+
return nil, err
164+
}
165+
if s.service.DNS != "" {
166+
backend.DefaultServer = &models.DefaultServer{InitAddr: "last,libc,none"}
167+
}
168+
if crInuse {
169+
return backend, nil
130170
}
131171
for _, a := range annotations.Backend(backend, store, s.certs) {
132172
err = a.Process(store, s.service.Annotations, s.ingress.Annotations, store.ConfigMaps.Main.Annotations)
133173
if err != nil {
134174
logger.Errorf("service '%s/%s': annotation '%s': %s", s.service.Namespace, s.service.Name, a.GetName(), err)
135175
}
136176
}
137-
// Update Backend
138-
result := deep.Equal(oldBackend, backend)
139-
if len(result) != 0 {
140-
if err = client.BackendEdit(*backend); err != nil {
141-
return
142-
}
143-
reload = true
144-
logger.Debugf("Ingress '%s/%s': backend '%s' updated: %s\nReload required", s.ingress.Namespace, s.ingress.Name, backend.Name, result)
145-
}
146-
change, errSnipp := annotations.UpdateBackendCfgSnippet(client, backend.Name)
147-
logger.Error(errSnipp)
148-
if len(change) != 0 {
149-
reload = true
150-
logger.Debugf("Ingress '%s/%s': backend '%s' updated: %s\nReload required", s.ingress.Namespace, s.ingress.Name, backend.Name, change)
151-
}
152-
return
177+
return backend, nil
153178
}
154179

155180
func getService(k8s store.K8s, namespace, name string) (*store.Service, error) {

controller/store/store.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ func (k K8s) GetNamespace(name string) *Namespace {
173173
Global: make(map[string]*models.Global),
174174
Defaults: make(map[string]*models.Defaults),
175175
LogTargets: make(map[string]models.LogTargets),
176+
Backends: make(map[string]*models.Backend),
176177
},
177178
Status: ADDED,
178179
}

controller/store/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type CustomResources struct {
8484
Global map[string]*models.Global
8585
Defaults map[string]*models.Defaults
8686
LogTargets map[string]models.LogTargets
87+
Backends map[string]*models.Backend
8788
}
8889

8990
type IngressClass struct {

0 commit comments

Comments
 (0)