2024-06-17 18:51:02 +00:00
|
|
|
// Package daemon implements the isle daemon, which is a long-running service
|
|
|
|
// managing all isle background tasks and sub-processes for a single cluster.
|
2022-10-26 21:21:31 +00:00
|
|
|
package daemon
|
|
|
|
|
|
|
|
import (
|
2024-06-17 18:51:02 +00:00
|
|
|
"context"
|
2022-10-26 21:21:31 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-06-17 18:51:02 +00:00
|
|
|
"isle/bootstrap"
|
2022-10-26 21:21:31 +00:00
|
|
|
"os"
|
2024-06-17 18:51:02 +00:00
|
|
|
"time"
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
"code.betamike.com/micropelago/pmux/pmuxlib"
|
|
|
|
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
|
2022-10-26 21:21:31 +00:00
|
|
|
)
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
type daemon struct {
|
|
|
|
logger *mlog.Logger
|
|
|
|
config Config
|
|
|
|
hostBootstrap bootstrap.Bootstrap
|
|
|
|
opts Opts
|
|
|
|
|
|
|
|
pmuxCancelFn context.CancelFunc
|
|
|
|
pmuxStoppedCh chan struct{}
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
// Daemon presents all functionality required for client frontends to interact
|
|
|
|
// with isle, typically via the unix socket.
|
|
|
|
type Daemon interface {
|
|
|
|
|
|
|
|
// Shutdown blocks until all resources held or created by the daemon,
|
|
|
|
// including child processes it has started, have been cleaned up, or until
|
|
|
|
// the context is canceled.
|
|
|
|
//
|
|
|
|
// If this returns an error then it's possible that child processes are
|
|
|
|
// still running and are no longer managed.
|
|
|
|
Shutdown(context.Context) error
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
// Opts are optional parameters which can be passed in when initializing a new
|
|
|
|
// Daemon instance. A nil Opts is equivalent to a zero value.
|
|
|
|
type Opts struct {
|
|
|
|
// SkipHostBootstrapPush, if set, will cause the Daemon to not push the
|
|
|
|
// bootstrap to garage upon a successful initialization.
|
|
|
|
SkipHostBootstrapPush bool
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
// 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)
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
if o.Stdout == nil {
|
|
|
|
o.Stdout = os.Stdout
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
if o.Stderr == nil {
|
|
|
|
o.Stderr = os.Stderr
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
return o
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
// New initialized and returns a Daemon. If initialization fails an error is
|
|
|
|
// returned.
|
|
|
|
func New(
|
|
|
|
ctx context.Context,
|
|
|
|
logger *mlog.Logger,
|
|
|
|
config Config,
|
|
|
|
hostBootstrap bootstrap.Bootstrap,
|
|
|
|
runtimeDirPath, binDirPath string,
|
|
|
|
opts *Opts,
|
2022-10-26 21:21:31 +00:00
|
|
|
) (
|
2024-06-17 18:51:02 +00:00
|
|
|
Daemon, error,
|
2022-10-26 21:21:31 +00:00
|
|
|
) {
|
2024-06-17 18:51:02 +00:00
|
|
|
opts = opts.withDefaults()
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(
|
|
|
|
runtimeDirPath, binDirPath, hostBootstrap, config,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("generating nebula config: %w", err)
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(
|
|
|
|
runtimeDirPath, binDirPath, hostBootstrap, config,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("generating dnsmasq config: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
garagePmuxProcConfigs, err := garagePmuxProcConfigs(
|
|
|
|
runtimeDirPath, binDirPath, hostBootstrap, config,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("generating garage children configs: %w", err)
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
pmuxConfig := pmuxlib.Config{
|
|
|
|
Processes: append(
|
|
|
|
[]pmuxlib.ProcessConfig{
|
|
|
|
nebulaPmuxProcConfig,
|
|
|
|
dnsmasqPmuxProcConfig,
|
|
|
|
},
|
|
|
|
garagePmuxProcConfigs...,
|
|
|
|
),
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
pmuxCtx, pmuxCancelFn := context.WithCancel(context.Background())
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
d := &daemon{
|
|
|
|
logger: logger,
|
|
|
|
config: config,
|
|
|
|
hostBootstrap: hostBootstrap,
|
|
|
|
opts: *opts,
|
|
|
|
pmuxCancelFn: pmuxCancelFn,
|
|
|
|
pmuxStoppedCh: make(chan struct{}),
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
go func() {
|
|
|
|
defer close(d.pmuxStoppedCh)
|
|
|
|
pmuxlib.Run(pmuxCtx, d.opts.Stdout, d.opts.Stderr, pmuxConfig)
|
|
|
|
}()
|
|
|
|
|
|
|
|
if initErr := d.postPmuxInit(ctx); initErr != nil {
|
|
|
|
logger.Warn(ctx, "failed to initialize daemon, shutting down child processes", err)
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
|
|
|
|
defer cancel()
|
|
|
|
if err := d.Shutdown(shutdownCtx); err != nil {
|
|
|
|
panic(fmt.Sprintf(
|
|
|
|
"failed to shut down child processes after initialization"+
|
|
|
|
" error, there may be zombie children leftover."+
|
|
|
|
" Original error: %v",
|
|
|
|
initErr,
|
|
|
|
))
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
2024-06-17 18:51:02 +00:00
|
|
|
|
|
|
|
return nil, initErr
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
return d, nil
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
func (d *daemon) postPmuxInit(ctx context.Context) error {
|
|
|
|
d.logger.Info(ctx, "waiting for nebula VPN to come online")
|
|
|
|
if err := waitForNebula(ctx, d.hostBootstrap); err != nil {
|
|
|
|
return fmt.Errorf("waiting for nebula to start: %w", err)
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
d.logger.Info(ctx, "waiting for garage instances to come online")
|
|
|
|
if err := d.waitForGarage(ctx); err != nil {
|
|
|
|
return fmt.Errorf("waiting for garage to start: %w", err)
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
if len(d.config.Storage.Allocations) > 0 {
|
|
|
|
|
|
|
|
err := until(ctx, func(ctx context.Context) error {
|
|
|
|
err := garageApplyLayout(ctx, d.logger, d.hostBootstrap, d.config)
|
|
|
|
if err != nil {
|
|
|
|
d.logger.Error(ctx, "applying garage layout", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("applying garage layout: %w", err)
|
|
|
|
}
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
|
2024-06-17 18:51:02 +00:00
|
|
|
if !d.opts.SkipHostBootstrapPush {
|
|
|
|
if err := until(ctx, func(ctx context.Context) error {
|
|
|
|
if err := d.hostBootstrap.PutGarageBoostrapHost(ctx); err != nil {
|
|
|
|
d.logger.Error(ctx, "updating host info in garage", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return fmt.Errorf("updating host info in garage: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *daemon) Shutdown(ctx context.Context) error {
|
|
|
|
d.pmuxCancelFn()
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
case <-d.pmuxStoppedCh:
|
|
|
|
return nil
|
|
|
|
}
|
2022-10-26 21:21:31 +00:00
|
|
|
}
|