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.
203 lines
5.3 KiB
203 lines
5.3 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 configuration
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
client_native "github.com/haproxytech/client-native/v6"
|
|
"github.com/haproxytech/client-native/v6/models"
|
|
"github.com/haproxytech/dataplaneapi/log"
|
|
)
|
|
|
|
type MapSync struct {
|
|
mapQuitChan chan struct{}
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
func NewMapSync() *MapSync {
|
|
return &MapSync{
|
|
mapQuitChan: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Stop stops maps syncing
|
|
func (ms *MapSync) Stop() {
|
|
ms.mapQuitChan <- struct{}{}
|
|
}
|
|
|
|
// SyncAll sync maps file entries with runtime maps entries for all configured files.
|
|
// Missing runtime entries are appended to the map file
|
|
func (ms *MapSync) SyncAll(client client_native.HAProxyClient) {
|
|
haproxyOptions := Get().HAProxy
|
|
|
|
d := time.Duration(haproxyOptions.UpdateMapFilesPeriod)
|
|
ticker := time.NewTicker(d * time.Second) //nolint:durationcheck
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
runtime, err := client.Runtime()
|
|
if err != nil {
|
|
log.Warning("show maps sync error: ", err.Error())
|
|
continue
|
|
}
|
|
maps, err := runtime.ShowMaps()
|
|
if err != nil {
|
|
log.Warning("show maps sync error: ", err.Error())
|
|
continue
|
|
}
|
|
for _, mp := range maps {
|
|
go func(mp *models.Map) {
|
|
_, err := ms.Sync(mp, client)
|
|
if err != nil {
|
|
log.Warning(err.Error())
|
|
}
|
|
}(mp)
|
|
}
|
|
case <-ms.mapQuitChan:
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sync syncs one map file to runtime entries
|
|
func (ms *MapSync) Sync(mp *models.Map, client client_native.HAProxyClient) (bool, error) {
|
|
ms.mu.Lock()
|
|
defer ms.mu.Unlock()
|
|
|
|
rawFile, err := os.Open(mp.File)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error reading map file: %s %s", mp.File, err.Error())
|
|
}
|
|
runtime, err := client.Runtime()
|
|
if err != nil {
|
|
return false, fmt.Errorf("getting runtime entries error: id: %s %s", mp.ID, err.Error())
|
|
}
|
|
fileEntries := runtime.ParseMapEntriesFromFile(rawFile, false)
|
|
sort.Slice(fileEntries, func(i, j int) bool { return fileEntries[i].Key < fileEntries[j].Key })
|
|
|
|
// runtime map entries
|
|
id := fmt.Sprintf("#%s", mp.ID)
|
|
runtimeEntries, err := runtime.ShowMapEntries(id)
|
|
if err != nil {
|
|
return false, fmt.Errorf("getting runtime entries error: id: %s %s", id, err.Error())
|
|
}
|
|
sort.Slice(runtimeEntries, func(i, j int) bool { return runtimeEntries[i].Key < runtimeEntries[j].Key })
|
|
|
|
if len(fileEntries) != len(runtimeEntries) {
|
|
return dumpRuntimeEntries(mp.File, runtimeEntries)
|
|
}
|
|
|
|
if !equalSomeEntries(fileEntries, runtimeEntries) {
|
|
return dumpRuntimeEntries(mp.File, runtimeEntries)
|
|
}
|
|
|
|
if !equal(fileEntries, runtimeEntries) {
|
|
return dumpRuntimeEntries(mp.File, runtimeEntries)
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// equalSomeEntries compares last few runtime entries with file entries
|
|
// if records differs, check is run against random entries
|
|
func equalSomeEntries(fEntries, rEntries models.MapEntries, index ...int) bool {
|
|
if len(fEntries) != len(rEntries) {
|
|
return false
|
|
}
|
|
|
|
max := 0
|
|
switch l := len(rEntries); {
|
|
case l > 19:
|
|
for i := l - 20; i < l; i++ {
|
|
if rEntries[i].Key != fEntries[i].Key || rEntries[i].Value != fEntries[i].Value {
|
|
return false
|
|
}
|
|
}
|
|
max = l - 19
|
|
case l == 0:
|
|
return true
|
|
default:
|
|
max = l
|
|
}
|
|
|
|
maxRandom := 10
|
|
if max < 10 {
|
|
maxRandom = max
|
|
}
|
|
|
|
for i := 0; i < maxRandom; i++ {
|
|
// There's no need for strong number generation, here, just need for performance
|
|
r := rand.Intn(max)
|
|
if len(index) > 0 {
|
|
r = index[0]
|
|
}
|
|
if rEntries[r].Key != fEntries[r].Key || rEntries[r].Value != fEntries[r].Value {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// equal compares runtime and map entries
|
|
// Returns true if all entries are same, otherwise returns false
|
|
func equal(a, b models.MapEntries) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i, v := range a {
|
|
// ID should not be compared, since it doesn't exists in file
|
|
if v.Key != b[i].Key || v.Value != b[i].Value {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// dumpRuntimeEntries dumps runtime entries into map file
|
|
// Returns true,nil if succeed, otherwise returns false,error
|
|
func dumpRuntimeEntries(file string, me models.MapEntries) (bool, error) {
|
|
f, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_TRUNC, 0o600)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error opening map file: %s %s", file, err.Error())
|
|
}
|
|
defer f.Close()
|
|
|
|
err = f.Truncate(0)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error truncating map file: %s %s", file, err.Error())
|
|
}
|
|
|
|
_, err = f.Seek(0, 0)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error setting file to offset: %s %s", file, err.Error())
|
|
}
|
|
|
|
for _, e := range me {
|
|
line := fmt.Sprintf("%s %s%s", e.Key, e.Value, "\n")
|
|
_, err = f.WriteString(line)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error writing map file: %s %s", file, err.Error())
|
|
}
|
|
}
|
|
log.Infof("map file %s synced with runtime entries", file)
|
|
return true, nil
|
|
}
|
|
|