Persistent routes from REST API (#145)

This commit is contained in:
Lasse15 2022-12-05 14:58:13 +01:00 committed by GitHub
parent e1bb4980bb
commit 787ab54ca4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 186 additions and 0 deletions

View File

@ -50,6 +50,8 @@ Routes Minecraft client connections to backend servers based upon the requested
(env METRICS_BACKEND_CONFIG_INFLUXDB_USERNAME) (env METRICS_BACKEND_CONFIG_INFLUXDB_USERNAME)
-port port -port port
The port bound to listen for Minecraft client connections (env PORT) (default 25565) The port bound to listen for Minecraft client connections (env PORT) (default 25565)
-routes-config string
The path to the routes config file (env ROUTES_CONFIG)
-version -version
Output version and exit (env VERSION) Output version and exit (env VERSION)
``` ```

View File

@ -46,6 +46,7 @@ type Config struct {
MetricsBackend string `default:"discard" usage:"Backend to use for metrics exposure/publishing: discard,expvar,influxdb"` MetricsBackend string `default:"discard" usage:"Backend to use for metrics exposure/publishing: discard,expvar,influxdb"`
UseProxyProtocol bool `default:"false" usage:"Send PROXY protocol to backend servers"` UseProxyProtocol bool `default:"false" usage:"Send PROXY protocol to backend servers"`
MetricsBackendConfig MetricsBackendConfig MetricsBackendConfig MetricsBackendConfig
RoutesConfig string `usage:"Name or full path to routes config file"`
SimplifySRV bool `default:"false" usage:"Simplify fully qualified SRV records for mapping"` SimplifySRV bool `default:"false" usage:"Simplify fully qualified SRV records for mapping"`
} }
@ -100,6 +101,13 @@ func main() {
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
if config.RoutesConfig != "" {
err := server.RoutesConfig.ReadRoutesConfig(config.RoutesConfig)
if err != nil {
logrus.WithError(err).Error("Unable to load routes from config file")
}
}
server.Routes.RegisterAll(parseMappings(config.Mapping)) server.Routes.RegisterAll(parseMappings(config.Mapping))
if config.ConnectionRateLimit < 1 { if config.ConnectionRateLimit < 1 {

View File

@ -37,6 +37,7 @@ func routesListHandler(writer http.ResponseWriter, request *http.Request) {
func routesDeleteHandler(writer http.ResponseWriter, request *http.Request) { func routesDeleteHandler(writer http.ResponseWriter, request *http.Request) {
serverAddress := mux.Vars(request)["serverAddress"] serverAddress := mux.Vars(request)["serverAddress"]
RoutesConfig.DeleteMapping(serverAddress)
if serverAddress != "" { if serverAddress != "" {
if Routes.DeleteMapping(serverAddress) { if Routes.DeleteMapping(serverAddress) {
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
@ -63,6 +64,7 @@ func routesCreateHandler(writer http.ResponseWriter, request *http.Request) {
} }
Routes.CreateMapping(definition.ServerAddress, definition.Backend, func(ctx context.Context) error { return nil }) Routes.CreateMapping(definition.ServerAddress, definition.Backend, func(ctx context.Context) error { return nil })
RoutesConfig.AddMapping(definition.ServerAddress, definition.Backend)
writer.WriteHeader(http.StatusCreated) writer.WriteHeader(http.StatusCreated)
} }
@ -82,6 +84,7 @@ func routesSetDefault(writer http.ResponseWriter, request *http.Request) {
} }
Routes.SetDefaultRoute(body.Backend) Routes.SetDefaultRoute(body.Backend)
RoutesConfig.SetDefaultRoute(body.Backend)
writer.WriteHeader(http.StatusOK) writer.WriteHeader(http.StatusOK)
} }

173
server/routes_config.go Normal file
View File

@ -0,0 +1,173 @@
package server
import (
"encoding/json"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"io/fs"
"os"
"sync"
)
type IRoutesConfig interface {
ReadRoutesConfig(routesConfig string)
AddMapping(serverAddress string, backend string)
DeleteMapping(serverAddress string)
SetDefaultRoute(backend string)
}
var RoutesConfig = &routesConfigImpl{}
type routesConfigImpl struct {
sync.RWMutex
fileName string
}
type routesConfigStructure struct {
DefaultServer string `json:"default-server"`
Mappings map[string]string `json:"mappings"`
}
func (r *routesConfigImpl) ReadRoutesConfig(routesConfig string) error {
r.fileName = routesConfig
logrus.WithField("routesConfig", r.fileName).Info("Loading routes config file")
config, readErr := r.readRoutesConfigFile()
if readErr != nil {
if errors.Is(readErr, fs.ErrNotExist) {
logrus.WithField("routesConfig", r.fileName).Info("Routes config file doses not exist, skipping reading it")
// File doesn't exist -> ignore it
return nil
}
return errors.Wrap(readErr, "Could not load the routes config file")
}
Routes.RegisterAll(config.Mappings)
Routes.SetDefaultRoute(config.DefaultServer)
return nil
}
func (r *routesConfigImpl) AddMapping(serverAddress string, backend string) {
if !r.isRoutesConfigEnabled() {
return
}
config, readErr := r.readRoutesConfigFile()
if readErr != nil && !errors.Is(readErr, fs.ErrNotExist) {
logrus.WithError(readErr).Error("Could not read the routes config file")
return
}
if config.Mappings == nil {
config.Mappings = make(map[string]string)
}
config.Mappings[serverAddress] = backend
writeErr := r.writeRoutesConfigFile(config)
if writeErr != nil {
logrus.WithError(writeErr).Error("Could not write to the routes config file")
return
}
logrus.WithFields(logrus.Fields{
"serverAddress": serverAddress,
"backend": backend,
}).Info("Added route to routes config")
return
}
func (r *routesConfigImpl) SetDefaultRoute(backend string) {
if !r.isRoutesConfigEnabled() {
return
}
config, readErr := r.readRoutesConfigFile()
if readErr != nil && !errors.Is(readErr, fs.ErrNotExist) {
logrus.WithError(readErr).Error("Could not read the routes config file")
return
}
config.DefaultServer = backend
writeErr := r.writeRoutesConfigFile(config)
if writeErr != nil {
logrus.WithError(writeErr).Error("Could not write to the routes config file")
return
}
logrus.WithFields(logrus.Fields{
"backend": backend,
}).Info("Set default route in routes config")
return
}
func (r *routesConfigImpl) DeleteMapping(serverAddress string) {
if !r.isRoutesConfigEnabled() {
return
}
config, readErr := r.readRoutesConfigFile()
if readErr != nil && !errors.Is(readErr, fs.ErrNotExist) {
logrus.WithError(readErr).Error("Could not read the routes config file")
return
}
delete(config.Mappings, serverAddress)
writeErr := r.writeRoutesConfigFile(config)
if writeErr != nil {
logrus.WithError(writeErr).Error("Could not write to the routes config file")
return
}
logrus.WithField("serverAddress", serverAddress).Info("Deleted route in routes config")
return
}
func (r *routesConfigImpl) isRoutesConfigEnabled() bool {
return r.fileName != ""
}
func (r *routesConfigImpl) readRoutesConfigFile() (routesConfigStructure, error) {
r.RLock()
defer r.RUnlock()
config := routesConfigStructure{
"",
make(map[string]string),
}
file, fileErr := os.ReadFile(r.fileName)
if fileErr != nil {
return config, errors.Wrap(fileErr, "Could not load the routes config file")
}
parseErr := json.Unmarshal(file, &config)
if parseErr != nil {
return config, errors.Wrap(parseErr, "Could not parse the json routes config file")
}
return config, nil
}
func (r *routesConfigImpl) writeRoutesConfigFile(config routesConfigStructure) error {
r.Lock()
defer r.Unlock()
newFileContent, parseErr := json.Marshal(config)
if parseErr != nil {
return errors.Wrap(parseErr, "Could not parse the routes to json")
}
fileErr := os.WriteFile(r.fileName, newFileContent, 0664)
if fileErr != nil {
return errors.Wrap(fileErr, "Could not write to the routes config file")
}
return nil
}