Provide option for kubernetes to watch only a specific namespace (#433)
This commit is contained in:
@@ -26,6 +26,7 @@ type Config struct {
|
||||
ConnectionRateLimit int `default:"1" usage:"Max number of connections to allow per second"`
|
||||
InKubeCluster bool `usage:"Use in-cluster Kubernetes config"`
|
||||
KubeConfig string `usage:"The path to a Kubernetes configuration file"`
|
||||
KubeNamespace string `usage:"The namespace to watch or blank for all, which is the default"`
|
||||
InDocker bool `usage:"Use Docker service discovery"`
|
||||
InDockerSwarm bool `usage:"Use Docker Swarm service discovery"`
|
||||
DockerSocket string `default:"unix:///var/run/docker.sock" usage:"Path to Docker socket to use"`
|
||||
|
||||
+99
-73
@@ -25,46 +25,60 @@ const (
|
||||
AnnotationDefaultServer = "mc-router.itzg.me/defaultServer"
|
||||
)
|
||||
|
||||
type IK8sWatcher interface {
|
||||
StartWithConfig(ctx context.Context, kubeConfigFile string, autoScaleUp bool, autoScaleDown bool) error
|
||||
StartInCluster(ctx context.Context, autoScaleUp bool, autoScaleDown bool) error
|
||||
}
|
||||
|
||||
var K8sWatcher IK8sWatcher = &k8sWatcherImpl{}
|
||||
|
||||
type k8sWatcherImpl struct {
|
||||
// K8sWatcher is a RouteFinder that can find routes from kubernetes services.
|
||||
// It also watches for stateful sets to auto scale up/down, if enabled.
|
||||
type K8sWatcher struct {
|
||||
sync.RWMutex
|
||||
config *rest.Config
|
||||
autoScaleUp bool
|
||||
autoScaleDown bool
|
||||
namespace string
|
||||
// The key in mappings is a Service, and the value the StatefulSet name
|
||||
mappings map[string]string
|
||||
|
||||
clientset *kubernetes.Clientset
|
||||
mappings map[string]string
|
||||
routesHandler RoutesHandler
|
||||
clientset *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func (w *k8sWatcherImpl) StartInCluster(ctx context.Context, autoScaleUp bool, autoScaleDown bool) error {
|
||||
func NewK8sWatcherInCluster() (*K8sWatcher, error) {
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to load in-cluster config")
|
||||
return nil, errors.Wrap(err, "Unable to load in-cluster config")
|
||||
}
|
||||
|
||||
return w.startWithLoadedConfig(ctx, config, autoScaleUp, autoScaleDown)
|
||||
return &K8sWatcher{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *k8sWatcherImpl) StartWithConfig(ctx context.Context, kubeConfigFile string, autoScaleUp bool, autoScaleDown bool) error {
|
||||
func NewK8sWatcherWithConfig(kubeConfigFile string) (*K8sWatcher, error) {
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeConfigFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not load kube config file")
|
||||
return nil, errors.Wrap(err, "Could not load kube config file")
|
||||
}
|
||||
|
||||
return w.startWithLoadedConfig(ctx, config, autoScaleUp, autoScaleDown)
|
||||
return &K8sWatcher{
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (w *k8sWatcherImpl) startWithLoadedConfig(ctx context.Context, config *rest.Config, autoScaleUp bool, autoScaleDown bool) error {
|
||||
func (w *K8sWatcher) WithAutoScale(autoScaleUp bool, autoScaleDown bool) *K8sWatcher {
|
||||
w.autoScaleUp = autoScaleUp
|
||||
w.autoScaleDown = autoScaleDown
|
||||
return w
|
||||
}
|
||||
|
||||
clientset, err := kubernetes.NewForConfig(config)
|
||||
func (w *K8sWatcher) WithNamespace(namespace string) *K8sWatcher {
|
||||
w.namespace = namespace
|
||||
return w
|
||||
}
|
||||
|
||||
func (w *K8sWatcher) String() string {
|
||||
return "k8s"
|
||||
}
|
||||
|
||||
func (w *K8sWatcher) Start(ctx context.Context, handler RoutesHandler) error {
|
||||
w.routesHandler = handler
|
||||
clientset, err := kubernetes.NewForConfig(w.config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Could not create kube clientset")
|
||||
}
|
||||
@@ -74,7 +88,7 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(ctx context.Context, config *rest
|
||||
ListerWatcher: cache.NewListWatchFromClient(
|
||||
clientset.CoreV1().RESTClient(),
|
||||
string(core.ResourceServices),
|
||||
core.NamespaceAll,
|
||||
w.namespace,
|
||||
fields.Everything(),
|
||||
),
|
||||
ObjectType: &core.Service{},
|
||||
@@ -87,51 +101,22 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(ctx context.Context, config *rest
|
||||
go serviceController.RunWithContext(ctx)
|
||||
|
||||
w.mappings = make(map[string]string)
|
||||
if autoScaleUp || autoScaleDown {
|
||||
_, statefulSetController := cache.NewInformer(
|
||||
cache.NewListWatchFromClient(
|
||||
if w.autoScaleUp || w.autoScaleDown {
|
||||
_, statefulSetController := cache.NewInformerWithOptions(cache.InformerOptions{
|
||||
ListerWatcher: cache.NewListWatchFromClient(
|
||||
clientset.AppsV1().RESTClient(),
|
||||
"statefulSets",
|
||||
core.NamespaceAll,
|
||||
w.namespace,
|
||||
fields.Everything(),
|
||||
),
|
||||
&apps.StatefulSet{},
|
||||
0,
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
statefulSet, ok := obj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
w.mappings[statefulSet.Spec.ServiceName] = statefulSet.Name
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
statefulSet, ok := obj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
delete(w.mappings, statefulSet.Spec.ServiceName)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
oldStatefulSet, ok := oldObj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
newStatefulSet, ok := newObj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
delete(w.mappings, oldStatefulSet.Spec.ServiceName)
|
||||
w.mappings[newStatefulSet.Spec.ServiceName] = newStatefulSet.Name
|
||||
},
|
||||
ObjectType: &apps.StatefulSet{},
|
||||
Handler: cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: w.handleAddStatefulSet(),
|
||||
DeleteFunc: w.handleDeleteStatefulSet(),
|
||||
UpdateFunc: w.handleUpdateStatefulSet(),
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
go statefulSetController.RunWithContext(ctx)
|
||||
}
|
||||
|
||||
@@ -139,14 +124,55 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(ctx context.Context, config *rest
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *K8sWatcher) handleAddStatefulSet() func(obj interface{}) {
|
||||
return func(obj interface{}) {
|
||||
statefulSet, ok := obj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
w.mappings[statefulSet.Spec.ServiceName] = statefulSet.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (w *K8sWatcher) handleUpdateStatefulSet() func(oldObj interface{}, newObj interface{}) {
|
||||
return func(oldObj, newObj interface{}) {
|
||||
oldStatefulSet, ok := oldObj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
newStatefulSet, ok := newObj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
delete(w.mappings, oldStatefulSet.Spec.ServiceName)
|
||||
w.mappings[newStatefulSet.Spec.ServiceName] = newStatefulSet.Name
|
||||
}
|
||||
}
|
||||
|
||||
func (w *K8sWatcher) handleDeleteStatefulSet() func(obj interface{}) {
|
||||
return func(obj interface{}) {
|
||||
statefulSet, ok := obj.(*apps.StatefulSet)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
w.RLock()
|
||||
defer w.RUnlock()
|
||||
delete(w.mappings, statefulSet.Spec.ServiceName)
|
||||
}
|
||||
}
|
||||
|
||||
// oldObj and newObj are expected to be *v1.Service
|
||||
func (w *k8sWatcherImpl) handleUpdate(oldObj interface{}, newObj interface{}) {
|
||||
func (w *K8sWatcher) handleUpdate(oldObj interface{}, newObj interface{}) {
|
||||
for _, oldRoutableService := range w.extractRoutableServices(oldObj) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"old": oldRoutableService,
|
||||
}).Debug("UPDATE")
|
||||
if oldRoutableService.externalServiceName != "" {
|
||||
Routes.DeleteMapping(oldRoutableService.externalServiceName)
|
||||
w.routesHandler.DeleteMapping(oldRoutableService.externalServiceName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,40 +181,40 @@ func (w *k8sWatcherImpl) handleUpdate(oldObj interface{}, newObj interface{}) {
|
||||
"new": newRoutableService,
|
||||
}).Debug("UPDATE")
|
||||
if newRoutableService.externalServiceName != "" {
|
||||
Routes.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint, newRoutableService.autoScaleUp, newRoutableService.autoScaleDown)
|
||||
w.routesHandler.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint, newRoutableService.autoScaleUp, newRoutableService.autoScaleDown)
|
||||
} else {
|
||||
Routes.SetDefaultRoute(newRoutableService.containerEndpoint)
|
||||
w.routesHandler.SetDefaultRoute(newRoutableService.containerEndpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// obj is expected to be a *v1.Service
|
||||
func (w *k8sWatcherImpl) handleDelete(obj interface{}) {
|
||||
func (w *K8sWatcher) handleDelete(obj interface{}) {
|
||||
routableServices := w.extractRoutableServices(obj)
|
||||
for _, routableService := range routableServices {
|
||||
if routableService != nil {
|
||||
logrus.WithField("routableService", routableService).Debug("DELETE")
|
||||
|
||||
if routableService.externalServiceName != "" {
|
||||
Routes.DeleteMapping(routableService.externalServiceName)
|
||||
w.routesHandler.DeleteMapping(routableService.externalServiceName)
|
||||
} else {
|
||||
Routes.SetDefaultRoute("")
|
||||
w.routesHandler.SetDefaultRoute("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// obj is expected to be a *v1.Service
|
||||
func (w *k8sWatcherImpl) handleAdd(obj interface{}) {
|
||||
func (w *K8sWatcher) handleAdd(obj interface{}) {
|
||||
routableServices := w.extractRoutableServices(obj)
|
||||
for _, routableService := range routableServices {
|
||||
if routableService != nil {
|
||||
logrus.WithField("routableService", routableService).Debug("ADD")
|
||||
|
||||
if routableService.externalServiceName != "" {
|
||||
Routes.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint, routableService.autoScaleUp, routableService.autoScaleDown)
|
||||
w.routesHandler.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint, routableService.autoScaleUp, routableService.autoScaleDown)
|
||||
} else {
|
||||
Routes.SetDefaultRoute(routableService.containerEndpoint)
|
||||
w.routesHandler.SetDefaultRoute(routableService.containerEndpoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +228,7 @@ type routableService struct {
|
||||
}
|
||||
|
||||
// obj is expected to be a *v1.Service
|
||||
func (w *k8sWatcherImpl) extractRoutableServices(obj interface{}) []*routableService {
|
||||
func (w *K8sWatcher) extractRoutableServices(obj interface{}) []*routableService {
|
||||
service, ok := obj.(*core.Service)
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -222,7 +248,7 @@ func (w *k8sWatcherImpl) extractRoutableServices(obj interface{}) []*routableSer
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *k8sWatcherImpl) buildDetails(service *core.Service, externalServiceName string) *routableService {
|
||||
func (w *K8sWatcher) buildDetails(service *core.Service, externalServiceName string) *routableService {
|
||||
clusterIp := service.Spec.ClusterIP
|
||||
if service.Spec.Type == core.ServiceTypeExternalName {
|
||||
clusterIp = service.Spec.ExternalName
|
||||
@@ -252,7 +278,7 @@ func (w *k8sWatcherImpl) buildDetails(service *core.Service, externalServiceName
|
||||
return rs
|
||||
}
|
||||
|
||||
func (w *k8sWatcherImpl) buildScaleFunction(service *core.Service, from int32, to int32) ScalerFunc {
|
||||
func (w *K8sWatcher) buildScaleFunction(service *core.Service, from int32, to int32) ScalerFunc {
|
||||
if from <= to && !w.autoScaleUp {
|
||||
return nil
|
||||
}
|
||||
|
||||
+114
-57
@@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -11,10 +12,50 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type MockedRoutesHandler struct {
|
||||
mock.Mock
|
||||
routes map[string]string
|
||||
defaultBackend string
|
||||
}
|
||||
|
||||
func (m *MockedRoutesHandler) GetBackendForServer(server string) string {
|
||||
backend, exists := m.routes[server]
|
||||
if exists {
|
||||
return backend
|
||||
} else {
|
||||
return m.defaultBackend
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockedRoutesHandler) CreateMapping(serverAddress string, backend string, waker ScalerFunc, sleeper ScalerFunc) {
|
||||
m.MethodCalled("CreateMapping", serverAddress, backend, waker, sleeper)
|
||||
if m.routes == nil {
|
||||
m.routes = make(map[string]string)
|
||||
}
|
||||
m.routes[serverAddress] = backend
|
||||
}
|
||||
|
||||
func (m *MockedRoutesHandler) SetDefaultRoute(backend string) {
|
||||
m.MethodCalled("SetDefaultRoute", backend)
|
||||
if m.routes == nil {
|
||||
m.routes = make(map[string]string)
|
||||
}
|
||||
m.defaultBackend = backend
|
||||
}
|
||||
|
||||
func (m *MockedRoutesHandler) DeleteMapping(serverAddress string) bool {
|
||||
args := m.MethodCalled("DeleteMapping", serverAddress)
|
||||
if m.routes == nil {
|
||||
m.routes = make(map[string]string)
|
||||
}
|
||||
delete(m.routes, serverAddress)
|
||||
return args.Bool(0)
|
||||
}
|
||||
|
||||
func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
type scenario struct {
|
||||
given string
|
||||
expect string
|
||||
server string
|
||||
backend string
|
||||
}
|
||||
type svcAndScenarios struct {
|
||||
svc string
|
||||
@@ -30,15 +71,15 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: ""},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -47,15 +88,15 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com,b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -64,15 +105,15 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com,b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: ""},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -81,17 +122,22 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// DownScaler needs to be instantiated
|
||||
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
|
||||
Routes.Reset()
|
||||
|
||||
watcher := &k8sWatcherImpl{}
|
||||
routesHandler := new(MockedRoutesHandler)
|
||||
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
||||
routesHandler.On("DeleteMapping", mock.Anything).Return(true)
|
||||
|
||||
watcher := &K8sWatcher{
|
||||
routesHandler: routesHandler,
|
||||
}
|
||||
initialSvc := v1.Service{}
|
||||
err := json.Unmarshal([]byte(test.initial.svc), &initialSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
watcher.handleAdd(&initialSvc)
|
||||
for _, s := range test.initial.scenarios {
|
||||
backend, _, _, _ := Routes.FindBackendForServerAddress(context.Background(), s.given)
|
||||
assert.Equal(t, s.expect, backend, "initial: given=%s", s.given)
|
||||
backend := routesHandler.GetBackendForServer(s.server)
|
||||
assert.Equal(t, s.backend, backend, "initial: given=%s", s.server)
|
||||
}
|
||||
|
||||
updatedSvc := v1.Service{}
|
||||
@@ -100,8 +146,8 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
|
||||
watcher.handleUpdate(&initialSvc, &updatedSvc)
|
||||
for _, s := range test.update.scenarios {
|
||||
backend, _, _, _ := Routes.FindBackendForServerAddress(context.Background(), s.given)
|
||||
assert.Equal(t, s.expect, backend, "update: given=%s", s.given)
|
||||
backend := routesHandler.GetBackendForServer(s.server)
|
||||
assert.Equal(t, s.backend, backend, "update: given=%s", s.server)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -109,8 +155,8 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
|
||||
|
||||
func TestK8sWatcherImpl_handleAddThenDelete(t *testing.T) {
|
||||
type scenario struct {
|
||||
given string
|
||||
expect string
|
||||
server string
|
||||
backend string
|
||||
}
|
||||
type svcAndScenarios struct {
|
||||
svc string
|
||||
@@ -119,20 +165,21 @@ func TestK8sWatcherImpl_handleAddThenDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
initial svcAndScenarios
|
||||
delete []scenario
|
||||
// non-empty `backend` in this case means the server is expected to be deleted
|
||||
delete []scenario
|
||||
}{
|
||||
{
|
||||
name: "single",
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
delete: []scenario{
|
||||
{given: "a.com", expect: ""},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -140,13 +187,13 @@ func TestK8sWatcherImpl_handleAddThenDelete(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com,b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
delete: []scenario{
|
||||
{given: "a.com", expect: ""},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -154,23 +201,28 @@ func TestK8sWatcherImpl_handleAddThenDelete(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// DownScaler needs to be instantiated
|
||||
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
|
||||
Routes.Reset()
|
||||
|
||||
watcher := &k8sWatcherImpl{}
|
||||
routesHandler := new(MockedRoutesHandler)
|
||||
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
||||
routesHandler.On("DeleteMapping", mock.Anything).Return(true)
|
||||
|
||||
watcher := &K8sWatcher{
|
||||
routesHandler: routesHandler,
|
||||
}
|
||||
initialSvc := v1.Service{}
|
||||
err := json.Unmarshal([]byte(test.initial.svc), &initialSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
watcher.handleAdd(&initialSvc)
|
||||
for _, s := range test.initial.scenarios {
|
||||
backend, _, _, _ := Routes.FindBackendForServerAddress(context.Background(), s.given)
|
||||
assert.Equal(t, s.expect, backend, "initial: given=%s", s.given)
|
||||
backend := routesHandler.GetBackendForServer(s.server)
|
||||
assert.Equal(t, s.backend, backend, "initial: given=%s", s.server)
|
||||
}
|
||||
|
||||
watcher.handleDelete(&initialSvc)
|
||||
for _, s := range test.delete {
|
||||
backend, _, _, _ := Routes.FindBackendForServerAddress(context.Background(), s.given)
|
||||
assert.Equal(t, s.expect, backend, "update: given=%s", s.given)
|
||||
backend := routesHandler.GetBackendForServer(s.server)
|
||||
assert.Equal(t, s.backend, backend, "update: given=%s", s.server)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -178,8 +230,8 @@ func TestK8sWatcherImpl_handleAddThenDelete(t *testing.T) {
|
||||
|
||||
func TestK8s_externalName(t *testing.T) {
|
||||
type scenario struct {
|
||||
given string
|
||||
expect string
|
||||
server string
|
||||
backend string
|
||||
}
|
||||
type svcAndScenarios struct {
|
||||
svc string
|
||||
@@ -195,15 +247,15 @@ func TestK8s_externalName(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com"}}, "spec":{"type":"ExternalName", "externalName": "mc-server.com"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "mc-server.com:25565"},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: "mc-server.com:25565"},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -212,15 +264,15 @@ func TestK8s_externalName(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com"}}, "spec":{"type":"ExternalName", "externalName": "mc-server.com"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "mc-server.com:25565"},
|
||||
{given: "b.com", expect: ""},
|
||||
{server: "a.com", backend: "mc-server.com:25565"},
|
||||
{server: "b.com", backend: ""},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "b.com"}}, "spec":{"clusterIP": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: ""},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: ""},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -229,15 +281,15 @@ func TestK8s_externalName(t *testing.T) {
|
||||
initial: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com,b.com"}}, "spec":{"type":"ExternalName", "externalName": "mc-server.com"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "mc-server.com:25565"},
|
||||
{given: "b.com", expect: "mc-server.com:25565"},
|
||||
{server: "a.com", backend: "mc-server.com:25565"},
|
||||
{server: "b.com", backend: "mc-server.com:25565"},
|
||||
},
|
||||
},
|
||||
update: svcAndScenarios{
|
||||
svc: ` {"metadata": {"annotations": {"mc-router.itzg.me/externalServerName": "a.com,b.com"}}, "spec":{"type":"ExternalName", "externalName": "1.1.1.1"}}`,
|
||||
scenarios: []scenario{
|
||||
{given: "a.com", expect: "1.1.1.1:25565"},
|
||||
{given: "b.com", expect: "1.1.1.1:25565"},
|
||||
{server: "a.com", backend: "1.1.1.1:25565"},
|
||||
{server: "b.com", backend: "1.1.1.1:25565"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -246,17 +298,22 @@ func TestK8s_externalName(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// DownScaler needs to be instantiated
|
||||
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
|
||||
Routes.Reset()
|
||||
|
||||
watcher := &k8sWatcherImpl{}
|
||||
routesHandler := new(MockedRoutesHandler)
|
||||
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
|
||||
routesHandler.On("DeleteMapping", mock.Anything).Return(true)
|
||||
|
||||
watcher := &K8sWatcher{
|
||||
routesHandler: routesHandler,
|
||||
}
|
||||
initialSvc := v1.Service{}
|
||||
err := json.Unmarshal([]byte(test.initial.svc), &initialSvc)
|
||||
require.NoError(t, err)
|
||||
|
||||
watcher.handleAdd(&initialSvc)
|
||||
for _, s := range test.initial.scenarios {
|
||||
backend, _, _, _ := Routes.FindBackendForServerAddress(context.Background(), s.given)
|
||||
assert.Equal(t, s.expect, backend, "initial: given=%s", s.given)
|
||||
backend := routesHandler.GetBackendForServer(s.server)
|
||||
assert.Equal(t, s.backend, backend, "initial: given=%s", s.server)
|
||||
}
|
||||
|
||||
updatedSvc := v1.Service{}
|
||||
@@ -265,8 +322,8 @@ func TestK8s_externalName(t *testing.T) {
|
||||
|
||||
watcher.handleUpdate(&initialSvc, &updatedSvc)
|
||||
for _, s := range test.update.scenarios {
|
||||
backend, _, _, _ := Routes.FindBackendForServerAddress(context.Background(), s.given)
|
||||
assert.Equal(t, s.expect, backend, "update: given=%s", s.given)
|
||||
backend := routesHandler.GetBackendForServer(s.server)
|
||||
assert.Equal(t, s.backend, backend, "update: given=%s", s.server)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+16
-3
@@ -15,7 +15,23 @@ var EmptyScalerFunc = func(ctx context.Context) error { return nil }
|
||||
|
||||
var tcpShieldPattern = regexp.MustCompile("///.*")
|
||||
|
||||
// RouteFinder implementations find new routes in the system that can be tracked by a RoutesHandler
|
||||
type RouteFinder interface {
|
||||
Start(ctx context.Context, handler RoutesHandler) error
|
||||
String() string
|
||||
}
|
||||
|
||||
type RoutesHandler interface {
|
||||
CreateMapping(serverAddress string, backend string, waker ScalerFunc, sleeper ScalerFunc)
|
||||
SetDefaultRoute(backend string)
|
||||
// DeleteMapping requests that the serverAddress be removed from routes.
|
||||
// Returns true if the route existed.
|
||||
DeleteMapping(serverAddress string) bool
|
||||
}
|
||||
|
||||
type IRoutes interface {
|
||||
RoutesHandler
|
||||
|
||||
Reset()
|
||||
RegisterAll(mappings map[string]string)
|
||||
// FindBackendForServerAddress returns the host:port for the external server address, if registered.
|
||||
@@ -24,9 +40,6 @@ type IRoutes interface {
|
||||
// The 4th value returned is an (optional) "sleeper" function which a caller must invoke to shut down serverAddress.
|
||||
FindBackendForServerAddress(ctx context.Context, serverAddress string) (string, string, ScalerFunc, ScalerFunc)
|
||||
GetMappings() map[string]string
|
||||
DeleteMapping(serverAddress string) bool
|
||||
CreateMapping(serverAddress string, backend string, waker ScalerFunc, sleeper ScalerFunc)
|
||||
SetDefaultRoute(backend string)
|
||||
GetDefaultRoute() string
|
||||
SimplifySRV(srvEnabled bool)
|
||||
}
|
||||
|
||||
+21
-4
@@ -120,18 +120,27 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
|
||||
StartApiServer(config.ApiBinding)
|
||||
}
|
||||
|
||||
routeWatchers := make([]RouteFinder, 0)
|
||||
|
||||
if config.InKubeCluster {
|
||||
err = K8sWatcher.StartInCluster(ctx, config.AutoScale.Up, config.AutoScale.Down)
|
||||
k8sWatcher, err := NewK8sWatcherInCluster()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not start in-cluster k8s integration: %w", err)
|
||||
return nil, fmt.Errorf("could not create in-cluster k8s watcher: %w", err)
|
||||
}
|
||||
k8sWatcher.WithAutoScale(config.AutoScale.Up, config.AutoScale.Down)
|
||||
k8sWatcher.WithNamespace(config.KubeNamespace)
|
||||
routeWatchers = append(routeWatchers, k8sWatcher)
|
||||
} else if config.KubeConfig != "" {
|
||||
err := K8sWatcher.StartWithConfig(ctx, config.KubeConfig, config.AutoScale.Up, config.AutoScale.Down)
|
||||
k8sWatcher, err := NewK8sWatcherWithConfig(config.KubeConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not start k8s integration with kube config: %w", err)
|
||||
return nil, fmt.Errorf("could not create k8s watcher with kube config: %w", err)
|
||||
}
|
||||
k8sWatcher.WithAutoScale(config.AutoScale.Up, config.AutoScale.Down)
|
||||
k8sWatcher.WithNamespace(config.KubeNamespace)
|
||||
routeWatchers = append(routeWatchers, k8sWatcher)
|
||||
}
|
||||
|
||||
// TODO convert to RouteFinder
|
||||
if config.InDocker {
|
||||
err = DockerWatcher.Start(config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down)
|
||||
if err != nil {
|
||||
@@ -141,6 +150,7 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO convert to RouteFinder
|
||||
if config.InDockerSwarm {
|
||||
err = DockerSwarmWatcher.Start(config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down)
|
||||
if err != nil {
|
||||
@@ -150,6 +160,13 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
|
||||
}
|
||||
}
|
||||
|
||||
for _, watcher := range routeWatchers {
|
||||
err := watcher.Start(ctx, Routes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not start route watcher %s: %w", watcher, err)
|
||||
}
|
||||
}
|
||||
|
||||
Routes.SimplifySRV(config.SimplifySRV)
|
||||
|
||||
err = metricsBuilder.Start(ctx)
|
||||
|
||||
Reference in New Issue
Block a user