Files
gocast/main.go
Ben Roberts fe399e2f03 Support for reloading config file on HUP signal
Implemented dynamic configuration reloading for gocast via SIGHUP signal handling. This allows updating BGP configuration, applications, and agent settings without service restart.

**Location:** `main.go:29-46`

Enhanced the existing signal handler to process SIGHUP:
```go
switch sig {
case syscall.SIGHUP:
    log.Info("Received SIGHUP, reloading configuration")
    if err := mon.Reload(*config); err != nil {
        log.Errorf("Failed to reload configuration: %v", err)
    } else {
        log.Info("Configuration reloaded successfully")
    }
case os.Interrupt, syscall.SIGTERM:
    log.Info("Received shutdown signal, cleaning up")
    mon.CloseAll()
    cancel()
    return
}
```

**Key Features:**
- Non-blocking: Uses goroutine to handle signals
- Graceful: SIGINT/SIGTERM still trigger clean shutdown
- Logged: All reload attempts are logged with success/failure status

**Location:** `controller/monitor.go:343-420`

Main reload orchestration method:

```go
func (m *MonitorMgr) Reload(configPath string) error
```

**Process Flow:**
1. Read new configuration from file
2. Compare with current configuration
3. If BGP config changed:
   - Withdraw all announced routes
   - Shutdown old BGP controller
   - Start new BGP controller
   - Re-announce routes for healthy apps
4. If Consul config changed:
   - Initialize new Consul monitor
5. Update agent settings
6. Reload applications (add/remove/update)

**Thread Safety:**
- Uses existing `monMu` mutex for monitor map access
- Atomic BGP controller replacement
- No race conditions during reload

**Location:** `controller/monitor.go:422-475`

```go
func (m *MonitorMgr) bgpConfigChanged(old, new c.BgpConfig) bool
```

Comprehensive comparison of:
- Local AS, Peer AS, Peer IPs
- BGP origin
- Multi-hop settings (including nil checks)
- MD5 passwords and environment variables
- Per-peer communities
- Global communities

**Important:** Deep comparison ensures even minor changes are detected.

**Location:** `controller/monitor.go:477-532`

```go
func (m *MonitorMgr) reloadApps(oldApps, newApps []c.AppConfig)
```

Intelligent app management:
- **Remove:** Apps no longer in config (source="config" only)
- **Update:** Apps with changed configuration (VIP, monitors, NAT, communities)
- **Add:** New apps in configuration

**Key Behavior:**
- Consul-discovered apps are NOT removed during reload
- Only config-defined apps are managed
- Config changes trigger remove + re-add

1. **TestBgpConfigChanged**
   - Tests all BGP configuration change scenarios
   - Validates detection of AS, peer, MD5, community changes
   - Includes nil multi-hop pointer checks

2. **TestEqualStringSlices**
   - Tests slice comparison helper
   - Validates empty, identical, and different slices

3. **TestReload** (Integration, requires root)
   - Full reload cycle with BGP AS change
   - App removal verification
   - BGP controller replacement validation

4. **TestReloadAddApp** (Integration)
   - Tests adding new app via reload
   - Validates app registration

5. **TestReloadMD5Change** (Integration)
   - Tests MD5 password change detection
   - Validates BGP controller restart

**Decision:** Reload BGP configuration requires full controller restart.

**Rationale:**
- GoBGP library doesn't support modifying peers dynamically
- Simplifies implementation
- Ensures clean state
- Brief interruption is acceptable for infrequent config changes

**Alternative Considered:** Per-peer updates
- Complex to implement correctly
- Partial state issues
- Not supported well by GoBGP library

**Decision:** Log errors but don't crash; maintain old state on failure.

**Rationale:**
- Availability over correctness for config errors
- Admin can fix config and retry
- Better than service downtime
- Logs provide clear error messages

1. **BGP Interruption**
   - Full BGP restart causes brief routing interruption
   - All routes withdrawn and re-announced
   - May impact traffic during reload

2. **No Atomic BGP Updates**
   - Cannot add/remove single peer without full restart
   - All peers affected even if one changes

3. **No Config Validation**
   - Invalid config is detected during reload
   - No pre-validation before applying
   - Syntax errors require manual fix and retry

4. **No Rollback**
   - Failed reload leaves service in potentially inconsistent state
   - Manual intervention required to restore
   - No automatic rollback to previous config

These changes were written using AI LLM

Authored-By: Claude Code (Sonnet 4.5)
2026-06-17 16:52:03 +01:00

54 lines
1.2 KiB
Go

package main
import (
"context"
"flag"
"github.com/golang/glog"
c "github.com/mayuresh82/gocast/config"
"github.com/mayuresh82/gocast/controller"
"github.com/mayuresh82/gocast/server"
log "github.com/sirupsen/logrus"
"os"
"os/signal"
"syscall"
)
var (
config = flag.String("config", "", "Path to config file")
)
func main() {
flag.Parse()
if glog.V(4) {
log.SetLevel(log.DebugLevel)
}
conf := c.GetConfig(*config)
mon := controller.NewMonitor(conf)
srv := server.NewServer(conf.Agent.ListenAddr, mon)
ctx, cancel := context.WithCancel(context.Background())
// catch interrupt
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
go func() {
for {
sig := <-signalChan
switch sig {
case syscall.SIGHUP:
log.Info("Received SIGHUP, reloading configuration")
if err := mon.Reload(*config); err != nil {
log.Errorf("Failed to reload configuration: %v", err)
} else {
log.Info("Configuration reloaded successfully")
}
case os.Interrupt, syscall.SIGTERM:
log.Info("Received shutdown signal, cleaning up")
mon.CloseAll()
cancel()
return
}
}
}()
srv.Serve(ctx)
}