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)
This commit is contained in:
Ben Roberts
2026-06-17 16:52:03 +01:00
parent d54573e469
commit fe399e2f03
4 changed files with 544 additions and 1 deletions

11
main.go
View File

@@ -33,7 +33,16 @@ func main() {
go func() {
for {
sig := <-signalChan
if sig == os.Interrupt || sig == syscall.SIGTERM {
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