Merge pull request #52 from hosting-de-labs/feature/reload-backendconfigs

Feature: Reload Backends on SIGHUP
This commit is contained in:
Joachim Bauch 2020-10-06 15:37:59 +02:00 committed by GitHub
commit bef54339e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 239 additions and 33 deletions

View File

@ -83,6 +83,10 @@ func NewBackendClient(config *goconf.ConfigFile, maxConcurrentRequestsPerHost in
}, nil
}
func (b *BackendClient) Reload(config *goconf.ConfigFile) {
b.backends.Reload(config)
}
func (b *BackendClient) getPool(url *url.URL) (*HttpClientPool, error) {
b.mu.Lock()
defer b.mu.Unlock()

View File

@ -24,6 +24,7 @@ package signaling
import (
"log"
"net/url"
"reflect"
"strings"
"github.com/dlintw/goconf"
@ -70,40 +71,11 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration,
compat: true,
}
} else if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
seenIds := make(map[string]bool)
for _, id := range strings.Split(backendIds, ",") {
id = strings.TrimSpace(id)
if id == "" {
continue
for host, configuredBackends := range getConfiguredHosts(config) {
backends[host] = append(backends[host], configuredBackends...)
for _, be := range configuredBackends {
log.Printf("Backend %s added for %s", be.id, be.url)
}
if seenIds[id] {
continue
}
seenIds[id] = true
u, _ := config.GetString(id, "url")
secret, _ := config.GetString(id, "secret")
if u == "" || secret == "" {
log.Printf("Backend %s is missing or incomplete, skipping", id)
continue
}
if u[len(u)-1] != '/' {
u += "/"
}
parsed, err := url.Parse(u)
if err != nil {
log.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err)
continue
}
backends[parsed.Host] = append(backends[parsed.Host], &Backend{
id: id,
url: u,
secret: []byte(secret),
})
log.Printf("Backend %s added for %s", id, u)
}
} else if allowedUrls, _ := config.GetString("backend", "allowed"); allowedUrls != "" {
// Old-style configuration, only hosts are configured and are using a common secret.
@ -148,6 +120,101 @@ func NewBackendConfiguration(config *goconf.ConfigFile) (*BackendConfiguration,
}, nil
}
func (b *BackendConfiguration) RemoveBackend(host string) {
delete(b.backends, host)
}
func (b *BackendConfiguration) UpsertHost(host string, backends []*Backend) {
existingIndex := 0
for _, existingBackend := range b.backends[host] {
found := false
index := 0
for _, newBackend := range backends {
if reflect.DeepEqual(existingBackend, newBackend) { // otherwise we could manually compare the struct members here
found = true
backends = append(backends[:index], backends[index+1:]...)
break
}
index++
}
if !found {
b.backends[host] = append(b.backends[host][:existingIndex], b.backends[host][existingIndex+1:]...)
}
existingIndex++
}
b.backends[host] = append(b.backends[host], backends...)
}
func getConfiguredBackendIDs(config *goconf.ConfigFile) (ids map[string]bool) {
ids = make(map[string]bool)
if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
for _, id := range strings.Split(backendIds, ",") {
id = strings.TrimSpace(id)
if id == "" {
continue
}
ids[id] = true
}
}
return ids
}
func getConfiguredHosts(config *goconf.ConfigFile) (hosts map[string][]*Backend) {
hosts = make(map[string][]*Backend)
for id := range getConfiguredBackendIDs(config) {
u, _ := config.GetString(id, "url")
if u == "" {
log.Printf("Backend %s is missing or incomplete, skipping", id)
continue
}
if u[len(u)-1] != '/' {
u += "/"
}
parsed, err := url.Parse(u)
if err != nil {
log.Printf("Backend %s has an invalid url %s configured (%s), skipping", id, u, err)
continue
}
secret, _ := config.GetString(id, "secret")
if u == "" || secret == "" {
log.Printf("Backend %s is missing or incomplete, skipping", id)
continue
}
hosts[parsed.Host] = append(hosts[parsed.Host], &Backend{
id: id,
url: u,
secret: []byte(secret),
})
}
return hosts
}
func (b *BackendConfiguration) Reload(config *goconf.ConfigFile) {
if backendIds, _ := config.GetString("backend", "backends"); backendIds != "" {
configuredHosts := getConfiguredHosts(config)
// remove backends that are no longer configured
for hostname := range b.backends {
if _, ok := configuredHosts[hostname]; !ok {
b.RemoveBackend(hostname)
}
}
// rewrite backends adding newly configured ones and rewriting existing ones
for hostname, configuredBackends := range configuredHosts {
b.UpsertHost(hostname, configuredBackends)
}
}
}
func (b *BackendConfiguration) GetCompatBackend() *Backend {
return b.compatBackend
}

