Centralize creation of garage admin client logic into Children

This commit is contained in:
Brian Picciano 2025-01-04 15:50:17 +01:00
parent 9e508ef4e2
commit 4bce0e3fa0
5 changed files with 121 additions and 94 deletions

View File

@ -3,20 +3,30 @@
package children package children
import ( import (
"cmp"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"code.betamike.com/micropelago/pmux/pmuxlib" "code.betamike.com/micropelago/pmux/pmuxlib"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx" "dev.mediocregopher.com/mediocre-go-lib.git/mctx"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog" "dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"golang.org/x/exp/maps"
"isle/bootstrap" "isle/bootstrap"
"isle/daemon/daecommon" "isle/daemon/daecommon"
"isle/garage"
"isle/secrets" "isle/secrets"
"isle/toolkit" "isle/toolkit"
) )
type garageProc struct {
*pmuxlib.Process
alloc daecommon.ConfigStorageAllocation
adminAddr string
}
// Opts are optional fields which can be passed into New. A nil value is // Opts are optional fields which can be passed into New. A nil value is
// equivalent to a zero value. // equivalent to a zero value.
type Opts struct { type Opts struct {
@ -48,7 +58,7 @@ type Children struct {
nebulaProc *pmuxlib.Process nebulaProc *pmuxlib.Process
dnsmasqProc *pmuxlib.Process dnsmasqProc *pmuxlib.Process
garageProcs map[string]*pmuxlib.Process garageProcs map[string]garageProc
} }
// New initializes and returns a Children instance. If initialization fails an // New initializes and returns a Children instance. If initialization fails an
@ -133,9 +143,8 @@ func New(
if err := waitForGarage( if err := waitForGarage(
ctx, ctx,
c.logger, c.logger,
networkConfig,
garageAdminToken, garageAdminToken,
hostBootstrap, c.garageProcs,
opts.GarageNewCluster, opts.GarageNewCluster,
); err != nil { ); err != nil {
logger.Warn(ctx, "Failed waiting for garage processes to initialize, shutting down child processes", err) logger.Warn(ctx, "Failed waiting for garage processes to initialize, shutting down child processes", err)
@ -205,6 +214,8 @@ func (c *Children) reloadGarage(
return nil return nil
} }
thisHost := hostBootstrap.ThisHost()
var anyCreated bool var anyCreated bool
for _, alloc := range allocs { for _, alloc := range allocs {
var ( var (
@ -237,18 +248,21 @@ func (c *Children) reloadGarage(
anyCreated = true anyCreated = true
c.logger.Info(ctx, "Garage config has been added, creating process") c.logger.Info(ctx, "Garage config has been added, creating process")
c.garageProcs[procName] = garagePmuxProc( c.garageProcs[procName] = garageProc{
ctx, c.logger, c.binDirPath, procName, childConfigPath, Process: garagePmuxProc(
) ctx, c.logger, c.binDirPath, procName, childConfigPath,
),
alloc: alloc,
adminAddr: garageAllocAdminAddr(thisHost, alloc),
}
} }
if anyCreated { if anyCreated {
if err := waitForGarage( if err := waitForGarage(
ctx, ctx,
c.logger, c.logger,
networkConfig,
c.garageAdminToken, c.garageAdminToken,
hostBootstrap, c.garageProcs,
false, false,
); err != nil { ); err != nil {
return fmt.Errorf("waiting for garage to start: %w", err) return fmt.Errorf("waiting for garage to start: %w", err)
@ -282,6 +296,46 @@ func (c *Children) Reload(
return errors.Join(errs...) return errors.Join(errs...)
} }
// GarageAdminClient returns an admin client for an active local garage process,
// or false if there are no garage processes.
func (c *Children) GarageAdminClient() (*garage.AdminClient, bool) {
if len(c.garageProcs) == 0 {
return nil, false
}
procsSlice := maps.Values(c.garageProcs)
slices.SortFunc(procsSlice, func(a, b garageProc) int {
return cmp.Compare(a.alloc.RPCPort, b.alloc.RPCPort)
})
return garage.NewAdminClient(
garageAdminClientLogger(c.logger),
procsSlice[0].adminAddr,
c.garageAdminToken,
), true
}
// GarageAdminClientForAlloc returns an admin client for a particular allocation
// which has a currently running garage instance, or false if there the
// allocation has no currently running instance.
func (c *Children) GarageAdminClientForAlloc(
alloc daecommon.ConfigStorageAllocation,
) (
*garage.AdminClient, bool,
) {
procName := garagePmuxProcName(alloc)
proc, ok := c.garageProcs[procName]
if !ok {
return nil, false
}
return garage.NewAdminClient(
garageAdminClientLogger(c.logger),
proc.adminAddr,
c.garageAdminToken,
), true
}
// Shutdown blocks until all child processes have gracefully shut themselves // Shutdown blocks until all child processes have gracefully shut themselves
// down. // down.
func (c *Children) Shutdown() { func (c *Children) Shutdown() {

View File

@ -21,43 +21,44 @@ func garageAdminClientLogger(logger *mlog.Logger) *mlog.Logger {
return logger.WithNamespace("garageAdminClient") return logger.WithNamespace("garageAdminClient")
} }
func garageAllocAdminAddr(
host bootstrap.Host, alloc daecommon.ConfigStorageAllocation,
) string {
return net.JoinHostPort(host.IP().String(), strconv.Itoa(alloc.AdminPort))
}
func waitForGarage( func waitForGarage(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
networkConfig daecommon.NetworkConfig,
adminToken string, adminToken string,
hostBootstrap bootstrap.Bootstrap, procs map[string]garageProc,
newCluster bool, newCluster bool,
) error { ) error {
allocs := networkConfig.Storage.Allocations
if len(allocs) == 0 {
return nil
}
adminClientLogger := garageAdminClientLogger(logger) adminClientLogger := garageAdminClientLogger(logger)
for _, alloc := range allocs { for _, proc := range procs {
adminAddr := net.JoinHostPort(
hostBootstrap.ThisHost().IP().String(),
strconv.Itoa(alloc.AdminPort),
)
adminClient := garage.NewAdminClient( adminClient := garage.NewAdminClient(
adminClientLogger, adminAddr, adminToken, adminClientLogger, proc.adminAddr, adminToken,
) )
ctx := mctx.Annotate( ctx := mctx.Annotate(
ctx, "garageAdminAddr", adminAddr, "garageDataPath", alloc.DataPath, ctx,
"garageAdminAddr", proc.adminAddr,
"garageDataPath", proc.alloc.DataPath,
) )
logger.Info(ctx, "Waiting for garage instance to be healthy") logger.Info(ctx, "Waiting for garage instance to be healthy")
if err := adminClient.Wait(ctx, !newCluster); err != nil { err := adminClient.Wait(ctx, !newCluster)
return fmt.Errorf("waiting for garage instance %q to start up: %w", adminAddr, err)
}
adminClient.Close() adminClient.Close()
if err != nil {
return fmt.Errorf(
"waiting for garage instance %q to start up: %w",
proc.adminAddr,
err,
)
}
} }
return nil return nil
@ -152,11 +153,12 @@ func garagePmuxProcs(
adminToken string, adminToken string,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
) ( ) (
map[string]*pmuxlib.Process, error, map[string]garageProc, error,
) { ) {
var ( var (
pmuxProcs = map[string]*pmuxlib.Process{} procs = map[string]garageProc{}
allocs = networkConfig.Storage.Allocations allocs = networkConfig.Storage.Allocations
thisHost = hostBootstrap.ThisHost()
) )
if len(allocs) > 0 && rpcSecret == "" { if len(allocs) > 0 && rpcSecret == "" {
@ -176,10 +178,14 @@ func garagePmuxProcs(
} }
procName := garagePmuxProcName(alloc) procName := garagePmuxProcName(alloc)
pmuxProcs[procName] = garagePmuxProc( procs[procName] = garageProc{
ctx, logger, binDirPath, procName, childConfigPath, Process: garagePmuxProc(
) ctx, logger, binDirPath, procName, childConfigPath,
),
alloc: alloc,
adminAddr: garageAllocAdminAddr(thisHost, alloc),
}
} }
return pmuxProcs, nil return procs, nil
} }

View File

@ -12,9 +12,7 @@ import (
"isle/nebula" "isle/nebula"
"isle/secrets" "isle/secrets"
"isle/toolkit" "isle/toolkit"
"net"
"path/filepath" "path/filepath"
"strconv"
"time" "time"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx" "dev.mediocregopher.com/mediocre-go-lib.git/mctx"
@ -53,39 +51,14 @@ func getGarageClientParams(
}, nil }, nil
} }
func garageAdminClientLogger(logger *mlog.Logger) *mlog.Logger {
return logger.WithNamespace("garageAdminClient")
}
// newGarageAdminClient will return an AdminClient for a local garage instance,
// or it will _panic_ if there is no local instance configured.
func newGarageAdminClient(
logger *mlog.Logger,
networkConfig daecommon.NetworkConfig,
adminToken string,
host bootstrap.Host,
) *garage.AdminClient {
return garage.NewAdminClient(
garageAdminClientLogger(logger),
net.JoinHostPort(
host.IP().String(),
strconv.Itoa(networkConfig.Storage.Allocations[0].AdminPort),
),
adminToken,
)
}
func garageApplyLayout( func garageApplyLayout(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
adminToken string, adminClient *garage.AdminClient,
prevHost, currHost bootstrap.Host, prevHost, currHost bootstrap.Host,
) error { ) error {
var ( var (
adminClient = newGarageAdminClient(
logger, networkConfig, adminToken, currHost,
)
hostName = currHost.Name hostName = currHost.Name
allocs = networkConfig.Storage.Allocations allocs = networkConfig.Storage.Allocations
roles = make([]garage.Role, len(allocs)) roles = make([]garage.Role, len(allocs))
@ -121,14 +94,11 @@ func garageInitializeGlobalBucket(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
networkConfig daecommon.NetworkConfig, networkConfig daecommon.NetworkConfig,
adminToken string, adminClient *garage.AdminClient,
host bootstrap.Host, host bootstrap.Host,
) ( ) (
garage.S3APICredentials, error, garage.S3APICredentials, error,
) { ) {
adminClient := newGarageAdminClient(logger, networkConfig, adminToken, host)
defer adminClient.Close()
creds, err := adminClient.CreateS3APICredentials( creds, err := adminClient.CreateS3APICredentials(
ctx, garage.GlobalBucketS3APICredentialsName, ctx, garage.GlobalBucketS3APICredentialsName,
) )
@ -303,22 +273,8 @@ func removeGarageBootstrapHost(
func garageWaitForAlloc( func garageWaitForAlloc(
ctx context.Context, ctx context.Context,
logger *mlog.Logger, logger *mlog.Logger,
alloc daecommon.ConfigStorageAllocation, adminClient *garage.AdminClient,
adminToken string,
host bootstrap.Host,
) error { ) error {
var (
hostIP = host.IP().String()
adminClient = garage.NewAdminClient(
garageAdminClientLogger(logger),
net.JoinHostPort(hostIP, strconv.Itoa(alloc.AdminPort)),
adminToken,
)
)
defer adminClient.Close()
ctx = mctx.WithAnnotator(ctx, alloc)
logger.Info(ctx, "Checking if garage instance has synced bucket list") logger.Info(ctx, "Checking if garage instance has synced bucket list")
if err := toolkit.UntilTrue( if err := toolkit.UntilTrue(
ctx, ctx,

View File

@ -524,15 +524,22 @@ func (n *network) postChildrenInit(
n.l.RLock() n.l.RLock()
defer n.l.RUnlock() defer n.l.RUnlock()
thisHost := n.currBootstrap.ThisHost() var (
thisHost = n.currBootstrap.ThisHost()
garageAdminClient, hasGarage = n.children.GarageAdminClient()
)
if len(thisHost.Garage.Instances) > 0 { if hasGarage {
defer garageAdminClient.Close()
}
if hasGarage {
n.logger.Info(ctx, "Applying garage layout") n.logger.Info(ctx, "Applying garage layout")
if err := garageApplyLayout( if err := garageApplyLayout(
ctx, ctx,
n.logger, n.logger,
n.networkConfig, n.networkConfig,
n.opts.GarageAdminToken, garageAdminClient,
prevThisHost, thisHost, prevThisHost, thisHost,
); err != nil { ); err != nil {
return fmt.Errorf("applying garage layout: %w", err) return fmt.Errorf("applying garage layout: %w", err)
@ -545,7 +552,7 @@ func (n *network) postChildrenInit(
ctx, ctx,
n.logger, n.logger,
n.networkConfig, n.networkConfig,
n.opts.GarageAdminToken, garageAdminClient,
thisHost, thisHost,
) )
if err != nil { if err != nil {
@ -561,8 +568,16 @@ func (n *network) postChildrenInit(
} }
for _, alloc := range n.networkConfig.Storage.Allocations { for _, alloc := range n.networkConfig.Storage.Allocations {
garageAdminClient, ok := n.children.GarageAdminClientForAlloc(alloc)
if !ok {
return fmt.Errorf("no garage instance created for %+v", alloc)
}
defer garageAdminClient.Close()
ctx := mctx.WithAnnotator(ctx, alloc)
if err := garageWaitForAlloc( if err := garageWaitForAlloc(
ctx, n.logger, alloc, n.opts.GarageAdminToken, thisHost, ctx, n.logger, garageAdminClient,
); err != nil { ); err != nil {
return fmt.Errorf( return fmt.Errorf(
"waiting for alloc %+v to initialize: %w", alloc, err, "waiting for alloc %+v to initialize: %w", alloc, err,

View File

@ -369,12 +369,8 @@ func (nh *integrationHarnessNetwork) getBootstrap(
func (nh *integrationHarnessNetwork) garageAdminClient( func (nh *integrationHarnessNetwork) garageAdminClient(
t *testing.T, t *testing.T,
) *garage.AdminClient { ) *garage.AdminClient {
c := newGarageAdminClient( c, ok := nh.Network.(*network).children.GarageAdminClient()
nh.logger, require.True(t, ok)
nh.getConfig(t),
nh.opts.GarageAdminToken,
nh.getBootstrap(t).ThisHost(),
)
t.Cleanup(func() { assert.NoError(t, c.Close()) }) t.Cleanup(func() { assert.NoError(t, c.Close()) })
return c return c
} }