2022-10-20 19:59:46 +00:00
package main
2021-04-20 21:31:37 +00:00
import (
"bytes"
"context"
"errors"
"fmt"
2022-10-26 22:23:39 +00:00
"io/fs"
2021-04-20 21:31:37 +00:00
"os"
"sync"
"time"
2023-08-05 21:53:17 +00:00
"isle/bootstrap"
"isle/daemon"
2021-04-20 21:31:37 +00:00
2023-07-06 15:51:38 +00:00
"code.betamike.com/micropelago/pmux/pmuxlib"
2022-11-13 15:45:42 +00:00
"github.com/mediocregopher/mediocre-go-lib/v2/mctx"
"github.com/mediocregopher/mediocre-go-lib/v2/mlog"
2021-04-20 21:31:37 +00:00
)
2023-08-05 21:53:17 +00:00
// The daemon sub-command deals with starting an actual isle daemon
// process, which is required to be running for most other Isle
2021-04-20 21:31:37 +00:00
// functionality. The sub-command does the following:
//
// * Creates and locks the runtime directory.
//
// * Creates the data directory and copies the appdir bootstrap file into there,
// if it's not already there.
//
2022-10-26 21:21:31 +00:00
// * Merges daemon configuration into the bootstrap configuration, and rewrites
// the bootstrap file.
2022-10-16 13:38:15 +00:00
//
2021-04-20 21:31:37 +00:00
// * Sets up environment variables that all other sub-processes then use, based
// on the runtime dir.
//
// * Dynamically creates the root pmux config and runs pmux.
//
// * (On exit) cleans up the runtime directory.
// creates a new bootstrap file using available information from the network. If
// the new bootstrap file is different than the existing one, the existing one
2022-10-26 22:23:39 +00:00
// is overwritten and true is returned.
func reloadBootstrap (
2022-10-26 22:37:03 +00:00
ctx context . Context ,
2022-11-13 15:45:42 +00:00
logger * mlog . Logger ,
2022-10-26 22:23:39 +00:00
hostBootstrap bootstrap . Bootstrap ,
) (
bootstrap . Bootstrap , bool , error ,
) {
2021-04-20 21:31:37 +00:00
2022-10-29 19:11:40 +00:00
thisHost := hostBootstrap . ThisHost ( )
2022-11-13 15:45:42 +00:00
newHosts , err := hostBootstrap . GetGarageBootstrapHosts ( ctx , logger )
2022-10-15 16:41:07 +00:00
if err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , false , fmt . Errorf ( "getting hosts from garage: %w" , err )
2022-10-15 16:41:07 +00:00
}
2021-04-20 21:31:37 +00:00
2022-10-29 19:11:40 +00:00
// the daemon's view of this host's bootstrap info takes precedence over
// whatever is in garage
newHosts [ thisHost . Name ] = thisHost
2022-10-15 16:41:07 +00:00
newHostsHash , err := bootstrap . HostsHash ( newHosts )
if err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , false , fmt . Errorf ( "calculating hash of new hosts: %w" , err )
2021-04-20 21:31:37 +00:00
}
2022-10-26 22:23:39 +00:00
currHostsHash , err := bootstrap . HostsHash ( hostBootstrap . Hosts )
2021-04-20 21:31:37 +00:00
if err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , false , fmt . Errorf ( "calculating hash of current hosts: %w" , err )
2021-04-20 21:31:37 +00:00
}
2022-10-15 16:41:07 +00:00
if bytes . Equal ( newHostsHash , currHostsHash ) {
2022-10-26 22:23:39 +00:00
return hostBootstrap , false , nil
2021-04-20 21:31:37 +00:00
}
2022-10-29 19:11:40 +00:00
hostBootstrap . Hosts = newHosts
2022-11-05 15:21:49 +00:00
if err := writeBootstrapToDataDir ( hostBootstrap ) ; err != nil {
return bootstrap . Bootstrap { } , false , fmt . Errorf ( "writing new bootstrap to data dir: %w" , err )
}
2022-10-29 19:11:40 +00:00
return hostBootstrap , true , nil
2021-04-20 21:31:37 +00:00
}
2022-10-20 19:59:46 +00:00
// runs a single pmux process of daemon, returning only once the env.Context has
// been canceled or bootstrap info has been changed. This will always block
2022-10-26 22:23:39 +00:00
// until the spawned pmux has returned, and returns a copy of hostBootstrap with
// updated boostrap info.
2022-10-26 21:21:31 +00:00
func runDaemonPmuxOnce (
2022-10-26 22:37:03 +00:00
ctx context . Context ,
2022-11-13 15:45:42 +00:00
logger * mlog . Logger ,
2022-10-26 22:23:39 +00:00
hostBootstrap bootstrap . Bootstrap ,
daemonConfig daemon . Config ,
2022-10-26 21:21:31 +00:00
) (
2022-10-26 22:23:39 +00:00
bootstrap . Bootstrap , error ,
2022-10-26 21:21:31 +00:00
) {
2021-04-20 21:31:37 +00:00
2022-10-26 22:37:03 +00:00
nebulaPmuxProcConfig , err := nebulaPmuxProcConfig ( hostBootstrap , daemonConfig )
2022-10-20 19:59:46 +00:00
if err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , fmt . Errorf ( "generating nebula config: %w" , err )
2022-10-20 19:59:46 +00:00
}
2022-10-26 22:37:03 +00:00
dnsmasqPmuxProcConfig , err := dnsmasqPmuxProcConfig ( hostBootstrap , daemonConfig )
2022-10-26 20:18:16 +00:00
if err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , fmt . Errorf ( "generating dnsmasq config: %w" , err )
2022-10-26 20:18:16 +00:00
}
2022-10-26 22:37:03 +00:00
garagePmuxProcConfigs , err := garagePmuxProcConfigs ( hostBootstrap , daemonConfig )
2022-10-26 21:21:31 +00:00
if err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , fmt . Errorf ( "generating garage children configs: %w" , err )
2021-04-20 21:31:37 +00:00
}
2022-10-26 21:21:31 +00:00
pmuxConfig := pmuxlib . Config {
Processes : append (
[ ] pmuxlib . ProcessConfig {
nebulaPmuxProcConfig ,
dnsmasqPmuxProcConfig ,
} ,
garagePmuxProcConfigs ... ,
) ,
2022-10-15 16:41:07 +00:00
}
2021-04-20 21:31:37 +00:00
var wg sync . WaitGroup
defer wg . Wait ( )
2022-10-26 22:37:03 +00:00
ctx , cancel := context . WithCancel ( ctx )
2021-04-20 21:31:37 +00:00
defer cancel ( )
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
2022-10-25 19:15:09 +00:00
pmuxlib . Run ( ctx , os . Stdout , os . Stderr , pmuxConfig )
2021-04-20 21:31:37 +00:00
} ( )
2022-11-13 15:45:42 +00:00
if err := waitForGarageAndNebula ( ctx , logger , hostBootstrap , daemonConfig ) ; err != nil {
2022-11-13 13:55:25 +00:00
return bootstrap . Bootstrap { } , fmt . Errorf ( "waiting for nebula/garage to start up: %w" , err )
}
2022-10-19 14:53:31 +00:00
2022-11-13 13:55:25 +00:00
if len ( daemonConfig . Storage . Allocations ) > 0 {
2022-10-19 14:53:31 +00:00
err := doOnce ( ctx , func ( ctx context . Context ) error {
2022-11-13 15:45:42 +00:00
if err := garageApplyLayout ( ctx , logger , hostBootstrap , daemonConfig ) ; err != nil {
logger . Error ( ctx , "applying garage layout" , err )
2022-11-05 12:57:21 +00:00
return err
}
return nil
2022-10-19 14:53:31 +00:00
} )
if err != nil {
2022-11-13 13:55:25 +00:00
return bootstrap . Bootstrap { } , fmt . Errorf ( "applying garage layout: %w" , err )
2022-10-19 14:53:31 +00:00
}
2022-10-19 14:20:26 +00:00
}
2022-11-15 19:11:47 +00:00
err = doOnce ( ctx , func ( ctx context . Context ) error {
if err := hostBootstrap . PutGarageBoostrapHost ( ctx ) ; err != nil {
logger . Error ( ctx , "updating host info in garage" , err )
return err
}
return nil
} )
if err != nil {
return bootstrap . Bootstrap { } , fmt . Errorf ( "updating host info in garage: %w" , err )
}
2021-04-20 21:31:37 +00:00
ticker := time . NewTicker ( 3 * time . Minute )
defer ticker . Stop ( )
for {
select {
2022-11-13 13:55:25 +00:00
case <- ctx . Done ( ) :
2022-10-26 22:37:03 +00:00
return bootstrap . Bootstrap { } , ctx . Err ( )
2021-04-20 21:31:37 +00:00
case <- ticker . C :
fmt . Fprintln ( os . Stderr , "checking for changes to bootstrap" )
2022-10-19 14:20:26 +00:00
var (
changed bool
err error
)
2022-11-13 15:45:42 +00:00
if hostBootstrap , changed , err = reloadBootstrap ( ctx , logger , hostBootstrap ) ; err != nil {
2022-10-26 22:23:39 +00:00
return bootstrap . Bootstrap { } , fmt . Errorf ( "reloading bootstrap: %w" , err )
2021-04-20 21:31:37 +00:00
} else if changed {
fmt . Fprintln ( os . Stderr , "bootstrap info has changed, restarting all processes" )
2022-10-26 22:23:39 +00:00
return hostBootstrap , nil
2021-04-20 21:31:37 +00:00
}
}
}
}
var subCmdDaemon = subCmd {
name : "daemon" ,
2023-08-05 21:53:17 +00:00
descr : "Runs the isle daemon (Default if no sub-command given)" ,
2021-04-20 21:31:37 +00:00
do : func ( subCmdCtx subCmdCtx ) error {
flags := subCmdCtx . flagSet ( false )
2022-10-26 21:21:31 +00:00
daemonConfigPath := flags . StringP (
2021-04-20 21:31:37 +00:00
"config-path" , "c" , "" ,
"Optional path to a daemon.yml file to load configuration from." ,
)
dumpConfig := flags . Bool (
"dump-config" , false ,
"Write the default configuration file to stdout and exit." ,
)
bootstrapPath := flags . StringP (
"bootstrap-path" , "b" , "" ,
2023-08-05 21:53:17 +00:00
` Path to a bootstrap.yml file. This only needs to be provided the first time the daemon is started, after that it is ignored. If the isle binary has a bootstrap built into it then this argument is always optional. ` ,
2021-04-20 21:31:37 +00:00
)
2022-11-13 15:45:42 +00:00
logLevelStr := flags . StringP (
"log-level" , "l" , "info" ,
` Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes ` ,
)
2021-04-20 21:31:37 +00:00
if err := flags . Parse ( subCmdCtx . args ) ; err != nil {
return fmt . Errorf ( "parsing flags: %w" , err )
}
2022-11-13 15:45:42 +00:00
ctx := subCmdCtx . ctx
2021-04-20 21:31:37 +00:00
if * dumpConfig {
2022-10-26 22:37:03 +00:00
return daemon . CopyDefaultConfig ( os . Stdout , envAppDirPath )
2021-04-20 21:31:37 +00:00
}
2022-11-13 15:45:42 +00:00
logLevel := mlog . LevelFromString ( * logLevelStr )
if logLevel == nil {
return fmt . Errorf ( "couldn't parse log level %q" , * logLevelStr )
}
logger := subCmdCtx . logger . WithMaxLevel ( logLevel . Int ( ) )
runtimeDirCleanup , err := setupAndLockRuntimeDir ( ctx , logger )
2022-10-26 22:45:40 +00:00
if err != nil {
return fmt . Errorf ( "setting up runtime directory: %w" , err )
2021-04-20 21:31:37 +00:00
}
2022-10-26 22:45:40 +00:00
defer runtimeDirCleanup ( )
2021-04-20 21:31:37 +00:00
2022-10-26 22:23:39 +00:00
var (
2022-10-26 22:37:03 +00:00
bootstrapDataDirPath = bootstrap . DataDirPath ( envDataDirPath )
bootstrapAppDirPath = bootstrap . AppDirPath ( envAppDirPath )
2021-04-20 21:31:37 +00:00
2022-11-05 12:57:21 +00:00
hostBootstrapPath string
hostBootstrap bootstrap . Bootstrap
2022-10-26 22:23:39 +00:00
)
2021-04-20 21:31:37 +00:00
2022-10-26 22:23:39 +00:00
tryLoadBootstrap := func ( path string ) bool {
2021-04-20 21:31:37 +00:00
if err != nil {
2022-10-26 22:23:39 +00:00
return false
} else if hostBootstrap , err = bootstrap . FromFile ( path ) ; errors . Is ( err , fs . ErrNotExist ) {
2022-11-05 12:57:21 +00:00
fmt . Fprintf ( os . Stderr , "bootstrap file not found at %q\n" , path )
2022-10-26 22:23:39 +00:00
err = nil
return false
} else if err != nil {
2022-11-02 13:34:40 +00:00
err = fmt . Errorf ( "parsing bootstrap.yml at %q: %w" , path , err )
2022-10-26 22:23:39 +00:00
return false
2021-04-20 21:31:37 +00:00
}
2022-11-13 15:45:42 +00:00
logger . Info (
2022-11-16 16:27:42 +00:00
mctx . Annotate ( ctx , "bootstrapFilePath" , path ) ,
2022-11-13 15:45:42 +00:00
"bootstrap file found" ,
)
2022-10-26 22:23:39 +00:00
hostBootstrapPath = path
return true
}
2022-11-05 12:57:21 +00:00
switch {
case tryLoadBootstrap ( bootstrapDataDirPath ) :
case * bootstrapPath != "" && tryLoadBootstrap ( * bootstrapPath ) :
case tryLoadBootstrap ( bootstrapAppDirPath ) :
case err != nil :
2022-11-02 13:34:40 +00:00
return fmt . Errorf ( "attempting to load bootstrap.yml file: %w" , err )
2022-11-05 12:57:21 +00:00
default :
2022-11-02 13:34:40 +00:00
return errors . New ( "No bootstrap.yml file could be found, and one is not provided with --bootstrap-path" )
2022-11-05 12:57:21 +00:00
}
2022-10-26 22:23:39 +00:00
2022-11-05 12:57:21 +00:00
if hostBootstrapPath != bootstrapDataDirPath {
2022-10-26 22:23:39 +00:00
// If the bootstrap file is not being stored in the data dir, copy
// it there, so it can be loaded from there next time.
2022-10-26 22:37:03 +00:00
if err := writeBootstrapToDataDir ( hostBootstrap ) ; err != nil {
2022-11-02 13:34:40 +00:00
return fmt . Errorf ( "writing bootstrap.yml to data dir: %w" , err )
2021-04-20 21:31:37 +00:00
}
}
2022-10-26 22:37:03 +00:00
daemonConfig , err := daemon . LoadConfig ( envAppDirPath , * daemonConfigPath )
2022-10-26 21:21:31 +00:00
if err != nil {
return fmt . Errorf ( "loading daemon config: %w" , err )
2021-04-20 21:31:37 +00:00
}
2022-10-16 18:33:31 +00:00
// we update this Host's data using whatever configuration has been
2022-10-26 21:21:31 +00:00
// provided by the daemon config. This way the daemon has the most
// up-to-date possible bootstrap. This updated bootstrap will later get
// updated in garage using bootstrap.PutGarageBoostrapHost, so other
// hosts will see it as well.
2023-01-17 19:31:22 +00:00
if hostBootstrap , daemonConfig , err = coalesceDaemonConfigAndBootstrap ( hostBootstrap , daemonConfig ) ; err != nil {
2022-10-26 21:21:31 +00:00
return fmt . Errorf ( "merging daemon config into bootstrap data: %w" , err )
2022-10-16 13:38:15 +00:00
}
2021-04-20 21:31:37 +00:00
for {
2022-11-13 15:45:42 +00:00
hostBootstrap , err = runDaemonPmuxOnce ( ctx , logger , hostBootstrap , daemonConfig )
2022-10-26 21:21:31 +00:00
if errors . Is ( err , context . Canceled ) {
2021-04-20 21:31:37 +00:00
return nil
} else if err != nil {
return fmt . Errorf ( "running pmux for daemon: %w" , err )
}
}
} ,
}