View File

@ -24,6 +24,7 @@ package signaling
import (
"bytes"
"net/url"
"reflect"
"testing"
"github.com/dlintw/goconf"
@ -163,3 +164,136 @@ func TestIsUrlAllowed_AllowAll(t *testing.T) {
}
testUrls(t, cfg, valid_urls, invalid_urls)
}
func TestBackendReloadChangeExistingURL(t *testing.T) {
original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false")
original_config.AddOption("backend1", "url", "http://domain1.invalid")
original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
original_config.AddOption("backend2", "url", "http://domain2.invalid")
original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
o_cfg, err := NewBackendConfiguration(original_config)
if err != nil {
t.Fatal(err)
}
new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2")
new_config.AddOption("backend", "allowall", "false")
new_config.AddOption("backend1", "url", "http://domain3.invalid")
new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
new_config.AddOption("backend2", "url", "http://domain2.invalid")
new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
n_cfg, err := NewBackendConfiguration(new_config)
if err != nil {
t.Fatal(err)
}
original_config.RemoveOption("backend1", "url")
original_config.AddOption("backend1", "url", "http://domain3.invalid")
o_cfg.Reload(original_config)
if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload")
}
}
func TestBackendReloadChangeSecret(t *testing.T) {
original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false")
original_config.AddOption("backend1", "url", "http://domain1.invalid")
original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
original_config.AddOption("backend2", "url", "http://domain2.invalid")
original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
o_cfg, err := NewBackendConfiguration(original_config)
if err != nil {
t.Fatal(err)
}
new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2")
new_config.AddOption("backend", "allowall", "false")
new_config.AddOption("backend1", "url", "http://domain1.invalid")
new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3")
new_config.AddOption("backend2", "url", "http://domain2.invalid")
new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
n_cfg, err := NewBackendConfiguration(new_config)
if err != nil {
t.Fatal(err)
}
original_config.RemoveOption("backend1", "secret")
original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend3")
o_cfg.Reload(original_config)
if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload")
}
}
func TestBackendReloadAddBackend(t *testing.T) {
original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1")
original_config.AddOption("backend", "allowall", "false")
original_config.AddOption("backend1", "url", "http://domain1.invalid")
original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
o_cfg, err := NewBackendConfiguration(original_config)
if err != nil {
t.Fatal(err)
}
new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1, backend2")
new_config.AddOption("backend", "allowall", "false")
new_config.AddOption("backend1", "url", "http://domain1.invalid")
new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
new_config.AddOption("backend2", "url", "http://domain2.invalid")
new_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
n_cfg, err := NewBackendConfiguration(new_config)
if err != nil {
t.Fatal(err)
}
original_config.RemoveOption("backend", "backends")
original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend2", "url", "http://domain2.invalid")
original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
o_cfg.Reload(original_config)
if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload")
}
}
func TestBackendReloadRemove(t *testing.T) {
original_config := goconf.NewConfigFile()
original_config.AddOption("backend", "backends", "backend1, backend2")
original_config.AddOption("backend", "allowall", "false")
original_config.AddOption("backend1", "url", "http://domain1.invalid")
original_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
original_config.AddOption("backend2", "url", "http://domain1.invalid")
original_config.AddOption("backend2", "secret", string(testBackendSecret)+"-backend2")
o_cfg, err := NewBackendConfiguration(original_config)
if err != nil {
t.Fatal(err)
}
new_config := goconf.NewConfigFile()
new_config.AddOption("backend", "backends", "backend1")
new_config.AddOption("backend", "allowall", "false")
new_config.AddOption("backend1", "url", "http://domain1.invalid")
new_config.AddOption("backend1", "secret", string(testBackendSecret)+"-backend1")
n_cfg, err := NewBackendConfiguration(new_config)
if err != nil {
t.Fatal(err)
}
original_config.RemoveSection("backend2")
o_cfg.Reload(original_config)
if !reflect.DeepEqual(n_cfg, o_cfg) {
t.Error("BackendConfiguration should be equal after Reload")
}
}

View File

@ -420,6 +420,7 @@ func (h *Hub) Reload(config *goconf.ConfigFile) {
if h.mcu != nil {
h.mcu.Reload(config)
}
h.backend.Reload(config)
}
func reverseSessionId(s string) (string, error) {