diff --git a/src/signaling/backend_client.go b/src/signaling/backend_client.go index a66979b..44aa120 100644 --- a/src/signaling/backend_client.go +++ b/src/signaling/backend_client.go @@ -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() diff --git a/src/signaling/backend_configuration.go b/src/signaling/backend_configuration.go index c99cefe..ceaf506 100644 --- a/src/signaling/backend_configuration.go +++ b/src/signaling/backend_configuration.go @@ -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 } diff --git a/src/signaling/backend_configuration_test.go b/src/signaling/backend_configuration_test.go index 2aea66c..f1a16f5 100644 --- a/src/signaling/backend_configuration_test.go +++ b/src/signaling/backend_configuration_test.go @@ -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") + } +} diff --git a/src/signaling/hub.go b/src/signaling/hub.go index 93e5f10..9c960da 100644 --- a/src/signaling/hub.go +++ b/src/signaling/hub.go @@ -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) {