TUN-1968: Gracefully diff StreamHandler.UpdateConfig
This commit is contained in:
parent
858ef29868
commit
ef5b44b2d0
|
@ -82,9 +82,16 @@ func (s *StreamHandler) UseConfiguration(ctx context.Context, config *pogs.Clien
|
||||||
|
|
||||||
// UpdateConfig replaces current originmapper mapping with mappings from newConfig
|
// UpdateConfig replaces current originmapper mapping with mappings from newConfig
|
||||||
func (s *StreamHandler) UpdateConfig(newConfig []*pogs.ReverseProxyConfig) (failedConfigs []*pogs.FailedConfig) {
|
func (s *StreamHandler) UpdateConfig(newConfig []*pogs.ReverseProxyConfig) (failedConfigs []*pogs.FailedConfig) {
|
||||||
// TODO: TUN-1968: Gracefully apply new config
|
|
||||||
s.tunnelHostnameMapper.DeleteAll()
|
// Delete old configs that aren't in the `newConfig`
|
||||||
for _, tunnelConfig := range newConfig {
|
toRemove := s.tunnelHostnameMapper.ToRemove(newConfig)
|
||||||
|
for _, hostnameToRemove := range toRemove {
|
||||||
|
s.tunnelHostnameMapper.Delete(hostnameToRemove)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new configs that weren't in the old mapper
|
||||||
|
toAdd := s.tunnelHostnameMapper.ToAdd(newConfig)
|
||||||
|
for _, tunnelConfig := range toAdd {
|
||||||
tunnelHostname := tunnelConfig.TunnelHostname
|
tunnelHostname := tunnelConfig.TunnelHostname
|
||||||
originSerice, err := tunnelConfig.OriginConfigJSONHandler.OriginConfig.Service()
|
originSerice, err := tunnelConfig.OriginConfigJSONHandler.OriginConfig.Service()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
"github.com/cloudflare/cloudflared/h2mux"
|
||||||
"github.com/cloudflare/cloudflared/originservice"
|
"github.com/cloudflare/cloudflared/originservice"
|
||||||
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TunnelHostnameMapper maps TunnelHostname to an OriginService
|
// TunnelHostnameMapper maps TunnelHostname to an OriginService
|
||||||
|
@ -38,12 +39,55 @@ func (om *TunnelHostnameMapper) Add(key h2mux.TunnelHostname, os originservice.O
|
||||||
om.tunnelHostnameToOrigin[key] = os
|
om.tunnelHostnameToOrigin[key] = os
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAll mappings, and shutdown all OriginService
|
// Delete a mapping, and shutdown its OriginService
|
||||||
func (om *TunnelHostnameMapper) DeleteAll() {
|
func (om *TunnelHostnameMapper) Delete(key h2mux.TunnelHostname) (keyFound bool) {
|
||||||
om.Lock()
|
om.Lock()
|
||||||
defer om.Unlock()
|
defer om.Unlock()
|
||||||
for key, os := range om.tunnelHostnameToOrigin {
|
if os, ok := om.tunnelHostnameToOrigin[key]; ok {
|
||||||
os.Shutdown()
|
os.Shutdown()
|
||||||
delete(om.tunnelHostnameToOrigin, key)
|
delete(om.tunnelHostnameToOrigin, key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToRemove finds all keys that should be removed from the TunnelHostnameMapper.
|
||||||
|
func (om *TunnelHostnameMapper) ToRemove(newConfigs []*pogs.ReverseProxyConfig) (toRemove []h2mux.TunnelHostname) {
|
||||||
|
om.Lock()
|
||||||
|
defer om.Unlock()
|
||||||
|
|
||||||
|
// Convert into a set, for O(1) lookups instead of O(n)
|
||||||
|
newConfigSet := toSet(newConfigs)
|
||||||
|
|
||||||
|
// If a config in `om` isn't in `newConfigs`, it must be removed.
|
||||||
|
for hostname := range om.tunnelHostnameToOrigin {
|
||||||
|
if _, ok := newConfigSet[hostname]; !ok {
|
||||||
|
toRemove = append(toRemove, hostname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToAdd filters the given configs, keeping those that should be added to the TunnelHostnameMapper.
|
||||||
|
func (om *TunnelHostnameMapper) ToAdd(newConfigs []*pogs.ReverseProxyConfig) (toAdd []*pogs.ReverseProxyConfig) {
|
||||||
|
om.Lock()
|
||||||
|
defer om.Unlock()
|
||||||
|
|
||||||
|
// If a config in `newConfigs` isn't in `om`, it must be added.
|
||||||
|
for _, config := range newConfigs {
|
||||||
|
if _, ok := om.tunnelHostnameToOrigin[config.TunnelHostname]; !ok {
|
||||||
|
toAdd = append(toAdd, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func toSet(configs []*pogs.ReverseProxyConfig) map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig {
|
||||||
|
m := make(map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig)
|
||||||
|
for _, config := range configs {
|
||||||
|
m[config.TunnelHostname] = config
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
|
@ -4,11 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/cloudflare/cloudflared/h2mux"
|
"github.com/cloudflare/cloudflared/h2mux"
|
||||||
"github.com/cloudflare/cloudflared/originservice"
|
"github.com/cloudflare/cloudflared/originservice"
|
||||||
|
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,9 +55,6 @@ func TestTunnelHostnameMapperConcurrentAccess(t *testing.T) {
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, secondHTTPOS, os)
|
assert.Equal(t, secondHTTPOS, os)
|
||||||
})
|
})
|
||||||
|
|
||||||
thm.DeleteAll()
|
|
||||||
assert.Empty(t, thm.tunnelHostnameToOrigin)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func concurrentOps(t *testing.T, f func(i int)) {
|
func concurrentOps(t *testing.T, f func(i int)) {
|
||||||
|
@ -72,3 +72,141 @@ func concurrentOps(t *testing.T, f func(i int)) {
|
||||||
func tunnelHostname(i int) h2mux.TunnelHostname {
|
func tunnelHostname(i int) h2mux.TunnelHostname {
|
||||||
return h2mux.TunnelHostname(fmt.Sprintf("%d.cftunnel.com", i))
|
return h2mux.TunnelHostname(fmt.Sprintf("%d.cftunnel.com", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_toSet(t *testing.T) {
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
configs []*pogs.ReverseProxyConfig
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty slice should yield empty map",
|
||||||
|
args: args{},
|
||||||
|
want: map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple elements",
|
||||||
|
args: args{[]*pogs.ReverseProxyConfig{sampleConfig1(), sampleConfig2()}},
|
||||||
|
want: map[h2mux.TunnelHostname]*pogs.ReverseProxyConfig{
|
||||||
|
"mock.example.com": sampleConfig1(),
|
||||||
|
"mock2.example.com": sampleConfig2(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := toSet(tt.args.configs); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("toSet() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTunnelHostnameMapper_ToAdd(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
newConfigs []*pogs.ReverseProxyConfig
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantToAdd []*pogs.ReverseProxyConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Mapper={}, NewConfig={}, toAdd={}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mapper={}, NewConfig={x}, toAdd={x}",
|
||||||
|
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig1()}},
|
||||||
|
wantToAdd: []*pogs.ReverseProxyConfig{sampleConfig1()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mapper={x}, NewConfig={x,y}, toAdd={y}",
|
||||||
|
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig2()}},
|
||||||
|
wantToAdd: []*pogs.ReverseProxyConfig{sampleConfig2()},
|
||||||
|
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
|
||||||
|
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
om := &TunnelHostnameMapper{
|
||||||
|
tunnelHostnameToOrigin: tt.fields.tunnelHostnameToOrigin,
|
||||||
|
}
|
||||||
|
if gotToAdd := om.ToAdd(tt.args.newConfigs); !reflect.DeepEqual(gotToAdd, tt.wantToAdd) {
|
||||||
|
t.Errorf("TunnelHostnameMapper.ToAdd() = %v, want %v", gotToAdd, tt.wantToAdd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTunnelHostnameMapper_ToRemove(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
tunnelHostnameToOrigin map[h2mux.TunnelHostname]originservice.OriginService
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
newConfigs []*pogs.ReverseProxyConfig
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantToRemove []h2mux.TunnelHostname
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Mapper={}, NewConfig={}, toRemove={}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mapper={x}, NewConfig={}, toRemove={x}",
|
||||||
|
wantToRemove: []h2mux.TunnelHostname{sampleConfig1().TunnelHostname},
|
||||||
|
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
|
||||||
|
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Mapper={x}, NewConfig={x}, toRemove={}",
|
||||||
|
args: args{newConfigs: []*pogs.ReverseProxyConfig{sampleConfig1()}},
|
||||||
|
fields: fields{tunnelHostnameToOrigin: map[h2mux.TunnelHostname]originservice.OriginService{
|
||||||
|
h2mux.TunnelHostname(sampleConfig1().TunnelHostname): &originservice.HelloWorldService{},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
om := &TunnelHostnameMapper{
|
||||||
|
tunnelHostnameToOrigin: tt.fields.tunnelHostnameToOrigin,
|
||||||
|
}
|
||||||
|
if gotToRemove := om.ToRemove(tt.args.newConfigs); !reflect.DeepEqual(gotToRemove, tt.wantToRemove) {
|
||||||
|
t.Errorf("TunnelHostnameMapper.ToRemove() = %v, want %v", gotToRemove, tt.wantToRemove)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleConfig1() *pogs.ReverseProxyConfig {
|
||||||
|
return &pogs.ReverseProxyConfig{
|
||||||
|
TunnelHostname: "mock.example.com",
|
||||||
|
OriginConfigJSONHandler: &pogs.OriginConfigJSONHandler{OriginConfig: &pogs.HelloWorldOriginConfig{}},
|
||||||
|
Retries: 18,
|
||||||
|
ConnectionTimeout: 5 * time.Second,
|
||||||
|
CompressionQuality: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleConfig2() *pogs.ReverseProxyConfig {
|
||||||
|
return &pogs.ReverseProxyConfig{
|
||||||
|
TunnelHostname: "mock2.example.com",
|
||||||
|
OriginConfigJSONHandler: &pogs.OriginConfigJSONHandler{OriginConfig: &pogs.HelloWorldOriginConfig{}},
|
||||||
|
Retries: 18,
|
||||||
|
ConnectionTimeout: 5 * time.Second,
|
||||||
|
CompressionQuality: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue