Skip to content

Commit

Permalink
Implement config reloading
Browse files Browse the repository at this point in the history
The approach here is to pass a channel to InnerMain functions that can be watched for incoming `syscall.SIGHUP` signals.

To trigger a config reload, run
- `service fleetspeak-client reload` on linux
- `launchctl kill SIGHUP system/com.google.code.fleetspeak` on mac
- `sc.exe control FleetspeakService paramchange` on windows

The default implementation provided simply stops the client and creates another one when a signal is received.

PiperOrigin-RevId: 667959949
  • Loading branch information
torsm authored and copybara-github committed Sep 5, 2024
1 parent a51d5c7 commit cc86b9c
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 21 deletions.
45 changes: 30 additions & 15 deletions cmd/fleetspeak_client/fleetspeak_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"os"

log "github.com/golang/glog"
"google.golang.org/protobuf/encoding/prototext"

"github.com/google/fleetspeak/fleetspeak/src/client"
Expand All @@ -24,18 +25,39 @@ import (

var configFile = flag.String("config", "", "Client configuration file, required.")

func innerMain(ctx context.Context) error {
func innerMain(ctx context.Context, cfgReloadSignals <-chan os.Signal) error {
for {
cl, err := createClient()
if err != nil {
return fmt.Errorf("error starting client: %v", err)
}

select {
case <-cfgReloadSignals:
// We implement config reloading by tearing down the client and creating a
// new one.
log.Info("Config reload requested")
cl.Stop()
continue
case <-ctx.Done():
cl.Stop()
return nil
}
}
}

func createClient() (*client.Client, error) {
b, err := os.ReadFile(*configFile)
if err != nil {
return fmt.Errorf("unable to read configuration file %q: %v", *configFile, err)
return nil, fmt.Errorf("unable to read configuration file %q: %v", *configFile, err)
}
cfgPB := &gpb.Config{}
if err := prototext.Unmarshal(b, cfgPB); err != nil {
return fmt.Errorf("unable to parse configuration file %q: %v", *configFile, err)
return nil, fmt.Errorf("unable to parse configuration file %q: %v", *configFile, err)
}
cfg, err := generic.MakeConfiguration(cfgPB)
if err != nil {
return fmt.Errorf("error in configuration file: %v", err)
return nil, fmt.Errorf("error in configuration file: %v", err)
}

var com comms.Communicator
Expand All @@ -45,7 +67,8 @@ func innerMain(ctx context.Context) error {
com = &https.Communicator{}
}

cl, err := client.New(cfg,
return client.New(
cfg,
client.Components{
ServiceFactories: map[string]service.Factory{
"Daemon": daemonservice.Factory,
Expand All @@ -55,16 +78,8 @@ func innerMain(ctx context.Context) error {
},
Communicator: com,
Stats: stats.NoopCollector{},
})
if err != nil {
return fmt.Errorf("error starting client: %v", err)
}

select {
case <-ctx.Done():
cl.Stop()
}
return nil
},
)
}

func main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Documentation=https://github.com/google/fleetspeak
[Service]
User=root
ExecStart=/usr/bin/fleetspeak-client --config /etc/fleetspeak-client/client.config
ExecReload=kill -s HUP $MAINPID
Restart=always
KillMode=process

Expand Down
8 changes: 6 additions & 2 deletions fleetspeak/src/client/entry/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package entry

import (
"context"
"os"
"time"
)

Expand All @@ -16,5 +17,8 @@ const shutdownTimeout = 10 * time.Second
// InnerMain is an inner entry function responsible for creating a
// [client.Client] and managing its configuration and lifecycle. It is called by
// [RunMain] which handles platform-specific mechanics to manage the passed
// Context.
type InnerMain func(ctx context.Context) error
// [Context].
// The [cfgReloadSignals] channel gets a [syscall.SIGHUP] when a config reload
// is requested. We use UNIX conventions here, the Windows layer can send a
// [syscall.SIGHUP] when appropriate.
type InnerMain func(ctx context.Context, cfgReloadSignals <-chan os.Signal) error
7 changes: 6 additions & 1 deletion fleetspeak/src/client/entry/entry_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ func RunMain(innerMain InnerMain, _ /* windowsServiceName */ string) {
}, syscall.SIGUSR1)
defer cancelSignal()

err := innerMain(ctx)
sighupCh := make(chan os.Signal, 1)
defer close(sighupCh)
signal.Notify(sighupCh, syscall.SIGHUP)
defer signal.Stop(sighupCh)

err := innerMain(ctx, sighupCh)
if err != nil {
log.Exitf("Stopped due to unrecoverable error: %v", err)
}
Expand Down
14 changes: 11 additions & 3 deletions fleetspeak/src/client/entry/entry_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type fleetspeakService struct {
}

func (m *fleetspeakService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (svcSpecificEC bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptParamChange
changes <- svc.Status{State: svc.StartPending}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
tryDisableStderr()
Expand All @@ -32,6 +32,9 @@ func (m *fleetspeakService) Execute(args []string, r <-chan svc.ChangeRequest, c

enforceShutdownTimeout(ctx)

sighupCh := make(chan os.Signal, 1)
defer close(sighupCh)

wg := sync.WaitGroup{}
wg.Add(1)
go func() {
Expand All @@ -51,14 +54,19 @@ func (m *fleetspeakService) Execute(args []string, r <-chan svc.ChangeRequest, c
case svc.Stop, svc.Shutdown:
cancel()
return
case svc.ParamChange:
select {
case sighupCh <- syscall.SIGHUP:
default:
}
default:
log.Warningf("Unsupported control request: %v", c.Cmd)
}
}
}
}()

err := m.innerMain(ctx)
err := m.innerMain(ctx, sighupCh)
cancel()
wg.Wait()
// Returning from this function tells Windows we're shutting down. Even if we
Expand All @@ -78,7 +86,7 @@ func (m *fleetspeakService) ExecuteAsRegularProcess() {

enforceShutdownTimeout(ctx)

err := m.innerMain(ctx)
err := m.innerMain(ctx, nil)
if err != nil {
log.Exitf("Stopped due to unrecoverable error: %v", err)
}
Expand Down

0 comments on commit cc86b9c

Please sign in to comment.