Fix docker scaling and show loading MOTD (#529)

* fix: Reduce log spam for sleeping servers

* fix: Fix autodownscaling for initial player connection

* fix: Instant route updating when a docker container is downscaled

* feat: Show asleep or loading motd while the server is waking up
This commit is contained in:
Lenart Kos
2026-02-26 03:46:51 +01:00
committed by GitHub
parent ea6419bece
commit 2023e73892
13 changed files with 220 additions and 78 deletions
+5 -1
View File
@@ -27,7 +27,9 @@ Some other features included:
-auto-scale-allow-deny string -auto-scale-allow-deny string
Path to config for server allowlists and denylists. If a global/server entry is specified, only players allowed to connect to the server will be able to trigger a scale up when -auto-scale-up is enabled or cancel active down scalers when -auto-scale-down is enabled (env AUTO_SCALE_ALLOW_DENY) Path to config for server allowlists and denylists. If a global/server entry is specified, only players allowed to connect to the server will be able to trigger a scale up when -auto-scale-up is enabled or cancel active down scalers when -auto-scale-down is enabled (env AUTO_SCALE_ALLOW_DENY)
-auto-scale-asleep-motd string -auto-scale-asleep-motd string
MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served (env AUTO_SCALE_ASLEEP_MOTD) MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served (env AUTO_SCALE_ASLEEP_MOTD)
-auto-scale-loading-motd string
MOTD to display while auto-scaled Docker servers are waking up; if empty, asleep status will be served (env AUTO_SCALE_LOADING_MOTD)
-auto-scale-down -auto-scale-down
Scale to zero after idle. For Kubernetes, decreases StatefulSet replicas from 1 to 0. For Docker, gracefully stops the container when there are no connections (env AUTO_SCALE_DOWN) Scale to zero after idle. For Kubernetes, decreases StatefulSet replicas from 1 to 0. For Docker, gracefully stops the container when there are no connections (env AUTO_SCALE_DOWN)
-auto-scale-down-after string -auto-scale-down-after string
@@ -176,6 +178,7 @@ These are the labels scanned:
- `mc-router.auto-scale-down`: Per-container override to enable/disable auto scale down for Docker. When true (or left unspecified and the global `-auto-scale-down` flag is enabled), mc-router will stop this container after it has been idle for the configured `-auto-scale-down-after` duration. - `mc-router.auto-scale-down`: Per-container override to enable/disable auto scale down for Docker. When true (or left unspecified and the global `-auto-scale-down` flag is enabled), mc-router will stop this container after it has been idle for the configured `-auto-scale-down-after` duration.
- `mc-router.auto-scale-asleep-motd`: Per-container override for MOTD to show when container is scaled to zero. If empty or not set the host will - `mc-router.auto-scale-asleep-motd`: Per-container override for MOTD to show when container is scaled to zero. If empty or not set the host will
appear unresponsive. appear unresponsive.
- `mc-router.auto-scale-loading-motd`: Per-container override for MOTD to show while the container is waking and not yet reachable. If empty or not set, the global `-auto-scale-loading-motd` value is used.
#### Docker Auto Scale Up/Down #### Docker Auto Scale Up/Down
@@ -198,6 +201,7 @@ For usage with docker compose refer to the [examples/docker-autoscale/compose.ym
Behavior: Behavior:
- When a client connects to a labeled hostname and the container is stopped or paused, mc-router will start/unpause it and wait until it becomes reachable (up to ~60s). - When a client connects to a labeled hostname and the container is stopped or paused, mc-router will start/unpause it and wait until it becomes reachable (up to ~60s).
- While that wake-up is in progress and status pings are received, mc-router can return a loading MOTD (per-container override or `-auto-scale-loading-motd`).
- When no clients remain connected and the idle timer elapses (`-auto-scale-down-after`), mc-router gracefully stops the container. - When no clients remain connected and the idle timer elapses (`-auto-scale-down-after`), mc-router gracefully stops the container.
Note: Docker Swarm discovery is supported; however, auto scale up/down is not yet supported for Swarm services. Note: Docker Swarm discovery is supported; however, auto scale up/down is not yet supported for Swarm services.
+2 -2
View File
@@ -81,7 +81,7 @@ func routesCreateHandler(writer http.ResponseWriter, request *http.Request) {
return return
} }
Routes.CreateMapping(definition.ServerAddress, definition.Backend, "", nil, nil, "") Routes.CreateMapping(definition.ServerAddress, definition.Backend, "", nil, nil, "", "")
RoutesConfigLoader.SaveRoutes() RoutesConfigLoader.SaveRoutes()
writer.WriteHeader(http.StatusCreated) writer.WriteHeader(http.StatusCreated)
} }
@@ -102,7 +102,7 @@ func routesSetDefault(writer http.ResponseWriter, request *http.Request) {
return return
} }
Routes.SetDefaultRoute(body.Backend, "", nil, nil, "") Routes.SetDefaultRoute(body.Backend, "", nil, nil, "", "")
RoutesConfigLoader.SaveRoutes() RoutesConfigLoader.SaveRoutes()
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
} }
+6 -5
View File
@@ -8,11 +8,12 @@ type WebhookConfig struct {
} }
type AutoScale struct { type AutoScale struct {
Up bool `usage:"Scale from zero on access. For Kubernetes, increases StatefulSet replicas from 0 to 1. For Docker, starts or unpauses the container when accessed"` Up bool `usage:"Scale from zero on access. For Kubernetes, increases StatefulSet replicas from 0 to 1. For Docker, starts or unpauses the container when accessed"`
Down bool `default:"false" usage:"Scale to zero after idle. For Kubernetes, decreases StatefulSet replicas from 1 to 0. For Docker, gracefully stops the container when there are no connections"` Down bool `default:"false" usage:"Scale to zero after idle. For Kubernetes, decreases StatefulSet replicas from 1 to 0. For Docker, gracefully stops the container when there are no connections"`
DownAfter time.Duration `default:"10m" usage:"Server scale down delay after there are no connections"` DownAfter time.Duration `default:"10m" usage:"Server scale down delay after there are no connections"`
AllowDeny string `usage:"Path to config for server allowlists and denylists. If a global/server entry is specified, only players allowed to connect to the server will be able to trigger a scale up when -auto-scale-up is enabled or cancel active down scalers when -auto-scale-down is enabled"` AllowDeny string `usage:"Path to config for server allowlists and denylists. If a global/server entry is specified, only players allowed to connect to the server will be able to trigger a scale up when -auto-scale-up is enabled or cancel active down scalers when -auto-scale-down is enabled"`
AsleepMOTD string `usage:"MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served"` AsleepMOTD string `usage:"MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served"`
LoadingMOTD string `usage:"MOTD to display while auto-scaled Docker servers are waking up; if empty, asleep status will be served"`
} }
type RoutesConfig struct { type RoutesConfig struct {
+75 -13
View File
@@ -78,6 +78,7 @@ func NewConnector(ctx context.Context, metrics *ConnectorMetrics, sendProxyProto
autoScaleUpAllowDenyConfig: autoScaleUpAllowDenyConfig, autoScaleUpAllowDenyConfig: autoScaleUpAllowDenyConfig,
activeConnections: NewActiveConnections(), activeConnections: NewActiveConnections(),
scaleActiveConnections: NewActiveConnections(), scaleActiveConnections: NewActiveConnections(),
wakingServers: NewActiveConnections(),
} }
} }
@@ -97,12 +98,14 @@ type Connector struct {
totalActiveConnections int32 totalActiveConnections int32
activeConnections *ActiveConnections activeConnections *ActiveConnections
scaleActiveConnections *ActiveConnections scaleActiveConnections *ActiveConnections
wakingServers *ActiveConnections
connectionsCond *sync.Cond connectionsCond *sync.Cond
ngrok NgrokConnector ngrok NgrokConnector
clientFilter *ClientFilter clientFilter *ClientFilter
autoScaleUpAllowDenyConfig *AllowDenyConfig autoScaleUpAllowDenyConfig *AllowDenyConfig
connectionNotifier ConnectionNotifier connectionNotifier ConnectionNotifier
asleepMOTD string asleepMOTD string
loadingMOTD string
} }
func (c *Connector) UseConnectionNotifier(notifier ConnectionNotifier) { func (c *Connector) UseConnectionNotifier(notifier ConnectionNotifier) {
@@ -364,9 +367,11 @@ func (c *Connector) HandleConnection(frontendConn net.Conn) {
// serveStatus writes a predefined status JSON and optionally handles ping/pong // serveStatus writes a predefined status JSON and optionally handles ping/pong
func (c *Connector) serveStatus(frontendConn net.Conn, reader *bufio.Reader, serverAddress string, clientProtocol int) { func (c *Connector) serveStatus(frontendConn net.Conn, reader *bufio.Reader, serverAddress string, clientProtocol int) {
motd := Routes.GetAsleepMOTD(serverAddress) motd := ""
if motd == "" { if c.isWakeInProgress(serverAddress) {
motd = c.asleepMOTD motd = c.getLoadingMOTD(serverAddress)
} else {
motd = c.getAsleepMOTD(serverAddress)
} }
if motd == "" { if motd == "" {
return return
@@ -453,8 +458,13 @@ func (c *Connector) serveStatus(frontendConn net.Conn, reader *bufio.Reader, ser
} }
// serveLegacyStatus writes a simple legacy SLP response and closes the connection // serveLegacyStatus writes a simple legacy SLP response and closes the connection
func (c *Connector) serveLegacyStatus(frontendConn net.Conn) { func (c *Connector) serveLegacyStatus(frontendConn net.Conn, serverAddress string) {
motd := c.asleepMOTD motd := ""
if c.isWakeInProgress(serverAddress) {
motd = c.getLoadingMOTD(serverAddress)
} else {
motd = c.getAsleepMOTD(serverAddress)
}
if motd == "" { if motd == "" {
return return
} }
@@ -550,7 +560,9 @@ func (c *Connector) findAndConnectBackend(frontendConn net.Conn,
} }
cleanupCheckScaleDown = true cleanupCheckScaleDown = true
logrus.WithField("serverAddress", serverAddress).Info("Waking up backend server") logrus.WithField("serverAddress", serverAddress).Info("Waking up backend server")
c.wakingServers.Increment(serverAddress)
newBackendHostPort, err := waker(c.ctx) newBackendHostPort, err := waker(c.ctx)
c.wakingServers.Decrement(serverAddress)
if err != nil { if err != nil {
logrus.WithFields(logrus.Fields{"serverAddress": serverAddress}).WithError(err).Error("failed to wake up backend") logrus.WithFields(logrus.Fields{"serverAddress": serverAddress}).WithError(err).Error("failed to wake up backend")
c.metrics.Errors.With("type", "wakeup_failed").Add(1) c.metrics.Errors.With("type", "wakeup_failed").Add(1)
@@ -561,6 +573,9 @@ func (c *Connector) findAndConnectBackend(frontendConn net.Conn,
c.metrics.Errors.With("type", "wakeup_no_address").Add(1) c.metrics.Errors.With("type", "wakeup_no_address").Add(1)
return return
} }
if scalingTarget == "" {
scalingTarget = newBackendHostPort
}
// Cancel again in case any routes were changed during wake up // Cancel again in case any routes were changed during wake up
DownScaler.Cancel(scalingTarget) DownScaler.Cancel(scalingTarget)
backendHostPort = newBackendHostPort backendHostPort = newBackendHostPort
@@ -571,13 +586,15 @@ func (c *Connector) findAndConnectBackend(frontendConn net.Conn,
} }
} }
if backendHostPort == "" { if backendHostPort == "" || (c.isWakeInProgress(serverAddress) && nextState == mcproto.StateStatus) {
logrus. if waker == nil {
WithField("serverAddress", serverAddress). logrus.
WithField("resolvedHost", resolvedHost). WithField("serverAddress", serverAddress).
WithField("player", playerInfo). WithField("resolvedHost", resolvedHost).
Warn("Unable to find registered backend") WithField("player", playerInfo).
c.metrics.Errors.With("type", "missing_backend").Add(1) Warn("Unable to find registered backend")
c.metrics.Errors.With("type", "missing_backend").Add(1)
}
if c.connectionNotifier != nil { if c.connectionNotifier != nil {
err := c.connectionNotifier.NotifyMissingBackend(c.ctx, clientAddr, serverAddress, playerInfo) err := c.connectionNotifier.NotifyMissingBackend(c.ctx, clientAddr, serverAddress, playerInfo)
@@ -597,7 +614,7 @@ func (c *Connector) findAndConnectBackend(frontendConn net.Conn,
// Read Status Request and Ping directly from the client connection // Read Status Request and Ping directly from the client connection
br := bufio.NewReader(frontendConn) br := bufio.NewReader(frontendConn)
if isLegacy { if isLegacy {
c.serveLegacyStatus(frontendConn) c.serveLegacyStatus(frontendConn, serverAddress)
} else { } else {
c.serveStatus(frontendConn, br, serverAddress, clientProtocol) c.serveStatus(frontendConn, br, serverAddress, clientProtocol)
} }
@@ -605,6 +622,18 @@ func (c *Connector) findAndConnectBackend(frontendConn net.Conn,
return return
} }
if c.isWakeInProgress(serverAddress) {
logrus.
WithField("serverAddress", serverAddress).
WithField("resolvedHost", resolvedHost).
WithField("player", playerInfo).
Debug("Waiting for backend to wake up before connecting")
// TODO: replace with event-based notification
for c.isWakeInProgress(serverAddress) {
time.Sleep(500 * time.Millisecond)
}
}
logrus. logrus.
WithField("client", clientAddr). WithField("client", clientAddr).
WithField("server", serverAddress). WithField("server", serverAddress).
@@ -793,6 +822,39 @@ func (c *Connector) UseAsleepMOTD(motd string) {
c.asleepMOTD = motd c.asleepMOTD = motd
} }
// UseLoadingMOTD configures a predefined MOTD to serve when backends are waking up
func (c *Connector) UseLoadingMOTD(motd string) {
c.loadingMOTD = motd
}
func (c *Connector) isWakeInProgress(serverAddress string) bool {
if serverAddress == "" {
return false
}
return c.wakingServers.GetCount(serverAddress) > 0
}
func (c *Connector) getAsleepMOTD(serverAddress string) string {
motd := Routes.GetAsleepMOTD(serverAddress)
if motd == "" {
motd = c.asleepMOTD
}
return motd
}
func (c *Connector) getLoadingMOTD(serverAddress string) string {
motd := Routes.GetLoadingMOTD(serverAddress)
if motd == "" {
motd = c.loadingMOTD
}
// If no specific loading MOTD, fall back to asleep MOTD
if motd == "" {
return c.getAsleepMOTD(serverAddress)
}
return motd
}
// getVersionInfo falls back to client protocol and a derived name but in future // getVersionInfo falls back to client protocol and a derived name but in future
// could be extended to cache server-reported versions // could be extended to cache server-reported versions
func (c *Connector) getVersionInfo(_ string, clientProtocol int) (string, int) { func (c *Connector) getVersionInfo(_ string, clientProtocol int) (string, int) {
+33
View File
@@ -77,3 +77,36 @@ func parseTrustedProxyNets(nets []string) []*net.IPNet {
} }
return parsedNets return parsedNets
} }
func TestConnectorWakeTracking(t *testing.T) {
c := &Connector{wakingServers: NewActiveConnections()}
assert.False(t, c.isWakeInProgress("scale-target"))
c.wakingServers.Increment("scale-target")
assert.True(t, c.isWakeInProgress("scale-target"))
// track concurrent wake operations for same route
c.wakingServers.Increment("scale-target")
c.wakingServers.Decrement("scale-target")
assert.True(t, c.isWakeInProgress("scale-target"))
c.wakingServers.Decrement("scale-target")
assert.False(t, c.isWakeInProgress("scale-target"))
}
func TestConnectorGetLoadingMOTD(t *testing.T) {
oldRoutes := Routes
defer func() {
Routes = oldRoutes
}()
Routes = NewRoutes()
Routes.CreateMapping("mc.example.com", "backend:25565", "", nil, nil, "", "route loading")
c := &Connector{loadingMOTD: "global loading"}
assert.Equal(t, "route loading", c.getLoadingMOTD("mc.example.com"))
assert.Equal(t, "global loading", c.getLoadingMOTD("other.example.com"))
Routes.SetDefaultRoute("default:25565", "", nil, nil, "", "default loading")
assert.Equal(t, "default loading", c.getLoadingMOTD(""))
}
+22 -8
View File
@@ -26,6 +26,7 @@ const (
DockerRouterLabelAutoScaleUp = "mc-router.auto-scale-up" DockerRouterLabelAutoScaleUp = "mc-router.auto-scale-up"
DockerRouterLabelAutoScaleDown = "mc-router.auto-scale-down" DockerRouterLabelAutoScaleDown = "mc-router.auto-scale-down"
DockerRouterLabelAutoScaleAsleepMOTD = "mc-router.auto-scale-asleep-motd" DockerRouterLabelAutoScaleAsleepMOTD = "mc-router.auto-scale-asleep-motd"
DockerRouterLabelAutoScaleLoadingMOTD = "mc-router.auto-scale-loading-motd"
) )
type dockerWatcherConfig struct { type dockerWatcherConfig struct {
@@ -163,6 +164,11 @@ func (w *dockerWatcherImpl) makeSleeperFunc(rc *routableContainer) SleeperFunc {
return err return err
} }
} }
err = w.monitorContainers(ctx)
if err != nil {
logrus.WithError(err).Error("Docker monitoring failed")
return err
}
return nil return nil
} }
} }
@@ -186,23 +192,24 @@ func (w *dockerWatcherImpl) monitorContainers(ctx context.Context) error {
wakerFunc := w.makeWakerFunc(rs) wakerFunc := w.makeWakerFunc(rs)
sleeperFunc := w.makeSleeperFunc(rs) sleeperFunc := w.makeSleeperFunc(rs)
if rs.externalContainerName != "" { if rs.externalContainerName != "" {
Routes.CreateMapping(rs.externalContainerName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD) Routes.CreateMapping(rs.externalContainerName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD, rs.autoScaleLoadingMOTD)
} else { } else {
Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD) Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD, rs.autoScaleLoadingMOTD)
} }
} else if oldRs.containerEndpoint != rs.containerEndpoint || } else if oldRs.containerEndpoint != rs.containerEndpoint ||
oldRs.containerID != rs.containerID || oldRs.containerID != rs.containerID ||
oldRs.autoScaleUp != rs.autoScaleUp || oldRs.autoScaleUp != rs.autoScaleUp ||
oldRs.autoScaleDown != rs.autoScaleDown || oldRs.autoScaleDown != rs.autoScaleDown ||
oldRs.autoScaleAsleepMOTD != rs.autoScaleAsleepMOTD { oldRs.autoScaleAsleepMOTD != rs.autoScaleAsleepMOTD ||
oldRs.autoScaleLoadingMOTD != rs.autoScaleLoadingMOTD {
w.containerMap[rs.externalContainerName] = rs w.containerMap[rs.externalContainerName] = rs
wakerFunc := w.makeWakerFunc(rs) wakerFunc := w.makeWakerFunc(rs)
sleeperFunc := w.makeSleeperFunc(rs) sleeperFunc := w.makeSleeperFunc(rs)
if rs.externalContainerName != "" { if rs.externalContainerName != "" {
Routes.DeleteMapping(rs.externalContainerName) Routes.DeleteMapping(rs.externalContainerName)
Routes.CreateMapping(rs.externalContainerName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD) Routes.CreateMapping(rs.externalContainerName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD, rs.autoScaleLoadingMOTD)
} else { } else {
Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD) Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, rs.autoScaleAsleepMOTD, rs.autoScaleLoadingMOTD)
} }
logrus.WithFields(logrus.Fields{"old": oldRs, "new": rs}).Debug("UPDATE") logrus.WithFields(logrus.Fields{"old": oldRs, "new": rs}).Debug("UPDATE")
} }
@@ -214,7 +221,7 @@ func (w *dockerWatcherImpl) monitorContainers(ctx context.Context) error {
if rs.externalContainerName != "" { if rs.externalContainerName != "" {
Routes.DeleteMapping(rs.externalContainerName) Routes.DeleteMapping(rs.externalContainerName)
} else { } else {
Routes.SetDefaultRoute("", "", nil, nil, "") Routes.SetDefaultRoute("", "", nil, nil, "", "")
} }
logrus.WithField("routableContainer", rs).Debug("DELETE") logrus.WithField("routableContainer", rs).Debug("DELETE")
} }
@@ -257,9 +264,9 @@ func (w *dockerWatcherImpl) Start(ctx context.Context) error {
wakerFunc := w.makeWakerFunc(c) wakerFunc := w.makeWakerFunc(c)
sleeperFunc := w.makeSleeperFunc(c) sleeperFunc := w.makeSleeperFunc(c)
if c.externalContainerName != "" { if c.externalContainerName != "" {
Routes.CreateMapping(c.externalContainerName, c.containerEndpoint, "", wakerFunc, sleeperFunc, c.autoScaleAsleepMOTD) Routes.CreateMapping(c.externalContainerName, c.containerEndpoint, "", wakerFunc, sleeperFunc, c.autoScaleAsleepMOTD, c.autoScaleLoadingMOTD)
} else { } else {
Routes.SetDefaultRoute(c.containerEndpoint, "", wakerFunc, sleeperFunc, c.autoScaleAsleepMOTD) Routes.SetDefaultRoute(c.containerEndpoint, "", wakerFunc, sleeperFunc, c.autoScaleAsleepMOTD, c.autoScaleLoadingMOTD)
} }
} }
@@ -315,6 +322,7 @@ func (w *dockerWatcherImpl) listContainers(ctx context.Context) ([]*routableCont
autoScaleUp: data.autoScaleUp, autoScaleUp: data.autoScaleUp,
autoScaleDown: data.autoScaleDown, autoScaleDown: data.autoScaleDown,
autoScaleAsleepMOTD: data.autoScaleAsleepMOTD, autoScaleAsleepMOTD: data.autoScaleAsleepMOTD,
autoScaleLoadingMOTD: data.autoScaleLoadingMOTD,
}) })
} }
if data.def != nil && *data.def { if data.def != nil && *data.def {
@@ -325,6 +333,7 @@ func (w *dockerWatcherImpl) listContainers(ctx context.Context) ([]*routableCont
autoScaleUp: data.autoScaleUp, autoScaleUp: data.autoScaleUp,
autoScaleDown: data.autoScaleDown, autoScaleDown: data.autoScaleDown,
autoScaleAsleepMOTD: data.autoScaleAsleepMOTD, autoScaleAsleepMOTD: data.autoScaleAsleepMOTD,
autoScaleLoadingMOTD: data.autoScaleLoadingMOTD,
}) })
} }
} }
@@ -341,6 +350,7 @@ type parsedDockerContainerData struct {
autoScaleDown bool autoScaleDown bool
autoScaleUp bool autoScaleUp bool
autoScaleAsleepMOTD string autoScaleAsleepMOTD string
autoScaleLoadingMOTD string
notRunning bool notRunning bool
} }
@@ -419,6 +429,9 @@ func (w *dockerWatcherImpl) parseContainerData(container *container.InspectRespo
if key == DockerRouterLabelAutoScaleAsleepMOTD { if key == DockerRouterLabelAutoScaleAsleepMOTD {
data.autoScaleAsleepMOTD = value data.autoScaleAsleepMOTD = value
} }
if key == DockerRouterLabelAutoScaleLoadingMOTD {
data.autoScaleLoadingMOTD = value
}
} }
// probably not minecraft related // probably not minecraft related
@@ -499,4 +512,5 @@ type routableContainer struct {
autoScaleUp bool autoScaleUp bool
autoScaleDown bool autoScaleDown bool
autoScaleAsleepMOTD string autoScaleAsleepMOTD string
autoScaleLoadingMOTD string
} }
+7 -7
View File
@@ -89,9 +89,9 @@ func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
wakerFunc := w.makeWakerFunc(s) wakerFunc := w.makeWakerFunc(s)
sleeperFunc := w.makeSleeperFunc(s) sleeperFunc := w.makeSleeperFunc(s)
if s.externalServiceName != "" { if s.externalServiceName != "" {
Routes.CreateMapping(s.externalServiceName, s.containerEndpoint, "", wakerFunc, sleeperFunc, "") Routes.CreateMapping(s.externalServiceName, s.containerEndpoint, "", wakerFunc, sleeperFunc, "", "")
} else { } else {
Routes.SetDefaultRoute(s.containerEndpoint, "", wakerFunc, sleeperFunc, "") Routes.SetDefaultRoute(s.containerEndpoint, "", wakerFunc, sleeperFunc, "", "")
} }
} }
@@ -113,9 +113,9 @@ func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
wakerFunc := w.makeWakerFunc(rs) wakerFunc := w.makeWakerFunc(rs)
sleeperFunc := w.makeSleeperFunc(rs) sleeperFunc := w.makeSleeperFunc(rs)
if rs.externalServiceName != "" { if rs.externalServiceName != "" {
Routes.CreateMapping(rs.externalServiceName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, "") Routes.CreateMapping(rs.externalServiceName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, "", "")
} else { } else {
Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, "") Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, "", "")
} }
} else if oldRs.containerEndpoint != rs.containerEndpoint { } else if oldRs.containerEndpoint != rs.containerEndpoint {
serviceMap[rs.externalServiceName] = rs serviceMap[rs.externalServiceName] = rs
@@ -123,9 +123,9 @@ func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
sleeperFunc := w.makeSleeperFunc(rs) sleeperFunc := w.makeSleeperFunc(rs)
if rs.externalServiceName != "" { if rs.externalServiceName != "" {
Routes.DeleteMapping(rs.externalServiceName) Routes.DeleteMapping(rs.externalServiceName)
Routes.CreateMapping(rs.externalServiceName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, "") Routes.CreateMapping(rs.externalServiceName, rs.containerEndpoint, "", wakerFunc, sleeperFunc, "", "")
} else { } else {
Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, "") Routes.SetDefaultRoute(rs.containerEndpoint, "", wakerFunc, sleeperFunc, "", "")
} }
logrus.WithFields(logrus.Fields{"old": oldRs, "new": rs}).Debug("UPDATE") logrus.WithFields(logrus.Fields{"old": oldRs, "new": rs}).Debug("UPDATE")
} }
@@ -137,7 +137,7 @@ func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
if rs.externalServiceName != "" { if rs.externalServiceName != "" {
Routes.DeleteMapping(rs.externalServiceName) Routes.DeleteMapping(rs.externalServiceName)
} else { } else {
Routes.SetDefaultRoute("", "", nil, nil, "") Routes.SetDefaultRoute("", "", nil, nil, "", "")
} }
logrus.WithField("routableService", rs).Debug("DELETE") logrus.WithField("routableService", rs).Debug("DELETE")
} }
+5 -5
View File
@@ -185,9 +185,9 @@ func (w *K8sWatcher) handleUpdate(oldObj interface{}, newObj interface{}) {
"new": newRoutableService, "new": newRoutableService,
}).Debug("UPDATE") }).Debug("UPDATE")
if newRoutableService.externalServiceName != "" { if newRoutableService.externalServiceName != "" {
w.routesHandler.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint, newRoutableService.scalingTarget, newRoutableService.autoScaleUp, newRoutableService.autoScaleDown, "") w.routesHandler.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint, newRoutableService.scalingTarget, newRoutableService.autoScaleUp, newRoutableService.autoScaleDown, "", "")
} else { } else {
w.routesHandler.SetDefaultRoute(newRoutableService.containerEndpoint, newRoutableService.scalingTarget, newRoutableService.autoScaleUp, newRoutableService.autoScaleDown, "") w.routesHandler.SetDefaultRoute(newRoutableService.containerEndpoint, newRoutableService.scalingTarget, newRoutableService.autoScaleUp, newRoutableService.autoScaleDown, "", "")
} }
} }
} }
@@ -202,7 +202,7 @@ func (w *K8sWatcher) handleDelete(obj interface{}) {
if routableService.externalServiceName != "" { if routableService.externalServiceName != "" {
w.routesHandler.DeleteMapping(routableService.externalServiceName) w.routesHandler.DeleteMapping(routableService.externalServiceName)
} else { } else {
w.routesHandler.SetDefaultRoute("", "", nil, nil, "") w.routesHandler.SetDefaultRoute("", "", nil, nil, "", "")
} }
} }
} }
@@ -216,9 +216,9 @@ func (w *K8sWatcher) handleAdd(obj interface{}) {
logrus.WithField("routableService", routableService).Debug("ADD") logrus.WithField("routableService", routableService).Debug("ADD")
if routableService.externalServiceName != "" { if routableService.externalServiceName != "" {
w.routesHandler.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint, routableService.scalingTarget, routableService.autoScaleUp, routableService.autoScaleDown, "") w.routesHandler.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint, routableService.scalingTarget, routableService.autoScaleUp, routableService.autoScaleDown, "", "")
} else { } else {
w.routesHandler.SetDefaultRoute(routableService.containerEndpoint, routableService.scalingTarget, routableService.autoScaleUp, routableService.autoScaleDown, "") w.routesHandler.SetDefaultRoute(routableService.containerEndpoint, routableService.scalingTarget, routableService.autoScaleUp, routableService.autoScaleDown, "", "")
} }
} }
} }
+20 -20
View File
@@ -28,16 +28,16 @@ func (m *MockedRoutesHandler) GetBackendForServer(server string) string {
} }
} }
func (m *MockedRoutesHandler) CreateMapping(serverAddress string, backend string, scaleKey string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string) { func (m *MockedRoutesHandler) CreateMapping(serverAddress string, backend string, scaleKey string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string, loadingMOTD string) {
m.MethodCalled("CreateMapping", serverAddress, backend, scaleKey, waker, sleeper, asleepMOTD) m.MethodCalled("CreateMapping", serverAddress, backend, scaleKey, waker, sleeper, asleepMOTD, loadingMOTD)
if m.routes == nil { if m.routes == nil {
m.routes = make(map[string]string) m.routes = make(map[string]string)
} }
m.routes[serverAddress] = backend m.routes[serverAddress] = backend
} }
func (m *MockedRoutesHandler) SetDefaultRoute(backend string, scaleKey string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string) { func (m *MockedRoutesHandler) SetDefaultRoute(backend string, scaleKey string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string, loadingMOTD string) {
m.MethodCalled("SetDefaultRoute", backend, scaleKey, waker, sleeper, asleepMOTD) m.MethodCalled("SetDefaultRoute", backend, scaleKey, waker, sleeper, asleepMOTD, loadingMOTD)
if m.routes == nil { if m.routes == nil {
m.routes = make(map[string]string) m.routes = make(map[string]string)
} }
@@ -183,8 +183,8 @@ func TestK8sWatcherImpl_handleAddThenUpdate(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -264,8 +264,8 @@ func TestK8sWatcherImpl_handleAddThenDelete(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -363,8 +363,8 @@ func TestK8s_externalName(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -431,8 +431,8 @@ func TestK8s_proxyServerName(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -456,8 +456,8 @@ func TestK8s_proxyServerNameScaleEndpoint(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -472,15 +472,15 @@ func TestK8s_proxyServerNameScaleEndpoint(t *testing.T) {
watcher.handleAdd(&svc) watcher.handleAdd(&svc)
// Verify CreateMapping was called with the correct scaleKey (original endpoint) // Verify CreateMapping was called with the correct scaleKey (original endpoint)
routesHandler.AssertCalled(t, "CreateMapping", "mc.example.com", "velocity:25577", "10.0.0.5:25565", mock.Anything, mock.Anything, mock.Anything) routesHandler.AssertCalled(t, "CreateMapping", "mc.example.com", "velocity:25577", "10.0.0.5:25565", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
} }
func TestK8s_proxyServerNameUpdate(t *testing.T) { func TestK8s_proxyServerNameUpdate(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -509,8 +509,8 @@ func TestK8s_autoScaleWithoutProxy(t *testing.T) {
DownScaler = NewDownScaler(context.Background(), false, 1*time.Second) DownScaler = NewDownScaler(context.Background(), false, 1*time.Second)
routesHandler := new(MockedRoutesHandler) routesHandler := new(MockedRoutesHandler)
routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("CreateMapping", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return() routesHandler.On("SetDefaultRoute", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return()
routesHandler.On("GetAsleepMOTD", mock.Anything).Return("") routesHandler.On("GetAsleepMOTD", mock.Anything).Return("")
routesHandler.On("DeleteMapping", mock.Anything).Return(true) routesHandler.On("DeleteMapping", mock.Anything).Return(true)
@@ -532,5 +532,5 @@ func TestK8s_autoScaleWithoutProxy(t *testing.T) {
// CRITICAL: Verify scaleKey is set to the service endpoint (not empty) // CRITICAL: Verify scaleKey is set to the service endpoint (not empty)
// This ensures auto-scaling targets the correct StatefulSet // This ensures auto-scaling targets the correct StatefulSet
routesHandler.AssertCalled(t, "CreateMapping", "atm-10.example.com", "10.0.0.10:25565", "10.0.0.10:25565", mock.Anything, mock.Anything, mock.Anything) routesHandler.AssertCalled(t, "CreateMapping", "atm-10.example.com", "10.0.0.10:25565", "10.0.0.10:25565", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
} }
+23 -7
View File
@@ -36,8 +36,8 @@ type RouteFinder interface {
} }
type RoutesHandler interface { type RoutesHandler interface {
CreateMapping(serverAddress string, backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string) CreateMapping(serverAddress string, backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string, loadingMOTD string)
SetDefaultRoute(backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string) SetDefaultRoute(backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string, loadingMOTD string)
// DeleteMapping requests that the serverAddress be removed from routes. // DeleteMapping requests that the serverAddress be removed from routes.
// Returns true if the route existed. // Returns true if the route existed.
DeleteMapping(serverAddress string) bool DeleteMapping(serverAddress string) bool
@@ -59,6 +59,7 @@ type IRoutes interface {
GetMappings() map[string]string GetMappings() map[string]string
GetDefaultRoute() (string, string, WakerFunc, SleeperFunc) GetDefaultRoute() (string, string, WakerFunc, SleeperFunc)
GetAsleepMOTD(serverAddress string) string GetAsleepMOTD(serverAddress string) string
GetLoadingMOTD(serverAddress string) string
SimplifySRV(srvEnabled bool) SimplifySRV(srvEnabled bool)
} }
@@ -74,7 +75,7 @@ func NewRoutes() IRoutes {
func (r *routesImpl) RegisterAll(mappings map[string]string) { func (r *routesImpl) RegisterAll(mappings map[string]string) {
for k, v := range mappings { for k, v := range mappings {
r.CreateMapping(k, v, "", nil, nil, "") r.CreateMapping(k, v, "", nil, nil, "", "")
} }
} }
@@ -83,6 +84,7 @@ type mapping struct {
waker WakerFunc waker WakerFunc
sleeper SleeperFunc sleeper SleeperFunc
asleepMOTD string asleepMOTD string
loadingMOTD string
scalingTarget string // The endpoint to scale (may differ from backend when using proxy) scalingTarget string // The endpoint to scale (may differ from backend when using proxy)
} }
@@ -98,11 +100,11 @@ func (r *routesImpl) Reset() {
DownScaler.Reset() DownScaler.Reset()
} }
func (r *routesImpl) SetDefaultRoute(backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string) { func (r *routesImpl) SetDefaultRoute(backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string, loadingMOTD string) {
if scalingTarget == "" { if scalingTarget == "" {
scalingTarget = backend scalingTarget = backend
} }
r.defaultRoute = mapping{backend: backend, scalingTarget: scalingTarget, waker: waker, sleeper: sleeper, asleepMOTD: asleepMOTD} r.defaultRoute = mapping{backend: backend, scalingTarget: scalingTarget, waker: waker, sleeper: sleeper, asleepMOTD: asleepMOTD, loadingMOTD: loadingMOTD}
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"backend": backend, "backend": backend,
@@ -127,6 +129,20 @@ func (r *routesImpl) GetAsleepMOTD(serverAddress string) string {
return "" return ""
} }
func (r *routesImpl) GetLoadingMOTD(serverAddress string) string {
r.RLock()
defer r.RUnlock()
if serverAddress == "" {
return r.defaultRoute.loadingMOTD
}
if m, ok := r.mappings[serverAddress]; ok {
return m.loadingMOTD
}
return ""
}
func (r *routesImpl) SimplifySRV(srvEnabled bool) { func (r *routesImpl) SimplifySRV(srvEnabled bool) {
r.simplifySRV = srvEnabled r.simplifySRV = srvEnabled
} }
@@ -225,7 +241,7 @@ func (r *routesImpl) DeleteMapping(serverAddress string) bool {
} }
} }
func (r *routesImpl) CreateMapping(serverAddress string, backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string) { func (r *routesImpl) CreateMapping(serverAddress string, backend string, scalingTarget string, waker WakerFunc, sleeper SleeperFunc, asleepMOTD string, loadingMOTD string) {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
@@ -239,7 +255,7 @@ func (r *routesImpl) CreateMapping(serverAddress string, backend string, scaling
"serverAddress": serverAddress, "serverAddress": serverAddress,
"backend": backend, "backend": backend,
}).Info("Created route mapping") }).Info("Created route mapping")
r.mappings[serverAddress] = mapping{backend: backend, scalingTarget: scalingTarget, waker: waker, sleeper: sleeper, asleepMOTD: asleepMOTD} r.mappings[serverAddress] = mapping{backend: backend, scalingTarget: scalingTarget, waker: waker, sleeper: sleeper, asleepMOTD: asleepMOTD, loadingMOTD: loadingMOTD}
// Trigger auto scale down when mapping is created to ensure servers are shut down if router restarts // Trigger auto scale down when mapping is created to ensure servers are shut down if router restarts
if DownScaler != nil && scalingTarget != "" { if DownScaler != nil && scalingTarget != "" {
+2 -2
View File
@@ -44,7 +44,7 @@ func (r *routesConfigLoader) Load(routesConfigFileName string) error {
} }
Routes.RegisterAll(config.Mappings) Routes.RegisterAll(config.Mappings)
Routes.SetDefaultRoute(config.DefaultServer, "", nil, nil, "") Routes.SetDefaultRoute(config.DefaultServer, "", nil, nil, "", "")
return nil return nil
} }
@@ -62,7 +62,7 @@ func (r *routesConfigLoader) Reload() error {
logrus.WithField("routesConfig", r.fileName).Info("Re-loading routes config file") logrus.WithField("routesConfig", r.fileName).Info("Re-loading routes config file")
Routes.Reset() Routes.Reset()
Routes.RegisterAll(config.Mappings) Routes.RegisterAll(config.Mappings)
Routes.SetDefaultRoute(config.DefaultServer, "", nil, nil, "") Routes.SetDefaultRoute(config.DefaultServer, "", nil, nil, "", "")
return nil return nil
} }
+18 -7
View File
@@ -68,7 +68,7 @@ func Test_routesImpl_FindBackendForServerAddress(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
r := NewRoutes() r := NewRoutes()
r.CreateMapping(tt.mapping.serverAddress, tt.mapping.backend, "", nil, nil, "") r.CreateMapping(tt.mapping.serverAddress, tt.mapping.backend, "", nil, nil, "", "")
if got, server, _, _, _ := r.FindBackendForServerAddress(context.Background(), tt.args.serverAddress); got != tt.want { if got, server, _, _, _ := r.FindBackendForServerAddress(context.Background(), tt.args.serverAddress); got != tt.want {
t.Errorf("routesImpl.FindBackendForServerAddress() = %v, want %v", got, tt.want) t.Errorf("routesImpl.FindBackendForServerAddress() = %v, want %v", got, tt.want)
@@ -84,7 +84,7 @@ func Test_routesImpl_ScaleKey(t *testing.T) {
t.Run("scaleKey defaults to backend when empty", func(t *testing.T) { t.Run("scaleKey defaults to backend when empty", func(t *testing.T) {
r := NewRoutes() r := NewRoutes()
r.CreateMapping("mc.example.com", "backend:25565", "", nil, nil, "") r.CreateMapping("mc.example.com", "backend:25565", "", nil, nil, "", "")
_, _, scaleKey, _, _ := r.FindBackendForServerAddress(context.Background(), "mc.example.com") _, _, scaleKey, _, _ := r.FindBackendForServerAddress(context.Background(), "mc.example.com")
assert.Equal(t, "backend:25565", scaleKey) assert.Equal(t, "backend:25565", scaleKey)
@@ -92,7 +92,7 @@ func Test_routesImpl_ScaleKey(t *testing.T) {
t.Run("scaleKey is set when provided", func(t *testing.T) { t.Run("scaleKey is set when provided", func(t *testing.T) {
r := NewRoutes() r := NewRoutes()
r.CreateMapping("mc.example.com", "proxy:25577", "10.0.0.5:25565", nil, nil, "") r.CreateMapping("mc.example.com", "proxy:25577", "10.0.0.5:25565", nil, nil, "", "")
backend, _, scaleKey, _, _ := r.FindBackendForServerAddress(context.Background(), "mc.example.com") backend, _, scaleKey, _, _ := r.FindBackendForServerAddress(context.Background(), "mc.example.com")
assert.Equal(t, "proxy:25577", backend) assert.Equal(t, "proxy:25577", backend)
@@ -108,8 +108,8 @@ func Test_routesImpl_ScaleKey(t *testing.T) {
} }
// Two routes with same proxy backend but different scaleKeys // Two routes with same proxy backend but different scaleKeys
r.CreateMapping("mc1.example.com", "proxy:25577", "10.0.0.1:25565", nil, sleeper, "") r.CreateMapping("mc1.example.com", "proxy:25577", "10.0.0.1:25565", nil, sleeper, "", "")
r.CreateMapping("mc2.example.com", "proxy:25577", "10.0.0.2:25565", nil, nil, "") r.CreateMapping("mc2.example.com", "proxy:25577", "10.0.0.2:25565", nil, nil, "", "")
sleepers := r.GetSleepers("10.0.0.1:25565") sleepers := r.GetSleepers("10.0.0.1:25565")
require.Len(t, sleepers, 1) require.Len(t, sleepers, 1)
@@ -127,7 +127,7 @@ func Test_routesImpl_ScaleKey(t *testing.T) {
t.Run("default route scaleKey", func(t *testing.T) { t.Run("default route scaleKey", func(t *testing.T) {
r := NewRoutes() r := NewRoutes()
r.SetDefaultRoute("proxy:25577", "10.0.0.5:25565", nil, nil, "") r.SetDefaultRoute("proxy:25577", "10.0.0.5:25565", nil, nil, "", "")
backend, scaleKey, _, _ := r.GetDefaultRoute() backend, scaleKey, _, _ := r.GetDefaultRoute()
assert.Equal(t, "proxy:25577", backend) assert.Equal(t, "proxy:25577", backend)
@@ -136,10 +136,21 @@ func Test_routesImpl_ScaleKey(t *testing.T) {
t.Run("default route scaleKey defaults to backend", func(t *testing.T) { t.Run("default route scaleKey defaults to backend", func(t *testing.T) {
r := NewRoutes() r := NewRoutes()
r.SetDefaultRoute("backend:25565", "", nil, nil, "") r.SetDefaultRoute("backend:25565", "", nil, nil, "", "")
backend, scaleKey, _, _ := r.GetDefaultRoute() backend, scaleKey, _, _ := r.GetDefaultRoute()
assert.Equal(t, "backend:25565", backend) assert.Equal(t, "backend:25565", backend)
assert.Equal(t, "backend:25565", scaleKey) assert.Equal(t, "backend:25565", scaleKey)
}) })
} }
func Test_routesImpl_LoadingMOTD(t *testing.T) {
r := NewRoutes()
r.CreateMapping("mc.example.com", "backend:25565", "", nil, nil, "asleep", "loading")
assert.Equal(t, "loading", r.GetLoadingMOTD("mc.example.com"))
assert.Equal(t, "", r.GetLoadingMOTD("other.example.com"))
r.SetDefaultRoute("default:25565", "", nil, nil, "default asleep", "default loading")
assert.Equal(t, "default loading", r.GetLoadingMOTD(""))
}
+2 -1
View File
@@ -69,7 +69,7 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
Routes.RegisterAll(config.Mapping) Routes.RegisterAll(config.Mapping)
if config.Default != "" { if config.Default != "" {
Routes.SetDefaultRoute(config.Default, "", nil, nil, "") Routes.SetDefaultRoute(config.Default, "", nil, nil, "", "")
} }
if config.ConnectionRateLimit < 1 { if config.ConnectionRateLimit < 1 {
@@ -83,6 +83,7 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
autoScaleAllowDenyConfig) autoScaleAllowDenyConfig)
connector.UseAsleepMOTD(config.AutoScale.AsleepMOTD) connector.UseAsleepMOTD(config.AutoScale.AsleepMOTD)
connector.UseLoadingMOTD(config.AutoScale.LoadingMOTD)
clientFilter, err := NewClientFilter(config.ClientsToAllow, config.ClientsToDeny) clientFilter, err := NewClientFilter(config.ClientsToAllow, config.ClientsToDeny)
if err != nil { if err != nil {