HAProxy Data Plane API
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

238 lines
6.4 KiB

// Copyright 2019 HAProxy Technologies
//
// 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 discovery
import (
"sync"
"github.com/haproxytech/client-native/v6/configuration"
"github.com/haproxytech/dataplaneapi/haproxy"
"github.com/haproxytech/dataplaneapi/log"
)
// Using a simple mutex to avoid race conditions when multiple Service Discovery instances are trying to commit
// changes at the same time: need to be refactored.
var mutex = &sync.Mutex{}
// ServiceInstance specifies the needed information required from the service to provide for the ServiceDiscoveryInstance.
type ServiceInstance interface {
GetName() string
GetBackendName() string
GetFrom() string
Changed() bool
GetServers() []configuration.ServiceServer
}
type confService struct {
confService *configuration.Service
cleanup bool
}
type discoveryInstanceParams struct {
LogFields map[string]interface{}
SlotsGrowthType string
Allowlist []string
Denylist []string
ServerSlotsBase int
SlotsIncrement int
}
// ServiceDiscoveryInstance manages and updates all services of a single service discovery.
type ServiceDiscoveryInstance struct {
client configuration.Configuration
reloadAgent haproxy.IReloadAgent
services map[string]*confService
transactionID string
params discoveryInstanceParams
}
// NewServiceDiscoveryInstance creates a new ServiceDiscoveryInstance.
func NewServiceDiscoveryInstance(client configuration.Configuration, reloadAgent haproxy.IReloadAgent, params discoveryInstanceParams) *ServiceDiscoveryInstance {
return &ServiceDiscoveryInstance{
client: client,
reloadAgent: reloadAgent,
params: params,
services: make(map[string]*confService),
}
}
// UpdateParams updates the scaling params for each service associated with the service discovery.
func (s *ServiceDiscoveryInstance) UpdateParams(params discoveryInstanceParams) error {
s.params = params
for _, se := range s.services {
err := se.confService.UpdateScalingParams(configuration.ScalingParams{
BaseSlots: s.params.ServerSlotsBase,
SlotsGrowthType: s.params.SlotsGrowthType,
SlotsIncrement: s.params.SlotsIncrement,
})
if err != nil {
return err
}
}
return nil
}
// UpdateServices updates each service and persists the changes inside a single transaction.
func (s *ServiceDiscoveryInstance) UpdateServices(services []ServiceInstance) error {
mutex.Lock()
defer mutex.Unlock()
err := s.startTransaction()
if err != nil {
return err
}
reload := false
s.markForCleanUp()
for _, service := range services {
if s.serviceNotTracked(service.GetName()) {
continue
}
if !service.Changed() {
if se, ok := s.services[service.GetName()]; ok {
se.cleanup = false
}
continue
}
r, err := s.initService(service)
if err != nil {
s.deleteTransaction()
return err
}
reload = reload || r
se := s.services[service.GetName()]
r, err = se.confService.Update(service.GetServers())
if err != nil {
s.deleteTransaction()
return err
}
reload = reload || r
}
r := s.cleanup()
reload = reload || r
if reload {
if err := s.commitTransaction(); err != nil {
return err
}
s.reloadAgent.Reload()
return nil
}
s.deleteTransaction()
return nil
}
func (s *ServiceDiscoveryInstance) startTransaction() error {
version, err := s.client.GetVersion("")
if err != nil {
return err
}
transaction, err := s.client.StartTransaction(version)
if err != nil {
return err
}
s.transactionID = transaction.ID
return nil
}
func (s *ServiceDiscoveryInstance) markForCleanUp() {
for id := range s.services {
s.services[id].cleanup = true
}
}
func (s *ServiceDiscoveryInstance) serviceNotTracked(service string) bool {
if len(s.params.Allowlist) > 0 {
for _, se := range s.params.Allowlist {
if se == service {
return false
}
}
return true
}
for _, se := range s.params.Denylist {
if se == service {
return true
}
}
return false
}
func (s *ServiceDiscoveryInstance) initService(service ServiceInstance) (bool, error) {
if se, ok := s.services[service.GetName()]; ok {
se.confService.SetTransactionID(s.transactionID)
se.cleanup = false
return false, nil
}
se, err := s.client.NewService(service.GetBackendName(), configuration.ScalingParams{
BaseSlots: s.params.ServerSlotsBase,
SlotsGrowthType: s.params.SlotsGrowthType,
SlotsIncrement: s.params.SlotsIncrement,
})
if err != nil {
return false, err
}
reload, err := se.Init(s.transactionID, service.GetFrom())
if err != nil {
return false, err
}
s.services[service.GetName()] = &confService{
confService: se,
cleanup: false,
}
return reload, nil
}
func (s *ServiceDiscoveryInstance) cleanup() (reload bool) {
for service := range s.services {
if s.services[service].cleanup {
s.services[service].confService.SetTransactionID(s.transactionID)
changed, err := s.services[service].confService.Update([]configuration.ServiceServer{})
if err != nil {
s.logErrorf("service %s marked for clean-up cannot be updated, %s", service, err.Error())
continue
}
if changed {
s.logWarningf("service %s marked for clean-up, has not any more backend servers", service)
}
reload = reload || changed
}
}
return reload
}
func (s *ServiceDiscoveryInstance) deleteTransaction() {
if err := s.client.DeleteTransaction(s.transactionID); err != nil {
s.logWarningf("cannot delete transaction due to an error: %s", err.Error())
}
s.transactionID = ""
}
func (s *ServiceDiscoveryInstance) commitTransaction() error {
_, err := s.client.CommitTransaction(s.transactionID)
s.transactionID = ""
return err
}
func (s *ServiceDiscoveryInstance) logWarningf(format string, args ...interface{}) {
log.WithFieldsf(s.params.LogFields, log.WarnLevel, format, args...)
}
func (s *ServiceDiscoveryInstance) logErrorf(format string, args ...interface{}) {
log.WithFieldsf(s.params.LogFields, log.ErrorLevel, format, args...)
}