From 15f7e626fb299da9a269fc721c26d179fbfa5f26 Mon Sep 17 00:00:00 2001 From: SkalaNetworks Date: Mon, 16 Oct 2023 18:52:50 +0000 Subject: [PATCH] feat(config): autoreload on config changes --- cmd/gobgpd/main.go | 63 ++++++++++++++++++++++----------- docs/sources/getting-started.md | 5 ++- go.mod | 3 +- go.sum | 4 +++ internal/pkg/config/serve.go | 13 +++++++ pkg/config/config.go | 6 ++++ 6 files changed, 72 insertions(+), 22 deletions(-) diff --git a/cmd/gobgpd/main.go b/cmd/gobgpd/main.go index bd1a1e61d..37f6793d1 100644 --- a/cmd/gobgpd/main.go +++ b/cmd/gobgpd/main.go @@ -28,6 +28,7 @@ import ( "os/signal" "runtime" "syscall" + "time" "github.com/coreos/go-systemd/v22/daemon" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" @@ -36,6 +37,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" + "golang.org/x/time/rate" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -52,26 +54,27 @@ func main() { signal.Notify(sigCh, syscall.SIGTERM, syscall.SIGINT) var opts struct { - ConfigFile string `short:"f" long:"config-file" description:"specifying a config file"` - ConfigType string `short:"t" long:"config-type" description:"specifying config type (toml, yaml, json)" default:"toml"` - LogLevel string `short:"l" long:"log-level" description:"specifying log level"` - LogPlain bool `short:"p" long:"log-plain" description:"use plain format for logging (json by default)"` - UseSyslog string `short:"s" long:"syslog" description:"use syslogd"` - Facility string `long:"syslog-facility" description:"specify syslog facility"` - DisableStdlog bool `long:"disable-stdlog" description:"disable standard logging"` - CPUs int `long:"cpus" description:"specify the number of CPUs to be used"` - GrpcHosts string `long:"api-hosts" description:"specify the hosts that gobgpd listens on" default:":50051"` - GracefulRestart bool `short:"r" long:"graceful-restart" description:"flag restart-state in graceful-restart capability"` - Dry bool `short:"d" long:"dry-run" description:"check configuration"` - PProfHost string `long:"pprof-host" description:"specify the host that gobgpd listens on for pprof and metrics" default:"localhost:6060"` - PProfDisable bool `long:"pprof-disable" description:"disable pprof profiling"` - MetricsPath string `long:"metrics-path" description:"specify path for prometheus metrics, empty value disables them" default:"/metrics"` - UseSdNotify bool `long:"sdnotify" description:"use sd_notify protocol"` - TLS bool `long:"tls" description:"enable TLS authentication for gRPC API"` - TLSCertFile string `long:"tls-cert-file" description:"The TLS cert file"` - TLSKeyFile string `long:"tls-key-file" description:"The TLS key file"` - TLSClientCAFile string `long:"tls-client-ca-file" description:"Optional TLS client CA file to authenticate clients against"` - Version bool `long:"version" description:"show version number"` + ConfigFile string `short:"f" long:"config-file" description:"specifying a config file"` + ConfigType string `short:"t" long:"config-type" description:"specifying config type (toml, yaml, json)" default:"toml"` + ConfigAutoReload bool `short:"a" long:"config-auto-reload" description:"activate config auto reload on changes"` + LogLevel string `short:"l" long:"log-level" description:"specifying log level"` + LogPlain bool `short:"p" long:"log-plain" description:"use plain format for logging (json by default)"` + UseSyslog string `short:"s" long:"syslog" description:"use syslogd"` + Facility string `long:"syslog-facility" description:"specify syslog facility"` + DisableStdlog bool `long:"disable-stdlog" description:"disable standard logging"` + CPUs int `long:"cpus" description:"specify the number of CPUs to be used"` + GrpcHosts string `long:"api-hosts" description:"specify the hosts that gobgpd listens on" default:":50051"` + GracefulRestart bool `short:"r" long:"graceful-restart" description:"flag restart-state in graceful-restart capability"` + Dry bool `short:"d" long:"dry-run" description:"check configuration"` + PProfHost string `long:"pprof-host" description:"specify the host that gobgpd listens on for pprof and metrics" default:"localhost:6060"` + PProfDisable bool `long:"pprof-disable" description:"disable pprof profiling"` + MetricsPath string `long:"metrics-path" description:"specify path for prometheus metrics, empty value disables them" default:"/metrics"` + UseSdNotify bool `long:"sdnotify" description:"use sd_notify protocol"` + TLS bool `long:"tls" description:"enable TLS authentication for gRPC API"` + TLSCertFile string `long:"tls-cert-file" description:"The TLS cert file"` + TLSKeyFile string `long:"tls-key-file" description:"The TLS key file"` + TLSClientCAFile string `long:"tls-client-ca-file" description:"Optional TLS client CA file to authenticate clients against"` + Version bool `long:"version" description:"show version number"` } _, err := flags.Parse(&opts) if err != nil { @@ -235,6 +238,26 @@ func main() { }).Fatalf("Failed to apply initial configuration %s", opts.ConfigFile) } + if opts.ConfigAutoReload { + logger.WithFields(logrus.Fields{ + "Topic": "Config", + }).Info("Watching for config changes to trigger auto-reload") + + // Writing to the config may trigger many events in quick successions + // To prevent abusive reloads, we ignore any event in a 100ms window + rateLimiter := rate.Sometimes{Interval: 100 * time.Millisecond} + + config.WatchConfigFile(opts.ConfigFile, opts.ConfigType, func() { + rateLimiter.Do(func() { + logger.WithFields(logrus.Fields{ + "Topic": "Config", + }).Info("Config changes detected, reloading configuration") + + sigCh <- syscall.SIGHUP + }) + }) + } + for sig := range sigCh { if sig != syscall.SIGHUP { stopServer(bgpServer, opts.UseSdNotify) diff --git a/docs/sources/getting-started.md b/docs/sources/getting-started.md index 8dfad0e78..46e53f41c 100644 --- a/docs/sources/getting-started.md +++ b/docs/sources/getting-started.md @@ -44,7 +44,7 @@ $ sudo -E gobgpd -f gobgpd.conf If you use a configuration format other than `toml`, you must specify the format by `-t` option. -equivalent yaml configuration. +Equivalent yaml configuration. ```yaml global: @@ -66,6 +66,9 @@ $ sudo -E gobgpd -t yaml -f gobgpd.yml {"level":"info","msg":"Peer 10.0.255.2 is added","time":"2015-04-06T20:32:28+09:00"} ``` +Sending the `SIGHUP` signal to `gobgpd` triggers a configuration reload. +The `-a` option enables the auto reloading of the configuration whenever a change is detected. + Let's show the information of all the peers. ```bash diff --git a/go.mod b/go.mod index 0c436c188..f9f5e67a0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 github.com/eapache/channels v1.1.0 + github.com/fsnotify/fsnotify v1.6.0 github.com/go-test/deep v1.1.0 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 @@ -20,6 +21,7 @@ require ( github.com/vishvananda/netlink v1.2.1-beta.2 golang.org/x/sys v0.6.0 golang.org/x/text v0.8.0 + golang.org/x/time v0.3.0 google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.30.0 ) @@ -29,7 +31,6 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 9b0ab605b..d2291f079 100644 --- a/go.sum +++ b/go.sum @@ -466,6 +466,10 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/internal/pkg/config/serve.go b/internal/pkg/config/serve.go index 84db10221..41bcde889 100644 --- a/internal/pkg/config/serve.go +++ b/internal/pkg/config/serve.go @@ -1,6 +1,7 @@ package config import ( + "github.com/fsnotify/fsnotify" "github.com/spf13/viper" "github.com/osrg/gobgp/v3/pkg/log" @@ -42,6 +43,18 @@ func ReadConfigfile(path, format string) (*BgpConfigSet, error) { return config, nil } +func WatchConfigFile(path, format string, callBack func()) { + v := viper.New() + v.SetConfigFile(path) + v.SetConfigType(format) + + v.OnConfigChange(func(e fsnotify.Event) { + callBack() + }) + + v.WatchConfig() +} + func ConfigSetToRoutingPolicy(c *BgpConfigSet) *RoutingPolicy { return &RoutingPolicy{ DefinedSets: c.DefinedSets, diff --git a/pkg/config/config.go b/pkg/config/config.go index 454473401..273b8eafa 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -20,6 +20,12 @@ func ReadConfigFile(configFile, configType string) (*config.BgpConfigSet, error) return config.ReadConfigfile(configFile, configType) } +// WatchConfigFile calls the callback function anytime an update to the +// config file is detected. +func WatchConfigFile(configFile, configType string, callBack func()) { + config.WatchConfigFile(configFile, configType, callBack) +} + func marshalRouteTargets(l []string) ([]*apb.Any, error) { rtList := make([]*apb.Any, 0, len(l)) for _, rtString := range l {