isle/go/daemon/children/children.go
Brian Picciano 8c3e6a2845 Separate Daemon and Network logic into separate packages
In a world where the daemon can manage more than one network, the Daemon
is really responsible only for knowing which networks are currently
joined, creating/joining/leaving networks, and routing incoming RPC
requests to the correct network handler as needed.

The new network package, with its Network interface, inherits most of
the logic that Daemon used to have, leaving Daemon only the parts needed
for the functionality just described. There's a lot of cleanup done here
in order to really nail down the separation of concerns between the two,
especially around directory creation.
2024-09-09 16:34:00 +02:00

176 lines
4.1 KiB
Go

// Package children manages the creation, lifetime, and shutdown of child
// processes created by the daemon.
package children
import (
"context"
"errors"
"fmt"
"io"
"os"
"code.betamike.com/micropelago/pmux/pmuxlib"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/secrets"
"isle/toolkit"
)
// Opts are optional parameters which can be passed in when initializing a new
// Children instance. A nil Opts is equivalent to a zero value.
type Opts struct {
// Stdout and Stderr are what the associated outputs from child processes
// will be directed to.
Stdout, Stderr io.Writer
}
func (o *Opts) withDefaults() *Opts {
if o == nil {
o = new(Opts)
}
if o.Stdout == nil {
o.Stdout = os.Stdout
}
if o.Stderr == nil {
o.Stderr = os.Stderr
}
return o
}
// Children manages all child processes of a network. Child processes are
// comprised of:
// - nebula
// - dnsmasq
// - garage (0 or more, depending on configured storage allocations)
type Children struct {
logger *mlog.Logger
daemonConfig daecommon.Config
runtimeDir toolkit.Dir
opts Opts
pmux *pmuxlib.Pmux
}
// New initializes and returns a Children instance. If initialization fails an
// error is returned.
func New(
ctx context.Context,
logger *mlog.Logger,
binDirPath string,
secretsStore secrets.Store,
daemonConfig daecommon.Config,
runtimeDir toolkit.Dir,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap,
opts *Opts,
) (
*Children, error,
) {
opts = opts.withDefaults()
logger.Info(ctx, "Loading secrets")
garageRPCSecret, err := daecommon.GetGarageRPCSecret(ctx, secretsStore)
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
return nil, fmt.Errorf("loading garage RPC secret: %w", err)
}
c := &Children{
logger: logger,
daemonConfig: daemonConfig,
runtimeDir: runtimeDir,
opts: *opts,
}
pmuxConfig, err := c.newPmuxConfig(
ctx,
garageRPCSecret,
binDirPath,
daemonConfig,
garageAdminToken,
hostBootstrap,
)
if err != nil {
return nil, fmt.Errorf("generating pmux config: %w", err)
}
c.pmux = pmuxlib.NewPmux(pmuxConfig, c.opts.Stdout, c.opts.Stderr)
initErr := c.postPmuxInit(
ctx, daemonConfig, garageAdminToken, hostBootstrap,
)
if initErr != nil {
logger.Warn(ctx, "failed to initialize Children, shutting down child processes", err)
c.Shutdown()
return nil, initErr
}
return c, nil
}
// RestartDNSMasq rewrites the dnsmasq config and restarts the process.
//
// TODO block until process has been confirmed to have come back up
// successfully.
func (c *Children) RestartDNSMasq(hostBootstrap bootstrap.Bootstrap) error {
_, err := dnsmasqWriteConfig(
c.runtimeDir.Path, c.daemonConfig, hostBootstrap,
)
if err != nil {
return fmt.Errorf("writing new dnsmasq config: %w", err)
}
c.pmux.Restart("dnsmasq")
return nil
}
// RestartNebula rewrites the nebula config and restarts the process.
//
// TODO block until process has been confirmed to have come back up
// successfully.
func (c *Children) RestartNebula(hostBootstrap bootstrap.Bootstrap) error {
_, err := nebulaWriteConfig(
c.runtimeDir.Path, c.daemonConfig, hostBootstrap,
)
if err != nil {
return fmt.Errorf("writing a new nebula config: %w", err)
}
c.pmux.Restart("nebula")
return nil
}
// Reload applies a ReloadDiff to the Children, using the given bootstrap which
// must be the same one which was passed to CalculateReloadDiff.
func (c *Children) Reload(
ctx context.Context, newBootstrap bootstrap.Bootstrap, diff ReloadDiff,
) error {
var errs []error
if diff.DNSChanged {
c.logger.Info(ctx, "Restarting dnsmasq to account for bootstrap changes")
if err := c.RestartDNSMasq(newBootstrap); err != nil {
errs = append(errs, fmt.Errorf("restarting dnsmasq: %w", err))
}
}
if diff.NebulaChanged {
c.logger.Info(ctx, "Restarting nebula to account for bootstrap changes")
if err := c.RestartNebula(newBootstrap); err != nil {
errs = append(errs, fmt.Errorf("restarting nebula: %w", err))
}
}
return errors.Join(errs...)
}
// Shutdown blocks until all child processes have gracefully shut themselves
// down.
func (c *Children) Shutdown() {
c.pmux.Stop()
}