Compare commits
No commits in common. "b7c097ef63512c5ec8a2a3f6389bc9e61fcf7f69" and "48611df2cb775ad3255e6c23be640796619e3fe7" have entirely different histories.
b7c097ef63
...
48611df2cb
61
default.nix
61
default.nix
@ -1,10 +1,14 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
buildSystem ? builtins.currentSystem,
|
buildSystem ? builtins.currentSystem,
|
||||||
hostSystem ? buildSystem,
|
hostSystem ? buildSystem,
|
||||||
pkgsNix ? (import ./nix/pkgs.nix),
|
pkgsNix ? (import ./nix/pkgs.nix),
|
||||||
|
|
||||||
revision ? "dev",
|
revision ? "dev",
|
||||||
releaseName ? "dev",
|
releaseName ? "dev",
|
||||||
|
|
||||||
|
bootstrap ? null, # TODO remove this
|
||||||
|
|
||||||
}: let
|
}: let
|
||||||
|
|
||||||
pkgs = pkgsNix.default {
|
pkgs = pkgsNix.default {
|
||||||
@ -90,23 +94,35 @@ in rec {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
appDirBase = pkgs.buildEnv {
|
rootedBootstrap = pkgs.stdenv.mkDerivation {
|
||||||
name = "isle-AppDir-base";
|
name = "isle-rooted-bootstrap";
|
||||||
paths = [
|
|
||||||
./AppDir
|
src = bootstrap;
|
||||||
version
|
|
||||||
dnsmasq
|
builder = builtins.toFile "builder.sh" ''
|
||||||
nebula
|
source $stdenv/setup
|
||||||
garage
|
mkdir -p "$out"/share
|
||||||
pkgs.minio-client
|
cp "$src" "$out"/share/bootstrap.json
|
||||||
];
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
appDir = pkgs.stdenv.mkDerivation {
|
appDir = pkgs.stdenv.mkDerivation {
|
||||||
name = "isle-AppDir";
|
name = "isle-AppDir";
|
||||||
|
|
||||||
src = appDirBase;
|
src = pkgs.buildEnv {
|
||||||
inherit goBinaries;
|
name = "isle-AppDir-base";
|
||||||
|
paths = [
|
||||||
|
|
||||||
|
./AppDir
|
||||||
|
version
|
||||||
|
dnsmasq
|
||||||
|
nebula
|
||||||
|
garage
|
||||||
|
pkgs.minio-client
|
||||||
|
goBinaries
|
||||||
|
|
||||||
|
] ++ (if bootstrap != null then [ rootedBootstrap ] else []);
|
||||||
|
};
|
||||||
|
|
||||||
builder = builtins.toFile "build.sh" ''
|
builder = builtins.toFile "build.sh" ''
|
||||||
source $stdenv/setup
|
source $stdenv/setup
|
||||||
@ -114,7 +130,7 @@ in rec {
|
|||||||
chmod +w "$out" -R
|
chmod +w "$out" -R
|
||||||
|
|
||||||
cd "$out"
|
cd "$out"
|
||||||
cp $goBinaries/bin/entrypoint ./AppRun
|
cp ./bin/entrypoint ./AppRun
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,23 +178,4 @@ in rec {
|
|||||||
export SHELL=${pkgs.bash}/bin/bash
|
export SHELL=${pkgs.bash}/bin/bash
|
||||||
exec ${pkgs.bash}/bin/bash ${./tests}/entrypoint.sh "$@"
|
exec ${pkgs.bash}/bin/bash ${./tests}/entrypoint.sh "$@"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
devShell = pkgs.mkShell {
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.go
|
|
||||||
pkgs.golangci-lint
|
|
||||||
pkgs.gopls
|
|
||||||
(pkgs.callPackage ./nix/gowrap.nix {})
|
|
||||||
];
|
|
||||||
shellHook = ''
|
|
||||||
true # placeholder
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
testShell = pkgs.mkShell {
|
|
||||||
APPDIR = appDirBase;
|
|
||||||
buildInputs = [
|
|
||||||
pkgs.go
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"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/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
|
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
@ -49,12 +48,10 @@ func (o *Opts) withDefaults() *Opts {
|
|||||||
// - dnsmasq
|
// - dnsmasq
|
||||||
// - garage (0 or more, depending on configured storage allocations)
|
// - garage (0 or more, depending on configured storage allocations)
|
||||||
type Children struct {
|
type Children struct {
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
runtimeDir toolkit.Dir
|
networkConfig daecommon.NetworkConfig
|
||||||
garageAdminToken string
|
runtimeDir toolkit.Dir
|
||||||
opts Opts
|
opts Opts
|
||||||
|
|
||||||
garageRPCSecret string
|
|
||||||
|
|
||||||
pmux *pmuxlib.Pmux
|
pmux *pmuxlib.Pmux
|
||||||
}
|
}
|
||||||
@ -83,11 +80,10 @@ func New(
|
|||||||
}
|
}
|
||||||
|
|
||||||
c := &Children{
|
c := &Children{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
runtimeDir: runtimeDir,
|
networkConfig: networkConfig,
|
||||||
garageAdminToken: garageAdminToken,
|
runtimeDir: runtimeDir,
|
||||||
opts: *opts,
|
opts: *opts,
|
||||||
garageRPCSecret: garageRPCSecret,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pmuxConfig, err := c.newPmuxConfig(
|
pmuxConfig, err := c.newPmuxConfig(
|
||||||
@ -116,126 +112,57 @@ func New(
|
|||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestartDNSMasq rewrites the dnsmasq config and restarts the process.
|
||||||
|
//
|
||||||
// TODO block until process has been confirmed to have come back up
|
// TODO block until process has been confirmed to have come back up
|
||||||
// successfully.
|
// successfully.
|
||||||
func (c *Children) reloadDNSMasq(
|
func (c *Children) RestartDNSMasq(hostBootstrap bootstrap.Bootstrap) error {
|
||||||
ctx context.Context,
|
_, err := dnsmasqWriteConfig(
|
||||||
networkConfig daecommon.NetworkConfig,
|
c.runtimeDir.Path, c.networkConfig, hostBootstrap,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
)
|
||||||
) error {
|
if err != nil {
|
||||||
if _, changed, err := dnsmasqWriteConfig(
|
|
||||||
ctx, c.logger, c.runtimeDir.Path, networkConfig, hostBootstrap,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("writing new dnsmasq config: %w", err)
|
return fmt.Errorf("writing new dnsmasq config: %w", err)
|
||||||
} else if !changed {
|
|
||||||
c.logger.Info(ctx, "No changes to dnsmasq config file")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Info(ctx, "dnsmasq config file has changed, restarting process")
|
|
||||||
c.pmux.Restart("dnsmasq")
|
c.pmux.Restart("dnsmasq")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Children) reloadNebula(
|
// RestartNebula rewrites the nebula config and restarts the process.
|
||||||
ctx context.Context,
|
//
|
||||||
networkConfig daecommon.NetworkConfig,
|
// TODO block until process has been confirmed to have come back up
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
// successfully.
|
||||||
) error {
|
func (c *Children) RestartNebula(hostBootstrap bootstrap.Bootstrap) error {
|
||||||
if _, changed, err := nebulaWriteConfig(
|
_, err := nebulaWriteConfig(
|
||||||
ctx, c.logger, c.runtimeDir.Path, networkConfig, hostBootstrap,
|
c.runtimeDir.Path, c.networkConfig, hostBootstrap,
|
||||||
); err != nil {
|
)
|
||||||
|
if err != nil {
|
||||||
return fmt.Errorf("writing a new nebula config: %w", err)
|
return fmt.Errorf("writing a new nebula config: %w", err)
|
||||||
} else if !changed {
|
|
||||||
c.logger.Info(ctx, "No changes to nebula config file")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Info(ctx, "nebula config file has changed, restarting process")
|
|
||||||
c.pmux.Restart("nebula")
|
c.pmux.Restart("nebula")
|
||||||
|
|
||||||
if err := waitForNebula(ctx, c.logger, hostBootstrap); err != nil {
|
|
||||||
return fmt.Errorf("waiting for nebula to start: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO this doesn't handle removing garage nodes
|
|
||||||
func (c *Children) reloadGarage(
|
|
||||||
ctx context.Context,
|
|
||||||
networkConfig daecommon.NetworkConfig,
|
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
|
||||||
) error {
|
|
||||||
allocs := networkConfig.Storage.Allocations
|
|
||||||
if len(allocs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var anyChanged bool
|
|
||||||
for _, alloc := range allocs {
|
|
||||||
var (
|
|
||||||
procName = garagePmuxProcName(alloc)
|
|
||||||
ctx = mctx.Annotate(
|
|
||||||
ctx,
|
|
||||||
"garageProcName", procName,
|
|
||||||
"garageDataPath", alloc.DataPath,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
_, changed, err := garageWriteChildConfig(
|
|
||||||
ctx,
|
|
||||||
c.logger,
|
|
||||||
c.garageRPCSecret,
|
|
||||||
c.runtimeDir.Path,
|
|
||||||
c.garageAdminToken,
|
|
||||||
hostBootstrap,
|
|
||||||
alloc,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
|
||||||
} else if !changed {
|
|
||||||
c.logger.Info(ctx, "No changes to garage config file")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
anyChanged = true
|
|
||||||
|
|
||||||
c.logger.Info(ctx, "garage config has changed, restarting process")
|
|
||||||
c.pmux.Restart(procName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if anyChanged {
|
|
||||||
if err := waitForGarage(
|
|
||||||
ctx, c.logger, networkConfig, c.garageAdminToken, hostBootstrap,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("waiting for garage to start: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload applies a ReloadDiff to the Children, using the given bootstrap which
|
// Reload applies a ReloadDiff to the Children, using the given bootstrap which
|
||||||
// must be the same one which was passed to CalculateReloadDiff.
|
// must be the same one which was passed to CalculateReloadDiff.
|
||||||
func (c *Children) Reload(
|
func (c *Children) Reload(
|
||||||
ctx context.Context,
|
ctx context.Context, newBootstrap bootstrap.Bootstrap, diff ReloadDiff,
|
||||||
newNetworkConfig daecommon.NetworkConfig,
|
|
||||||
newBootstrap bootstrap.Bootstrap,
|
|
||||||
) error {
|
) error {
|
||||||
if err := c.reloadNebula(ctx, newNetworkConfig, newBootstrap); err != nil {
|
|
||||||
return fmt.Errorf("reloading nebula: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
if err := c.reloadDNSMasq(ctx, newNetworkConfig, newBootstrap); err != nil {
|
if diff.DNSChanged {
|
||||||
errs = append(errs, fmt.Errorf("reloading dnsmasq: %w", err))
|
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 err := c.reloadGarage(ctx, newNetworkConfig, newBootstrap); err != nil {
|
if diff.NebulaChanged {
|
||||||
errs = append(errs, fmt.Errorf("reloading garage: %w", err))
|
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...)
|
return errors.Join(errs...)
|
||||||
|
47
go/daemon/children/diff.go
Normal file
47
go/daemon/children/diff.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package children
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"isle/bootstrap"
|
||||||
|
"isle/daemon/daecommon"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReloadDiff describes which children had their configurations changed as part
|
||||||
|
// of a change in the bootstrap.
|
||||||
|
type ReloadDiff struct {
|
||||||
|
NebulaChanged bool
|
||||||
|
DNSChanged bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateReloadDiff calculates a ReloadDiff based on an old and new
|
||||||
|
// bootstrap.
|
||||||
|
func CalculateReloadDiff(
|
||||||
|
networkConfig daecommon.NetworkConfig,
|
||||||
|
prevBootstrap, nextBootstrap bootstrap.Bootstrap,
|
||||||
|
) (
|
||||||
|
diff ReloadDiff, err error,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
prevNebulaConfig, prevErr := nebulaConfig(networkConfig, prevBootstrap)
|
||||||
|
nextNebulaConfig, nextErr := nebulaConfig(networkConfig, nextBootstrap)
|
||||||
|
if err = errors.Join(prevErr, nextErr); err != nil {
|
||||||
|
err = fmt.Errorf("calculating nebula config: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
diff.NebulaChanged = !reflect.DeepEqual(
|
||||||
|
prevNebulaConfig, nextNebulaConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
diff.DNSChanged = !reflect.DeepEqual(
|
||||||
|
dnsmasqConfig(networkConfig, prevBootstrap),
|
||||||
|
dnsmasqConfig(networkConfig, nextBootstrap),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -7,50 +7,55 @@ import (
|
|||||||
"isle/daemon/daecommon"
|
"isle/daemon/daecommon"
|
||||||
"isle/dnsmasq"
|
"isle/dnsmasq"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"code.betamike.com/micropelago/pmux/pmuxlib"
|
"code.betamike.com/micropelago/pmux/pmuxlib"
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func dnsmasqWriteConfig(
|
func dnsmasqConfig(
|
||||||
ctx context.Context,
|
networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap,
|
||||||
logger *mlog.Logger,
|
) dnsmasq.ConfData {
|
||||||
runtimeDirPath string,
|
hostsSlice := make([]dnsmasq.ConfDataHost, 0, len(hostBootstrap.Hosts))
|
||||||
networkConfig daecommon.NetworkConfig,
|
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
|
||||||
) (
|
|
||||||
string, bool, error,
|
|
||||||
) {
|
|
||||||
hosts := make([]dnsmasq.ConfDataHost, 0, len(hostBootstrap.Hosts))
|
|
||||||
for _, host := range hostBootstrap.Hosts {
|
for _, host := range hostBootstrap.Hosts {
|
||||||
hosts = append(hosts, dnsmasq.ConfDataHost{
|
hostsSlice = append(hostsSlice, dnsmasq.ConfDataHost{
|
||||||
Name: string(host.Name),
|
Name: string(host.Name),
|
||||||
IP: host.IP().String(),
|
IP: host.IP().String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sort.Slice(hostsSlice, func(i, j int) bool {
|
||||||
|
return hostsSlice[i].IP < hostsSlice[j].IP
|
||||||
|
})
|
||||||
|
|
||||||
|
return dnsmasq.ConfData{
|
||||||
|
Resolvers: networkConfig.DNS.Resolvers,
|
||||||
|
Domain: hostBootstrap.NetworkCreationParams.Domain,
|
||||||
|
IP: hostBootstrap.ThisHost().IP().String(),
|
||||||
|
Hosts: hostsSlice,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dnsmasqWriteConfig(
|
||||||
|
runtimeDirPath string,
|
||||||
|
networkConfig daecommon.NetworkConfig,
|
||||||
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
|
) (
|
||||||
|
string, error,
|
||||||
|
) {
|
||||||
var (
|
var (
|
||||||
confPath = filepath.Join(runtimeDirPath, "dnsmasq.conf")
|
confPath = filepath.Join(runtimeDirPath, "dnsmasq.conf")
|
||||||
confData = dnsmasq.ConfData{
|
confData = dnsmasqConfig(networkConfig, hostBootstrap)
|
||||||
Resolvers: networkConfig.DNS.Resolvers,
|
|
||||||
Domain: hostBootstrap.NetworkCreationParams.Domain,
|
|
||||||
IP: hostBootstrap.ThisHost().IP().String(),
|
|
||||||
Hosts: hosts,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
changed, err := dnsmasq.WriteConfFile(ctx, logger, confPath, confData)
|
if err := dnsmasq.WriteConfFile(confPath, confData); err != nil {
|
||||||
if err != nil {
|
return "", fmt.Errorf("writing dnsmasq.conf to %q: %w", confPath, err)
|
||||||
return "", false, fmt.Errorf(
|
|
||||||
"writing dnsmasq.conf to %q: %w", confPath, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return confPath, changed, nil
|
return confPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsmasqPmuxProcConfig(
|
func dnsmasqPmuxProcConfig(
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
runtimeDirPath, binDirPath string,
|
runtimeDirPath, binDirPath string,
|
||||||
networkConfig daecommon.NetworkConfig,
|
networkConfig daecommon.NetworkConfig,
|
||||||
@ -58,8 +63,8 @@ func dnsmasqPmuxProcConfig(
|
|||||||
) (
|
) (
|
||||||
pmuxlib.ProcessConfig, error,
|
pmuxlib.ProcessConfig, error,
|
||||||
) {
|
) {
|
||||||
confPath, _, err := dnsmasqWriteConfig(
|
confPath, err := dnsmasqWriteConfig(
|
||||||
ctx, logger, runtimeDirPath, networkConfig, hostBootstrap,
|
runtimeDirPath, networkConfig, hostBootstrap,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
||||||
|
@ -2,7 +2,6 @@ package children
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon/daecommon"
|
"isle/daemon/daecommon"
|
||||||
@ -30,6 +29,9 @@ func waitForGarage(
|
|||||||
) error {
|
) error {
|
||||||
|
|
||||||
allocs := networkConfig.Storage.Allocations
|
allocs := networkConfig.Storage.Allocations
|
||||||
|
|
||||||
|
// if this host doesn't have any allocations specified then fall back to
|
||||||
|
// waiting for nebula
|
||||||
if len(allocs) == 0 {
|
if len(allocs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -47,16 +49,12 @@ func waitForGarage(
|
|||||||
adminClientLogger, adminAddr, adminToken,
|
adminClientLogger, adminAddr, adminToken,
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx := mctx.Annotate(
|
ctx := mctx.Annotate(ctx, "garageAdminAddr", adminAddr)
|
||||||
ctx, "garageAdminAddr", adminAddr, "garageDataPath", alloc.DataPath,
|
logger.Debug(ctx, "waiting for garage instance to start")
|
||||||
)
|
|
||||||
logger.Info(ctx, "Waiting for garage instance to be healthy")
|
|
||||||
|
|
||||||
if err := adminClient.Wait(ctx); err != nil {
|
if err := adminClient.Wait(ctx); err != nil {
|
||||||
return fmt.Errorf("waiting for garage instance %q to start up: %w", adminAddr, err)
|
return fmt.Errorf("waiting for garage instance %q to start up: %w", adminAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
adminClient.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -64,60 +62,46 @@ func waitForGarage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func garageWriteChildConfig(
|
func garageWriteChildConfig(
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
|
||||||
rpcSecret, runtimeDirPath, adminToken string,
|
rpcSecret, runtimeDirPath, adminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
alloc daecommon.ConfigStorageAllocation,
|
alloc daecommon.ConfigStorageAllocation,
|
||||||
) (
|
) (
|
||||||
string, bool, error,
|
string, error,
|
||||||
) {
|
) {
|
||||||
var (
|
|
||||||
thisHost = hostBootstrap.ThisHost()
|
|
||||||
id = daecommon.BootstrapGarageHostForAlloc(thisHost, alloc).ID
|
|
||||||
|
|
||||||
peer = garage.LocalPeer{
|
thisHost := hostBootstrap.ThisHost()
|
||||||
RemotePeer: garage.RemotePeer{
|
id := daecommon.BootstrapGarageHostForAlloc(thisHost, alloc).ID
|
||||||
ID: id,
|
|
||||||
IP: thisHost.IP().String(),
|
|
||||||
RPCPort: alloc.RPCPort,
|
|
||||||
S3APIPort: alloc.S3APIPort,
|
|
||||||
},
|
|
||||||
AdminPort: alloc.AdminPort,
|
|
||||||
}
|
|
||||||
|
|
||||||
garageTomlPath = filepath.Join(
|
peer := garage.LocalPeer{
|
||||||
runtimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort),
|
RemotePeer: garage.RemotePeer{
|
||||||
)
|
ID: id,
|
||||||
)
|
IP: thisHost.IP().String(),
|
||||||
|
RPCPort: alloc.RPCPort,
|
||||||
changed, err := garagesrv.WriteGarageTomlFile(
|
S3APIPort: alloc.S3APIPort,
|
||||||
ctx,
|
|
||||||
logger,
|
|
||||||
garageTomlPath,
|
|
||||||
garagesrv.GarageTomlData{
|
|
||||||
MetaPath: alloc.MetaPath,
|
|
||||||
DataPath: alloc.DataPath,
|
|
||||||
|
|
||||||
RPCSecret: rpcSecret,
|
|
||||||
AdminToken: adminToken,
|
|
||||||
|
|
||||||
LocalPeer: peer,
|
|
||||||
BootstrapPeers: hostBootstrap.GaragePeers(),
|
|
||||||
},
|
},
|
||||||
)
|
AdminPort: alloc.AdminPort,
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", false, fmt.Errorf(
|
|
||||||
"creating garage.toml file at %q: %w", garageTomlPath, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return garageTomlPath, changed, nil
|
garageTomlPath := filepath.Join(
|
||||||
}
|
runtimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort),
|
||||||
|
)
|
||||||
|
|
||||||
func garagePmuxProcName(alloc daecommon.ConfigStorageAllocation) string {
|
err := garagesrv.WriteGarageTomlFile(garageTomlPath, garagesrv.GarageTomlData{
|
||||||
return fmt.Sprintf("garage-%d", alloc.RPCPort)
|
MetaPath: alloc.MetaPath,
|
||||||
|
DataPath: alloc.DataPath,
|
||||||
|
|
||||||
|
RPCSecret: rpcSecret,
|
||||||
|
AdminToken: adminToken,
|
||||||
|
|
||||||
|
LocalPeer: peer,
|
||||||
|
BootstrapPeers: hostBootstrap.GaragePeers(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("creating garage.toml file at %q: %w", garageTomlPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return garageTomlPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func garagePmuxProcConfigs(
|
func garagePmuxProcConfigs(
|
||||||
@ -136,22 +120,20 @@ func garagePmuxProcConfigs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if len(allocs) > 0 && rpcSecret == "" {
|
if len(allocs) > 0 && rpcSecret == "" {
|
||||||
return nil, errors.New("Storage allocations defined, but garage RPC secret is not available")
|
logger.WarnString(ctx, "Not starting garage instances for storage allocations, missing garage RPC secret")
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, alloc := range allocs {
|
for _, alloc := range allocs {
|
||||||
childConfigPath, _, err := garageWriteChildConfig(
|
|
||||||
ctx,
|
childConfigPath, err := garageWriteChildConfig(
|
||||||
logger,
|
rpcSecret, runtimeDirPath, adminToken, hostBootstrap, alloc,
|
||||||
rpcSecret, runtimeDirPath, adminToken,
|
|
||||||
hostBootstrap,
|
|
||||||
alloc,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
procName := garagePmuxProcName(alloc)
|
procName := fmt.Sprintf("garage-%d", alloc.RPCPort)
|
||||||
pmuxProcConfigs[procName] = pmuxlib.ProcessConfig{
|
pmuxProcConfigs[procName] = pmuxlib.ProcessConfig{
|
||||||
Cmd: filepath.Join(binDirPath, "garage"),
|
Cmd: filepath.Join(binDirPath, "garage"),
|
||||||
Args: []string{"-c", childConfigPath, "server"},
|
Args: []string{"-c", childConfigPath, "server"},
|
||||||
|
@ -3,10 +3,9 @@ package children
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon/daecommon"
|
"isle/daemon/daecommon"
|
||||||
"isle/toolkit"
|
"isle/yamlutil"
|
||||||
"net"
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -14,7 +13,6 @@ import (
|
|||||||
"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"
|
||||||
"github.com/slackhq/nebula/cert"
|
"github.com/slackhq/nebula/cert"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// waitForNebula waits for the nebula interface to have been started up. It does
|
// waitForNebula waits for the nebula interface to have been started up. It does
|
||||||
@ -35,7 +33,7 @@ func waitForNebula(
|
|||||||
until(
|
until(
|
||||||
ctx,
|
ctx,
|
||||||
logger,
|
logger,
|
||||||
"Checking if nebula is online by creating UDP socket from nebula IP",
|
"Creating UDP socket from nebula addr",
|
||||||
func(context.Context) error {
|
func(context.Context) error {
|
||||||
conn, err := net.DialUDP("udp", lUDPAddr, rUDPAddr)
|
conn, err := net.DialUDP("udp", lUDPAddr, rUDPAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -49,7 +47,6 @@ func waitForNebula(
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this needs to be produce a deterministic config value.
|
|
||||||
func nebulaConfig(
|
func nebulaConfig(
|
||||||
networkConfig daecommon.NetworkConfig,
|
networkConfig daecommon.NetworkConfig,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
@ -116,22 +113,16 @@ func nebulaConfig(
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(publicAddr)
|
_, port, err := net.SplitHostPort(publicAddr)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"parsing public address %q: %w", publicAddr, err,
|
"parsing public address %q: %w", publicAddr, err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This helps with integration testing, so we can set a test to listen
|
|
||||||
// on some local IP without conflicting with something else running on
|
|
||||||
// the host.
|
|
||||||
if hostIP := net.ParseIP(host); hostIP == nil || !hostIP.IsLoopback() {
|
|
||||||
host = "0.0.0.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
config["listen"] = map[string]string{
|
config["listen"] = map[string]string{
|
||||||
"host": host,
|
"host": "0.0.0.0",
|
||||||
"port": port,
|
"port": port,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,50 +136,44 @@ func nebulaConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nebulaWriteConfig(
|
func nebulaWriteConfig(
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
|
||||||
runtimeDirPath string,
|
runtimeDirPath string,
|
||||||
networkConfig daecommon.NetworkConfig,
|
networkConfig daecommon.NetworkConfig,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
string, bool, error,
|
string, error,
|
||||||
) {
|
) {
|
||||||
config, err := nebulaConfig(networkConfig, hostBootstrap)
|
config, err := nebulaConfig(networkConfig, hostBootstrap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", false, fmt.Errorf("creating nebula config: %w", err)
|
return "", fmt.Errorf("creating nebula config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nebulaYmlPath := filepath.Join(runtimeDirPath, "nebula.yml")
|
nebulaYmlPath := filepath.Join(runtimeDirPath, "nebula.yml")
|
||||||
|
|
||||||
changed, err := toolkit.WriteFileCheckChanged(
|
if err := yamlutil.WriteYamlFile(config, nebulaYmlPath, 0600); err != nil {
|
||||||
ctx, logger, nebulaYmlPath, 0600, func(w io.Writer) error {
|
return "", fmt.Errorf("writing nebula.yml to %q: %w", nebulaYmlPath, err)
|
||||||
return yaml.NewEncoder(w).Encode(config)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", false, fmt.Errorf(
|
|
||||||
"writing nebula.yml to %q: %w", nebulaYmlPath, err,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nebulaYmlPath, changed, nil
|
return nebulaYmlPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nebulaPmuxProcConfig(
|
func nebulaPmuxProcConfig(
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
|
||||||
runtimeDirPath, binDirPath string,
|
runtimeDirPath, binDirPath string,
|
||||||
networkConfig daecommon.NetworkConfig,
|
networkConfig daecommon.NetworkConfig,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
pmuxlib.ProcessConfig, error,
|
pmuxlib.ProcessConfig, error,
|
||||||
) {
|
) {
|
||||||
nebulaYmlPath, _, err := nebulaWriteConfig(
|
config, err := nebulaConfig(networkConfig, hostBootstrap)
|
||||||
ctx, logger, runtimeDirPath, networkConfig, hostBootstrap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
||||||
"writing nebula config: %w", err,
|
"creating nebula config: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
nebulaYmlPath := filepath.Join(runtimeDirPath, "nebula.yml")
|
||||||
|
if err := yamlutil.WriteYamlFile(config, nebulaYmlPath, 0600); err != nil {
|
||||||
|
return pmuxlib.ProcessConfig{}, fmt.Errorf(
|
||||||
|
"writing nebula.yml to %q: %w", nebulaYmlPath, err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +19,6 @@ func (c *Children) newPmuxConfig(
|
|||||||
pmuxlib.Config, error,
|
pmuxlib.Config, error,
|
||||||
) {
|
) {
|
||||||
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(
|
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(
|
||||||
ctx,
|
|
||||||
c.logger,
|
|
||||||
c.runtimeDir.Path,
|
c.runtimeDir.Path,
|
||||||
binDirPath,
|
binDirPath,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
@ -31,7 +29,6 @@ func (c *Children) newPmuxConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(
|
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(
|
||||||
ctx,
|
|
||||||
c.logger,
|
c.logger,
|
||||||
c.runtimeDir.Path,
|
c.runtimeDir.Path,
|
||||||
binDirPath,
|
binDirPath,
|
||||||
@ -75,10 +72,12 @@ func (c *Children) postPmuxInit(
|
|||||||
garageAdminToken string,
|
garageAdminToken string,
|
||||||
hostBootstrap bootstrap.Bootstrap,
|
hostBootstrap bootstrap.Bootstrap,
|
||||||
) error {
|
) error {
|
||||||
|
c.logger.Info(ctx, "Waiting for nebula VPN to come online")
|
||||||
if err := waitForNebula(ctx, c.logger, hostBootstrap); err != nil {
|
if err := waitForNebula(ctx, c.logger, hostBootstrap); err != nil {
|
||||||
return fmt.Errorf("waiting for nebula to start: %w", err)
|
return fmt.Errorf("waiting for nebula to start: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.logger.Info(ctx, "Waiting for garage instances to come online")
|
||||||
err := waitForGarage(
|
err := waitForGarage(
|
||||||
ctx, c.logger, networkConfig, garageAdminToken, hostBootstrap,
|
ctx, c.logger, networkConfig, garageAdminToken, hostBootstrap,
|
||||||
)
|
)
|
||||||
|
@ -9,7 +9,6 @@ package daemon
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon/daecommon"
|
|
||||||
"isle/daemon/jsonrpc2"
|
"isle/daemon/jsonrpc2"
|
||||||
"isle/daemon/network"
|
"isle/daemon/network"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
@ -60,15 +59,6 @@ func (c *rpcClient) CreateNetwork(ctx context.Context, name string, domain strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rpcClient) GetConfig(ctx context.Context) (n1 daecommon.NetworkConfig, err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
&n1,
|
|
||||||
"GetConfig",
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *rpcClient) GetGarageClientParams(ctx context.Context) (g1 network.GarageClientParams, err error) {
|
func (c *rpcClient) GetGarageClientParams(ctx context.Context) (g1 network.GarageClientParams, err error) {
|
||||||
err = c.client.Call(
|
err = c.client.Call(
|
||||||
ctx,
|
ctx,
|
||||||
@ -124,13 +114,3 @@ func (c *rpcClient) RemoveHost(ctx context.Context, hostName nebula.HostName) (e
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rpcClient) SetConfig(ctx context.Context, n1 daecommon.NetworkConfig) (err error) {
|
|
||||||
err = c.client.Call(
|
|
||||||
ctx,
|
|
||||||
nil,
|
|
||||||
"SetConfig",
|
|
||||||
n1,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/toolkit"
|
"isle/toolkit"
|
||||||
"isle/yamlutil"
|
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
@ -71,7 +71,7 @@ type NetworkConfig struct {
|
|||||||
Tun ConfigTun `yaml:"tun"`
|
Tun ConfigTun `yaml:"tun"`
|
||||||
} `yaml:"vpn"`
|
} `yaml:"vpn"`
|
||||||
Storage struct {
|
Storage struct {
|
||||||
Allocations []ConfigStorageAllocation `yaml:"allocations"`
|
Allocations []ConfigStorageAllocation
|
||||||
} `yaml:"storage"`
|
} `yaml:"storage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,18 +145,6 @@ func (c *NetworkConfig) fillDefaults() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface. It will attempt to
|
|
||||||
// fill in default values where it can.
|
|
||||||
func (c *NetworkConfig) UnmarshalYAML(n *yaml.Node) error {
|
|
||||||
type wrap NetworkConfig
|
|
||||||
if err := n.Decode((*wrap)(c)); err != nil {
|
|
||||||
return fmt.Errorf("decoding into %T: %w", c, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.fillDefaults()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config describes the structure of the daemon config file.
|
// Config describes the structure of the daemon config file.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Networks map[string]NetworkConfig `yaml:"networks"`
|
Networks map[string]NetworkConfig `yaml:"networks"`
|
||||||
@ -198,30 +186,6 @@ func CopyDefaultConfig(into io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalYAML implements the yaml.Unmarshaler interface. It will attempt to
|
|
||||||
// fill in default values where it can.
|
|
||||||
func (c *Config) UnmarshalYAML(n *yaml.Node) error {
|
|
||||||
{ // DEPRECATED
|
|
||||||
var networkConfig NetworkConfig
|
|
||||||
_ = n.Decode(&networkConfig)
|
|
||||||
if !toolkit.IsZero(networkConfig) {
|
|
||||||
*c = Config{
|
|
||||||
Networks: map[string]NetworkConfig{
|
|
||||||
DeprecatedNetworkID: networkConfig,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c.Validate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrap Config
|
|
||||||
if err := n.Decode((*wrap)(c)); err != nil {
|
|
||||||
return fmt.Errorf("yaml unmarshaling back into Config struct: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadConfig loads the daemon config from userConfigPath.
|
// LoadConfig loads the daemon config from userConfigPath.
|
||||||
//
|
//
|
||||||
// If userConfigPath is not given then the default is loaded and returned.
|
// If userConfigPath is not given then the default is loaded and returned.
|
||||||
@ -230,17 +194,47 @@ func LoadConfig(userConfigPath string) (Config, error) {
|
|||||||
return Config{}, nil
|
return Config{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userConfigB, err := os.ReadFile(userConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, fmt.Errorf("reading from file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // DEPRECATED
|
||||||
|
var networkConfig NetworkConfig
|
||||||
|
_ = yaml.Unmarshal(userConfigB, &networkConfig)
|
||||||
|
if !toolkit.IsZero(networkConfig) {
|
||||||
|
networkConfig.fillDefaults()
|
||||||
|
config := Config{
|
||||||
|
Networks: map[string]NetworkConfig{
|
||||||
|
DeprecatedNetworkID: networkConfig,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return config, config.Validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var config Config
|
var config Config
|
||||||
err := yamlutil.LoadYamlFile(&config, userConfigPath)
|
if err := yaml.Unmarshal(userConfigB, &config); err != nil {
|
||||||
return config, err
|
return Config{}, fmt.Errorf("yaml unmarshaling back into Config struct: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for id := range config.Networks {
|
||||||
|
network := config.Networks[id]
|
||||||
|
network.fillDefaults()
|
||||||
|
config.Networks[id] = network
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, config.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BootstrapGarageHostForAlloc returns the bootstrap.GarageHostInstance which
|
// BootstrapGarageHostForAlloc returns the bootstrap.GarageHostInstance which
|
||||||
// corresponds with the given alloc from the daemon config. This will panic if
|
// corresponds with the given alloc from the daemon config. This will panic if
|
||||||
// no associated instance can be found.
|
// no associated instance can be found.
|
||||||
func BootstrapGarageHostForAlloc(
|
func BootstrapGarageHostForAlloc(
|
||||||
host bootstrap.Host, alloc ConfigStorageAllocation,
|
host bootstrap.Host,
|
||||||
|
alloc ConfigStorageAllocation,
|
||||||
) bootstrap.GarageHostInstance {
|
) bootstrap.GarageHostInstance {
|
||||||
|
|
||||||
for _, inst := range host.Garage.Instances {
|
for _, inst := range host.Garage.Instances {
|
||||||
if inst.RPCPort == alloc.RPCPort {
|
if inst.RPCPort == alloc.RPCPort {
|
||||||
return inst
|
return inst
|
||||||
|
@ -138,6 +138,7 @@ func New(
|
|||||||
d.networks[id], err = network.Load(
|
d.networks[id], err = network.Load(
|
||||||
ctx,
|
ctx,
|
||||||
logger.WithNamespace("network"),
|
logger.WithNamespace("network"),
|
||||||
|
id,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
d.envBinDirPath,
|
d.envBinDirPath,
|
||||||
networkStateDir,
|
networkStateDir,
|
||||||
@ -439,39 +440,6 @@ func (d *Daemon) CreateNebulaCertificate(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) GetConfig(
|
|
||||||
ctx context.Context,
|
|
||||||
) (
|
|
||||||
daecommon.NetworkConfig, error,
|
|
||||||
) {
|
|
||||||
return withNetwork(
|
|
||||||
ctx,
|
|
||||||
d,
|
|
||||||
func(
|
|
||||||
ctx context.Context, n network.Network,
|
|
||||||
) (
|
|
||||||
daecommon.NetworkConfig, error,
|
|
||||||
) {
|
|
||||||
return n.GetConfig(ctx)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Daemon) SetConfig(
|
|
||||||
ctx context.Context, config daecommon.NetworkConfig,
|
|
||||||
) error {
|
|
||||||
_, err := withNetwork(
|
|
||||||
ctx,
|
|
||||||
d,
|
|
||||||
func(ctx context.Context, n network.Network) (struct{}, error) {
|
|
||||||
// TODO needs to check that public addresses aren't being shared
|
|
||||||
// across networks, and whatever else happens in Config.Validate.
|
|
||||||
return struct{}{}, n.SetConfig(ctx, config)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown blocks until all resources held or created by the daemon,
|
// Shutdown blocks until all resources held or created by the daemon,
|
||||||
// including child processes it has started, have been cleaned up.
|
// including child processes it has started, have been cleaned up.
|
||||||
//
|
//
|
||||||
|
@ -70,10 +70,13 @@ type divider interface {
|
|||||||
Divide2FromMeta(ctx context.Context) (int, error)
|
Divide2FromMeta(ctx context.Context) (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testHandler(t *testing.T) Handler {
|
var testHandler = func() Handler {
|
||||||
var (
|
var (
|
||||||
logger = mlog.NewTestLogger(t)
|
logger = mlog.NewLogger(&mlog.LoggerOpts{
|
||||||
d = divider(dividerImpl{})
|
MaxLevel: mlog.LevelDebug.Int(),
|
||||||
|
})
|
||||||
|
|
||||||
|
d = divider(dividerImpl{})
|
||||||
)
|
)
|
||||||
|
|
||||||
return Chain(
|
return Chain(
|
||||||
@ -82,7 +85,7 @@ func testHandler(t *testing.T) Handler {
|
|||||||
)(
|
)(
|
||||||
NewDispatchHandler(&d),
|
NewDispatchHandler(&d),
|
||||||
)
|
)
|
||||||
}
|
}()
|
||||||
|
|
||||||
func testClient(t *testing.T, client Client) {
|
func testClient(t *testing.T, client Client) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
@ -191,7 +194,7 @@ func TestReadWriter(t *testing.T) {
|
|||||||
clientRW = rw{clientReader, clientWriter}
|
clientRW = rw{clientReader, clientWriter}
|
||||||
handlerRW = rw{handlerReader, handlerWriter}
|
handlerRW = rw{handlerReader, handlerWriter}
|
||||||
|
|
||||||
server = NewReadWriterServer(testHandler(t), handlerRW)
|
server = NewReadWriterServer(testHandler, handlerRW)
|
||||||
client = NewReadWriterClient(clientRW)
|
client = NewReadWriterClient(clientRW)
|
||||||
|
|
||||||
wg = new(sync.WaitGroup)
|
wg = new(sync.WaitGroup)
|
||||||
@ -217,7 +220,7 @@ func TestReadWriter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTP(t *testing.T) {
|
func TestHTTP(t *testing.T) {
|
||||||
server := httptest.NewServer(NewHTTPHandler(testHandler(t)))
|
server := httptest.NewServer(NewHTTPHandler(testHandler))
|
||||||
t.Cleanup(server.Close)
|
t.Cleanup(server.Close)
|
||||||
testClient(t, NewHTTPClient(server.URL))
|
testClient(t, NewHTTPClient(server.URL))
|
||||||
}
|
}
|
||||||
@ -225,7 +228,7 @@ func TestHTTP(t *testing.T) {
|
|||||||
func TestUnixHTTP(t *testing.T) {
|
func TestUnixHTTP(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
unixSocketPath = filepath.Join(t.TempDir(), "test.sock")
|
unixSocketPath = filepath.Join(t.TempDir(), "test.sock")
|
||||||
server = httptest.NewUnstartedServer(NewHTTPHandler(testHandler(t)))
|
server = httptest.NewUnstartedServer(NewHTTPHandler(testHandler))
|
||||||
)
|
)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -25,21 +25,19 @@ const (
|
|||||||
garageGlobalBucketBootstrapHostsDirPath = "bootstrap/hosts"
|
garageGlobalBucketBootstrapHostsDirPath = "bootstrap/hosts"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getGarageClientParams(
|
func (n *network) getGarageClientParams(
|
||||||
ctx context.Context,
|
ctx context.Context, currBootstrap bootstrap.Bootstrap,
|
||||||
secretsStore secrets.Store,
|
|
||||||
currBootstrap bootstrap.Bootstrap,
|
|
||||||
) (
|
) (
|
||||||
GarageClientParams, error,
|
GarageClientParams, error,
|
||||||
) {
|
) {
|
||||||
creds, err := daecommon.GetGarageS3APIGlobalBucketCredentials(
|
creds, err := daecommon.GetGarageS3APIGlobalBucketCredentials(
|
||||||
ctx, secretsStore,
|
ctx, n.secretsStore,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return GarageClientParams{}, fmt.Errorf("getting garage global bucket creds: %w", err)
|
return GarageClientParams{}, fmt.Errorf("getting garage global bucket creds: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcSecret, err := daecommon.GetGarageRPCSecret(ctx, secretsStore)
|
rpcSecret, err := daecommon.GetGarageRPCSecret(ctx, n.secretsStore)
|
||||||
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
|
||||||
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
|
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
|
||||||
}
|
}
|
||||||
@ -94,8 +92,6 @@ func garageApplyLayout(
|
|||||||
peers = make([]garage.PeerLayout, len(allocs))
|
peers = make([]garage.PeerLayout, len(allocs))
|
||||||
)
|
)
|
||||||
|
|
||||||
defer adminClient.Close()
|
|
||||||
|
|
||||||
for i, alloc := range allocs {
|
for i, alloc := range allocs {
|
||||||
|
|
||||||
id := daecommon.BootstrapGarageHostForAlloc(thisHost, alloc).ID
|
id := daecommon.BootstrapGarageHostForAlloc(thisHost, alloc).ID
|
||||||
@ -128,7 +124,6 @@ func garageInitializeGlobalBucket(
|
|||||||
adminClient := newGarageAdminClient(
|
adminClient := newGarageAdminClient(
|
||||||
logger, networkConfig, adminToken, hostBootstrap,
|
logger, networkConfig, adminToken, hostBootstrap,
|
||||||
)
|
)
|
||||||
defer adminClient.Close()
|
|
||||||
|
|
||||||
creds, err := adminClient.CreateS3APICredentials(
|
creds, err := adminClient.CreateS3APICredentials(
|
||||||
ctx, garage.GlobalBucketS3APICredentialsName,
|
ctx, garage.GlobalBucketS3APICredentialsName,
|
||||||
@ -157,17 +152,12 @@ func garageInitializeGlobalBucket(
|
|||||||
return creds, nil
|
return creds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGarageBootstrapHosts(
|
func (n *network) getGarageBootstrapHosts(
|
||||||
ctx context.Context,
|
ctx context.Context, currBootstrap bootstrap.Bootstrap,
|
||||||
logger *mlog.Logger,
|
|
||||||
secretsStore secrets.Store,
|
|
||||||
currBootstrap bootstrap.Bootstrap,
|
|
||||||
) (
|
) (
|
||||||
map[nebula.HostName]bootstrap.Host, error,
|
map[nebula.HostName]bootstrap.Host, error,
|
||||||
) {
|
) {
|
||||||
garageClientParams, err := getGarageClientParams(
|
garageClientParams, err := n.getGarageClientParams(ctx, currBootstrap)
|
||||||
ctx, secretsStore, currBootstrap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("getting garage client params: %w", err)
|
return nil, fmt.Errorf("getting garage client params: %w", err)
|
||||||
}
|
}
|
||||||
@ -185,8 +175,6 @@ func getGarageBootstrapHosts(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
for objInfo := range objInfoCh {
|
for objInfo := range objInfoCh {
|
||||||
|
|
||||||
ctx := mctx.Annotate(ctx, "objectKey", objInfo.Key)
|
ctx := mctx.Annotate(ctx, "objectKey", objInfo.Key)
|
||||||
@ -209,13 +197,13 @@ func getGarageBootstrapHosts(
|
|||||||
obj.Close()
|
obj.Close()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(ctx, "Object contains invalid json", err)
|
n.logger.Warn(ctx, "Object contains invalid json", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
host, err := authedHost.Unwrap(currBootstrap.CAPublicCredentials)
|
host, err := authedHost.Unwrap(currBootstrap.CAPublicCredentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(ctx, "Host could not be authenticated", err)
|
n.logger.Warn(ctx, "Host could not be authenticated", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hosts[host.Name] = host
|
hosts[host.Name] = host
|
||||||
@ -227,14 +215,10 @@ func getGarageBootstrapHosts(
|
|||||||
// putGarageBoostrapHost places the <hostname>.json.signed file for this host
|
// putGarageBoostrapHost places the <hostname>.json.signed file for this host
|
||||||
// into garage so that other hosts are able to see relevant configuration for
|
// into garage so that other hosts are able to see relevant configuration for
|
||||||
// it.
|
// it.
|
||||||
func putGarageBoostrapHost(
|
func (n *network) putGarageBoostrapHost(
|
||||||
ctx context.Context,
|
ctx context.Context, currBootstrap bootstrap.Bootstrap,
|
||||||
secretsStore secrets.Store,
|
|
||||||
currBootstrap bootstrap.Bootstrap,
|
|
||||||
) error {
|
) error {
|
||||||
garageClientParams, err := getGarageClientParams(
|
garageClientParams, err := n.getGarageClientParams(ctx, currBootstrap)
|
||||||
ctx, secretsStore, currBootstrap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("getting garage client params: %w", err)
|
return fmt.Errorf("getting garage client params: %w", err)
|
||||||
}
|
}
|
||||||
@ -244,8 +228,6 @@ func putGarageBoostrapHost(
|
|||||||
client = garageClientParams.GlobalBucketS3APIClient()
|
client = garageClientParams.GlobalBucketS3APIClient()
|
||||||
)
|
)
|
||||||
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
configured, err := nebula.Sign(
|
configured, err := nebula.Sign(
|
||||||
host.HostConfigured, currBootstrap.PrivateCredentials.SigningPrivateKey,
|
host.HostConfigured, currBootstrap.PrivateCredentials.SigningPrivateKey,
|
||||||
)
|
)
|
||||||
@ -283,7 +265,7 @@ func putGarageBoostrapHost(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func removeGarageBootstrapHost(
|
func removeGarageBootstrapHost(
|
||||||
ctx context.Context, client *garage.S3APIClient, hostName nebula.HostName,
|
ctx context.Context, client garage.S3APIClient, hostName nebula.HostName,
|
||||||
) error {
|
) error {
|
||||||
|
|
||||||
filePath := filepath.Join(
|
filePath := filepath.Join(
|
||||||
|
@ -39,7 +39,7 @@ type GarageClientParams struct {
|
|||||||
|
|
||||||
// GlobalBucketS3APIClient returns an S3 client pre-configured with access to
|
// GlobalBucketS3APIClient returns an S3 client pre-configured with access to
|
||||||
// the global bucket.
|
// the global bucket.
|
||||||
func (p GarageClientParams) GlobalBucketS3APIClient() *garage.S3APIClient {
|
func (p GarageClientParams) GlobalBucketS3APIClient() garage.S3APIClient {
|
||||||
var (
|
var (
|
||||||
addr = p.Peer.S3APIAddr()
|
addr = p.Peer.S3APIAddr()
|
||||||
creds = p.GlobalBucketS3APICredentials
|
creds = p.GlobalBucketS3APICredentials
|
||||||
@ -110,13 +110,6 @@ type RPC interface {
|
|||||||
) (
|
) (
|
||||||
nebula.Certificate, error,
|
nebula.Certificate, error,
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetConfig returns the configuration currently in use.
|
|
||||||
GetConfig(context.Context) (daecommon.NetworkConfig, error)
|
|
||||||
|
|
||||||
// SetConfig overrides the current config with the given one, adjusting any
|
|
||||||
// running child processes as needed.
|
|
||||||
SetConfig(context.Context, daecommon.NetworkConfig) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Network manages membership in a single micropelago network. Each Network
|
// Network manages membership in a single micropelago network. Each Network
|
||||||
@ -149,24 +142,18 @@ type Network interface {
|
|||||||
// Network instance. A nil Opts is equivalent to a zero value.
|
// Network instance. A nil Opts is equivalent to a zero value.
|
||||||
type Opts struct {
|
type Opts struct {
|
||||||
ChildrenOpts *children.Opts
|
ChildrenOpts *children.Opts
|
||||||
|
|
||||||
GarageAdminToken string // Will be randomly generated if left unset.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Opts) withDefaults() *Opts {
|
func (o *Opts) withDefaults() *Opts {
|
||||||
if o == nil {
|
if o == nil {
|
||||||
o = new(Opts)
|
o = new(Opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.GarageAdminToken == "" {
|
|
||||||
o.GarageAdminToken = toolkit.RandStr(32)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
type network struct {
|
type network struct {
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
|
networkConfig daecommon.NetworkConfig
|
||||||
|
|
||||||
envBinDirPath string
|
envBinDirPath string
|
||||||
stateDir toolkit.Dir
|
stateDir toolkit.Dir
|
||||||
@ -174,11 +161,11 @@ type network struct {
|
|||||||
|
|
||||||
opts *Opts
|
opts *Opts
|
||||||
|
|
||||||
secretsStore secrets.Store
|
secretsStore secrets.Store
|
||||||
|
garageAdminToken string
|
||||||
|
|
||||||
l sync.RWMutex
|
l sync.RWMutex
|
||||||
children *children.Children
|
children *children.Children
|
||||||
networkConfig daecommon.NetworkConfig
|
|
||||||
currBootstrap bootstrap.Bootstrap
|
currBootstrap bootstrap.Bootstrap
|
||||||
|
|
||||||
shutdownCh chan struct{}
|
shutdownCh chan struct{}
|
||||||
@ -189,6 +176,7 @@ type network struct {
|
|||||||
// been initialized.
|
// been initialized.
|
||||||
func instatiateNetwork(
|
func instatiateNetwork(
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
|
networkID string,
|
||||||
networkConfig daecommon.NetworkConfig,
|
networkConfig daecommon.NetworkConfig,
|
||||||
envBinDirPath string,
|
envBinDirPath string,
|
||||||
stateDir toolkit.Dir,
|
stateDir toolkit.Dir,
|
||||||
@ -196,13 +184,14 @@ func instatiateNetwork(
|
|||||||
opts *Opts,
|
opts *Opts,
|
||||||
) *network {
|
) *network {
|
||||||
return &network{
|
return &network{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
networkConfig: networkConfig,
|
networkConfig: networkConfig,
|
||||||
envBinDirPath: envBinDirPath,
|
envBinDirPath: envBinDirPath,
|
||||||
stateDir: stateDir,
|
stateDir: stateDir,
|
||||||
runtimeDir: runtimeDir,
|
runtimeDir: runtimeDir,
|
||||||
opts: opts.withDefaults(),
|
opts: opts.withDefaults(),
|
||||||
shutdownCh: make(chan struct{}),
|
garageAdminToken: toolkit.RandStr(32),
|
||||||
|
shutdownCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,6 +224,7 @@ func LoadCreationParams(
|
|||||||
func Load(
|
func Load(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
|
networkID string,
|
||||||
networkConfig daecommon.NetworkConfig,
|
networkConfig daecommon.NetworkConfig,
|
||||||
envBinDirPath string,
|
envBinDirPath string,
|
||||||
stateDir toolkit.Dir,
|
stateDir toolkit.Dir,
|
||||||
@ -245,6 +235,7 @@ func Load(
|
|||||||
) {
|
) {
|
||||||
n := instatiateNetwork(
|
n := instatiateNetwork(
|
||||||
logger,
|
logger,
|
||||||
|
networkID,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
envBinDirPath,
|
envBinDirPath,
|
||||||
stateDir,
|
stateDir,
|
||||||
@ -290,6 +281,7 @@ func Join(
|
|||||||
) {
|
) {
|
||||||
n := instatiateNetwork(
|
n := instatiateNetwork(
|
||||||
logger,
|
logger,
|
||||||
|
joiningBootstrap.Bootstrap.NetworkCreationParams.ID,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
envBinDirPath,
|
envBinDirPath,
|
||||||
stateDir,
|
stateDir,
|
||||||
@ -356,6 +348,7 @@ func Create(
|
|||||||
|
|
||||||
n := instatiateNetwork(
|
n := instatiateNetwork(
|
||||||
logger,
|
logger,
|
||||||
|
creationParams.ID,
|
||||||
networkConfig,
|
networkConfig,
|
||||||
envBinDirPath,
|
envBinDirPath,
|
||||||
stateDir,
|
stateDir,
|
||||||
@ -436,8 +429,8 @@ func (n *network) initialize(
|
|||||||
n.secretsStore,
|
n.secretsStore,
|
||||||
n.networkConfig,
|
n.networkConfig,
|
||||||
n.runtimeDir,
|
n.runtimeDir,
|
||||||
n.opts.GarageAdminToken,
|
n.garageAdminToken,
|
||||||
n.currBootstrap,
|
currBootstrap,
|
||||||
n.opts.ChildrenOpts,
|
n.opts.ChildrenOpts,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -452,16 +445,8 @@ func (n *network) initialize(
|
|||||||
return fmt.Errorf("performing post-initialization: %w", err)
|
return fmt.Errorf("performing post-initialization: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do this now so that everything is stable before returning. This also
|
// TODO annotate this context with creation params
|
||||||
// serves a dual-purpose, as it makes sure that the PUT from the postInit
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
// above has propagated from the local garage instance, if there is one.
|
|
||||||
n.logger.Info(ctx, "Reloading hosts from network storage")
|
|
||||||
if err = n.reloadHosts(ctx); err != nil {
|
|
||||||
return fmt.Errorf("Reloading network bootstrap: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = context.WithoutCancel(ctx)
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
n.wg.Add(1)
|
n.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer n.wg.Done()
|
defer n.wg.Done()
|
||||||
@ -473,7 +458,7 @@ func (n *network) initialize(
|
|||||||
go func() {
|
go func() {
|
||||||
defer n.wg.Done()
|
defer n.wg.Done()
|
||||||
n.reloadLoop(ctx)
|
n.reloadLoop(ctx)
|
||||||
n.logger.Debug(ctx, "Daemon reload loop stopped")
|
n.logger.Debug(ctx, "Daemon restart loop stopped")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -483,11 +468,7 @@ func (n *network) postInit(ctx context.Context) error {
|
|||||||
if len(n.networkConfig.Storage.Allocations) > 0 {
|
if len(n.networkConfig.Storage.Allocations) > 0 {
|
||||||
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.networkConfig, n.garageAdminToken, n.currBootstrap,
|
||||||
n.logger,
|
|
||||||
n.networkConfig,
|
|
||||||
n.opts.GarageAdminToken,
|
|
||||||
n.currBootstrap,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return fmt.Errorf("applying garage layout: %w", err)
|
return fmt.Errorf("applying garage layout: %w", err)
|
||||||
}
|
}
|
||||||
@ -507,7 +488,7 @@ func (n *network) postInit(ctx context.Context) error {
|
|||||||
ctx,
|
ctx,
|
||||||
n.logger,
|
n.logger,
|
||||||
n.networkConfig,
|
n.networkConfig,
|
||||||
n.opts.GarageAdminToken,
|
n.garageAdminToken,
|
||||||
n.currBootstrap,
|
n.currBootstrap,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -523,7 +504,7 @@ func (n *network) postInit(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
n.logger.Info(ctx, "Updating host info in garage")
|
n.logger.Info(ctx, "Updating host info in garage")
|
||||||
err = putGarageBoostrapHost(ctx, n.secretsStore, n.currBootstrap)
|
err = n.putGarageBoostrapHost(ctx, n.currBootstrap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("updating host info in garage: %w", err)
|
return fmt.Errorf("updating host info in garage: %w", err)
|
||||||
}
|
}
|
||||||
@ -531,39 +512,8 @@ func (n *network) postInit(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *network) reloadHosts(ctx context.Context) error {
|
|
||||||
n.l.RLock()
|
|
||||||
networkConfig := n.networkConfig
|
|
||||||
currBootstrap := n.currBootstrap
|
|
||||||
n.l.RUnlock()
|
|
||||||
|
|
||||||
n.logger.Info(ctx, "Checking for bootstrap changes")
|
|
||||||
newHosts, err := getGarageBootstrapHosts(
|
|
||||||
ctx, n.logger, n.secretsStore, currBootstrap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("getting hosts from garage: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO there's some potential race conditions here, where
|
|
||||||
// CreateHost could be called at this point, write the new host to
|
|
||||||
// garage and the bootstrap, but then this reload call removes the
|
|
||||||
// host from this bootstrap/children until the next reload.
|
|
||||||
|
|
||||||
newBootstrap := currBootstrap
|
|
||||||
newBootstrap.Hosts = newHosts
|
|
||||||
|
|
||||||
err = n.reload(ctx, networkConfig, newBootstrap)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reloading with new host data: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *network) reloadLoop(ctx context.Context) {
|
func (n *network) reloadLoop(ctx context.Context) {
|
||||||
const period = 3 * time.Minute
|
ticker := time.NewTicker(3 * time.Minute)
|
||||||
ticker := time.NewTicker(period)
|
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -572,44 +522,66 @@ func (n *network) reloadLoop(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if err := n.reloadHosts(ctx); err != nil {
|
n.l.RLock()
|
||||||
n.logger.Error(ctx, "Attempting to reload", err)
|
currBootstrap := n.currBootstrap
|
||||||
|
n.l.RUnlock()
|
||||||
|
|
||||||
|
n.logger.Info(ctx, "Checking for bootstrap changes")
|
||||||
|
newHosts, err := n.getGarageBootstrapHosts(ctx, currBootstrap)
|
||||||
|
if err != nil {
|
||||||
|
n.logger.Error(ctx, "Failed to get hosts from garage", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO there's some potential race conditions here, where
|
||||||
|
// CreateHost could be called at this point, write the new host to
|
||||||
|
// garage and the bootstrap, but then this reload call removes the
|
||||||
|
// host from this bootstrap/children until the next reload.
|
||||||
|
|
||||||
|
if err := n.reload(ctx, currBootstrap, newHosts); err != nil {
|
||||||
|
n.logger.Error(ctx, "Reloading with new host data failed", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reload will check the existing hosts data from currBootstrap against
|
// reload will check the existing hosts data from currBootstrap against a
|
||||||
// a potentially updated set of hosts data, and if there are any differences
|
// potentially updated set of hosts data, and if there are any differences will
|
||||||
// will perform whatever changes are necessary.
|
// perform whatever changes are necessary.
|
||||||
func (n *network) reload(
|
func (n *network) reload(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
newNetworkConfig daecommon.NetworkConfig,
|
currBootstrap bootstrap.Bootstrap,
|
||||||
newBootstrap bootstrap.Bootstrap,
|
newHosts map[nebula.HostName]bootstrap.Host,
|
||||||
) error {
|
) error {
|
||||||
n.l.Lock()
|
var (
|
||||||
defer n.l.Unlock()
|
newBootstrap = currBootstrap
|
||||||
|
thisHost = currBootstrap.ThisHost()
|
||||||
|
)
|
||||||
|
|
||||||
|
newBootstrap.Hosts = newHosts
|
||||||
|
|
||||||
// the daemon's view of this host's bootstrap info takes precedence over
|
// the daemon's view of this host's bootstrap info takes precedence over
|
||||||
// whatever is in garage. The garage version lacks the private credentials
|
// whatever is in garage
|
||||||
// which must be stored locally.
|
|
||||||
thisHost := n.currBootstrap.ThisHost()
|
|
||||||
newBootstrap.Hosts[thisHost.Name] = thisHost
|
newBootstrap.Hosts[thisHost.Name] = thisHost
|
||||||
|
|
||||||
n.logger.Info(ctx, "Writing updated bootstrap to state dir")
|
diff, err := children.CalculateReloadDiff(
|
||||||
err := writeBootstrapToStateDir(n.stateDir.Path, newBootstrap)
|
n.networkConfig, currBootstrap, newBootstrap,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("writing bootstrap to state dir: %w", err)
|
return fmt.Errorf("calculating diff between bootstraps: %w", err)
|
||||||
|
} else if diff == (children.ReloadDiff{}) {
|
||||||
|
n.logger.Info(ctx, "No changes to bootstrap detected")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n.networkConfig = newNetworkConfig
|
n.logger.Info(ctx, "Bootstrap has changed, storing new bootstrap")
|
||||||
|
n.l.Lock()
|
||||||
n.currBootstrap = newBootstrap
|
n.currBootstrap = newBootstrap
|
||||||
|
n.l.Unlock()
|
||||||
|
|
||||||
n.logger.Info(ctx, "Reloading child processes")
|
if err := n.children.Reload(ctx, newBootstrap, diff); err != nil {
|
||||||
err = n.children.Reload(ctx, newNetworkConfig, newBootstrap)
|
return fmt.Errorf("reloading child processes (diff:%+v): %w", diff, err)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reloading child processes: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -625,7 +597,9 @@ func withCurrBootstrap[Res any](
|
|||||||
return fn(currBootstrap)
|
return fn(currBootstrap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *network) getBootstrap() (
|
func (n *network) getBootstrap(
|
||||||
|
ctx context.Context,
|
||||||
|
) (
|
||||||
bootstrap.Bootstrap, error,
|
bootstrap.Bootstrap, error,
|
||||||
) {
|
) {
|
||||||
return withCurrBootstrap(n, func(
|
return withCurrBootstrap(n, func(
|
||||||
@ -638,17 +612,17 @@ func (n *network) getBootstrap() (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *network) GetHosts(ctx context.Context) ([]bootstrap.Host, error) {
|
func (n *network) GetHosts(ctx context.Context) ([]bootstrap.Host, error) {
|
||||||
return withCurrBootstrap(n, func(
|
b, err := n.getBootstrap(ctx)
|
||||||
currBootstrap bootstrap.Bootstrap,
|
if err != nil {
|
||||||
) (
|
return nil, fmt.Errorf("retrieving bootstrap: %w", err)
|
||||||
[]bootstrap.Host, error,
|
}
|
||||||
) {
|
|
||||||
hosts := maps.Values(currBootstrap.Hosts)
|
hosts := maps.Values(b.Hosts)
|
||||||
slices.SortFunc(hosts, func(a, b bootstrap.Host) int {
|
slices.SortFunc(hosts, func(a, b bootstrap.Host) int {
|
||||||
return cmp.Compare(a.Name, b.Name)
|
return cmp.Compare(a.Name, b.Name)
|
||||||
})
|
|
||||||
return hosts, nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return hosts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *network) GetGarageClientParams(
|
func (n *network) GetGarageClientParams(
|
||||||
@ -661,7 +635,7 @@ func (n *network) GetGarageClientParams(
|
|||||||
) (
|
) (
|
||||||
GarageClientParams, error,
|
GarageClientParams, error,
|
||||||
) {
|
) {
|
||||||
return getGarageClientParams(ctx, n.secretsStore, currBootstrap)
|
return n.getGarageClientParams(ctx, currBootstrap)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -670,7 +644,7 @@ func (n *network) GetNebulaCAPublicCredentials(
|
|||||||
) (
|
) (
|
||||||
nebula.CAPublicCredentials, error,
|
nebula.CAPublicCredentials, error,
|
||||||
) {
|
) {
|
||||||
b, err := n.getBootstrap()
|
b, err := n.getBootstrap(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nebula.CAPublicCredentials{}, fmt.Errorf(
|
return nebula.CAPublicCredentials{}, fmt.Errorf(
|
||||||
"retrieving bootstrap: %w", err,
|
"retrieving bootstrap: %w", err,
|
||||||
@ -688,16 +662,12 @@ func (n *network) RemoveHost(ctx context.Context, hostName nebula.HostName) erro
|
|||||||
) (
|
) (
|
||||||
struct{}, error,
|
struct{}, error,
|
||||||
) {
|
) {
|
||||||
garageClientParams, err := getGarageClientParams(
|
garageClientParams, err := n.getGarageClientParams(ctx, currBootstrap)
|
||||||
ctx, n.secretsStore, currBootstrap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, fmt.Errorf("get garage client params: %w", err)
|
return struct{}{}, fmt.Errorf("get garage client params: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := garageClientParams.GlobalBucketS3APIClient()
|
client := garageClientParams.GlobalBucketS3APIClient()
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
return struct{}{}, removeGarageBootstrapHost(ctx, client, hostName)
|
return struct{}{}, removeGarageBootstrapHost(ctx, client, hostName)
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
@ -800,7 +770,6 @@ func (n *network) CreateHost(
|
|||||||
JoiningBootstrap, error,
|
JoiningBootstrap, error,
|
||||||
) {
|
) {
|
||||||
n.l.RLock()
|
n.l.RLock()
|
||||||
networkConfig := n.networkConfig
|
|
||||||
currBootstrap := n.currBootstrap
|
currBootstrap := n.currBootstrap
|
||||||
n.l.RUnlock()
|
n.l.RUnlock()
|
||||||
|
|
||||||
@ -854,19 +823,17 @@ func (n *network) CreateHost(
|
|||||||
}
|
}
|
||||||
|
|
||||||
n.logger.Info(ctx, "Putting new host in garage")
|
n.logger.Info(ctx, "Putting new host in garage")
|
||||||
err = putGarageBoostrapHost(ctx, n.secretsStore, joiningBootstrap.Bootstrap)
|
err = n.putGarageBoostrapHost(ctx, joiningBootstrap.Bootstrap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JoiningBootstrap{}, fmt.Errorf("putting new host in garage: %w", err)
|
return JoiningBootstrap{}, fmt.Errorf("putting new host in garage: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the new bootstrap will have been initialized with both all existing hosts
|
// the new bootstrap will have been initialized with both all existing hosts
|
||||||
// (based on currBootstrap) and the host being created.
|
// (based on currBootstrap) and the host being created.
|
||||||
newBootstrap := currBootstrap
|
newHosts := joiningBootstrap.Bootstrap.Hosts
|
||||||
newBootstrap.Hosts = joiningBootstrap.Bootstrap.Hosts
|
|
||||||
|
|
||||||
n.logger.Info(ctx, "Reloading local state with new host")
|
n.logger.Info(ctx, "Reloading local state with new host")
|
||||||
err = n.reload(ctx, networkConfig, newBootstrap)
|
if err := n.reload(ctx, currBootstrap, newHosts); err != nil {
|
||||||
if err != nil {
|
|
||||||
return JoiningBootstrap{}, fmt.Errorf("reloading child processes: %w", err)
|
return JoiningBootstrap{}, fmt.Errorf("reloading child processes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -904,66 +871,12 @@ func (n *network) CreateNebulaCertificate(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *network) GetConfig(context.Context) (daecommon.NetworkConfig, error) {
|
|
||||||
n.l.RLock()
|
|
||||||
defer n.l.RUnlock()
|
|
||||||
return n.networkConfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *network) SetConfig(
|
|
||||||
ctx context.Context, config daecommon.NetworkConfig,
|
|
||||||
) error {
|
|
||||||
newBootstrap, err := coalesceNetworkConfigAndBootstrap(
|
|
||||||
config, n.currBootstrap,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("combining configuration into bootstrap: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.l.Lock()
|
|
||||||
defer n.l.Unlock()
|
|
||||||
|
|
||||||
n.logger.Info(ctx, "Shutting down children")
|
|
||||||
n.children.Shutdown()
|
|
||||||
|
|
||||||
err = writeBootstrapToStateDir(n.stateDir.Path, newBootstrap)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("writing bootstrap to state dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.networkConfig = config
|
|
||||||
n.currBootstrap = newBootstrap
|
|
||||||
|
|
||||||
n.logger.Info(ctx, "Creating child processes")
|
|
||||||
n.children, err = children.New(
|
|
||||||
ctx,
|
|
||||||
n.logger.WithNamespace("children"),
|
|
||||||
n.envBinDirPath,
|
|
||||||
n.secretsStore,
|
|
||||||
n.networkConfig,
|
|
||||||
n.runtimeDir,
|
|
||||||
n.opts.GarageAdminToken,
|
|
||||||
n.currBootstrap,
|
|
||||||
n.opts.ChildrenOpts,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("creating child processes: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n.logger.Info(ctx, "Child processes re-created")
|
|
||||||
|
|
||||||
if err := n.postInit(ctx); err != nil {
|
|
||||||
return fmt.Errorf("performing post-initialization: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *network) GetNetworkCreationParams(
|
func (n *network) GetNetworkCreationParams(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (
|
) (
|
||||||
bootstrap.CreationParams, error,
|
bootstrap.CreationParams, error,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
return withCurrBootstrap(n, func(
|
return withCurrBootstrap(n, func(
|
||||||
currBootstrap bootstrap.Bootstrap,
|
currBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"isle/bootstrap"
|
|
||||||
"isle/daemon/daecommon"
|
|
||||||
"isle/garage"
|
|
||||||
"isle/jsonutil"
|
|
||||||
"isle/nebula"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreate(t *testing.T) {
|
|
||||||
var (
|
|
||||||
h = newIntegrationHarness(t)
|
|
||||||
network = h.createNetwork(t, "primus", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
gotCreationParams, err := LoadCreationParams(network.stateDir)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, gotCreationParams, network.creationParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoad(t *testing.T) {
|
|
||||||
var (
|
|
||||||
h = newIntegrationHarness(t)
|
|
||||||
network = h.createNetwork(t, "primus", &createNetworkOpts{
|
|
||||||
manualShutdown: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Log("Shutting down network")
|
|
||||||
assert.NoError(t, network.Shutdown())
|
|
||||||
|
|
||||||
t.Log("Calling Load")
|
|
||||||
loadedNetwork, err := Load(
|
|
||||||
h.ctx,
|
|
||||||
h.logger.WithNamespace("loadedNetwork"),
|
|
||||||
network.networkConfig,
|
|
||||||
getEnvBinDirPath(),
|
|
||||||
network.stateDir,
|
|
||||||
h.mkDir(t, "runtime"),
|
|
||||||
network.opts,
|
|
||||||
)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
t.Log("Shutting down loadedNetwork")
|
|
||||||
assert.NoError(t, loadedNetwork.Shutdown())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestJoin(t *testing.T) {
|
|
||||||
var (
|
|
||||||
h = newIntegrationHarness(t)
|
|
||||||
primus = h.createNetwork(t, "primus", nil)
|
|
||||||
secondus = h.joinNetwork(t, primus, "secondus", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
primusHosts, err := primus.GetHosts(h.ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
secondusHosts, err := secondus.GetHosts(h.ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, primusHosts, secondusHosts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetwork_GetConfig(t *testing.T) {
|
|
||||||
var (
|
|
||||||
h = newIntegrationHarness(t)
|
|
||||||
network = h.createNetwork(t, "primus", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
config, err := network.GetConfig(h.ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, config, network.networkConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNetwork_SetConfig(t *testing.T) {
|
|
||||||
t.Run("adding storage alloc", func(t *testing.T) {
|
|
||||||
var (
|
|
||||||
h = newIntegrationHarness(t)
|
|
||||||
network = h.createNetwork(t, "primus", nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
network.networkConfig.Storage.Allocations = append(
|
|
||||||
network.networkConfig.Storage.Allocations,
|
|
||||||
daecommon.ConfigStorageAllocation{
|
|
||||||
DataPath: h.mkDir(t, "data").Path,
|
|
||||||
MetaPath: h.mkDir(t, "meta").Path,
|
|
||||||
Capacity: 1,
|
|
||||||
S3APIPort: 4900,
|
|
||||||
RPCPort: 4901,
|
|
||||||
AdminPort: 4902,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert.NoError(t, network.SetConfig(h.ctx, network.networkConfig))
|
|
||||||
|
|
||||||
// Check that the Host information was updated
|
|
||||||
newHosts, err := network.GetHosts(h.ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
newHostsByName := map[nebula.HostName]bootstrap.Host{}
|
|
||||||
for _, h := range newHosts {
|
|
||||||
newHostsByName[h.Name] = h
|
|
||||||
}
|
|
||||||
|
|
||||||
newHost, ok := newHostsByName[network.hostName]
|
|
||||||
assert.True(t, ok)
|
|
||||||
|
|
||||||
allocs := newHost.HostConfigured.Garage.Instances
|
|
||||||
assert.Len(t, allocs, 4)
|
|
||||||
|
|
||||||
newAlloc := allocs[3]
|
|
||||||
assert.NotEmpty(t, newAlloc.ID)
|
|
||||||
newAlloc.ID = ""
|
|
||||||
assert.Equal(t, bootstrap.GarageHostInstance{
|
|
||||||
S3APIPort: 4900,
|
|
||||||
RPCPort: 4901,
|
|
||||||
}, newAlloc)
|
|
||||||
|
|
||||||
// Check that the bootstrap file was written with the new host config
|
|
||||||
var storedBootstrap bootstrap.Bootstrap
|
|
||||||
assert.NoError(t, jsonutil.LoadFile(
|
|
||||||
&storedBootstrap, bootstrap.StateDirPath(network.stateDir.Path),
|
|
||||||
))
|
|
||||||
assert.Equal(t, newHostsByName, storedBootstrap.Hosts)
|
|
||||||
|
|
||||||
// Check that garage layout contains the new allocation
|
|
||||||
garageAdminClient := newGarageAdminClient(
|
|
||||||
h.logger,
|
|
||||||
network.networkConfig,
|
|
||||||
network.opts.GarageAdminToken,
|
|
||||||
storedBootstrap,
|
|
||||||
)
|
|
||||||
|
|
||||||
layout, err := garageAdminClient.GetLayout(h.ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
expPeers := make([]garage.PeerLayout, len(allocs))
|
|
||||||
for i := range allocs {
|
|
||||||
expPeers[i] = garage.PeerLayout{
|
|
||||||
ID: allocs[i].ID,
|
|
||||||
Capacity: 1_000_000_000,
|
|
||||||
Zone: string(network.hostName),
|
|
||||||
Tags: []string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.ElementsMatch(t, expPeers, layout.Peers)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,368 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"isle/bootstrap"
|
|
||||||
"isle/daemon/children"
|
|
||||||
"isle/daemon/daecommon"
|
|
||||||
"isle/nebula"
|
|
||||||
"isle/toolkit"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Utilities related to running network integration tests
|
|
||||||
|
|
||||||
var (
|
|
||||||
getEnvBinDirPath = sync.OnceValue(func() string {
|
|
||||||
appDirPath := os.Getenv("APPDIR")
|
|
||||||
if appDirPath == "" {
|
|
||||||
panic("APPDIR not set")
|
|
||||||
}
|
|
||||||
return filepath.Join(appDirPath, "bin")
|
|
||||||
})
|
|
||||||
|
|
||||||
ipNetCounter uint64 = 0
|
|
||||||
publicAddrPortCounter uint64 = 1024
|
|
||||||
tunDeviceCounter uint64 = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
func newIPNet() nebula.IPNet {
|
|
||||||
var (
|
|
||||||
ipNet nebula.IPNet
|
|
||||||
ipNetStr = fmt.Sprintf(
|
|
||||||
"172.16.%d.0/24", atomic.AddUint64(&ipNetCounter, 1),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := ipNet.UnmarshalText([]byte(ipNetStr)); err != nil {
|
|
||||||
panic(fmt.Sprintf("parsing IPNet from %q: %v", ipNetStr, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipNet
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPublicAddr() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"127.0.0.200:%d", atomic.AddUint64(&publicAddrPortCounter, 1),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTunDevice() string {
|
|
||||||
return fmt.Sprintf("isle-test-%d", atomic.AddUint64(&tunDeviceCounter, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
func mustParseNetworkConfigf(str string, args ...any) daecommon.NetworkConfig {
|
|
||||||
str = fmt.Sprintf(str, args...)
|
|
||||||
|
|
||||||
var networkConfig daecommon.NetworkConfig
|
|
||||||
if err := yaml.Unmarshal([]byte(str), &networkConfig); err != nil {
|
|
||||||
panic(fmt.Sprintf("parsing network config: %v", err))
|
|
||||||
}
|
|
||||||
return networkConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type integrationHarness struct {
|
|
||||||
ctx context.Context
|
|
||||||
logger *mlog.Logger
|
|
||||||
rootDir toolkit.Dir
|
|
||||||
dirCounter uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIntegrationHarness(t *testing.T) *integrationHarness {
|
|
||||||
t.Parallel()
|
|
||||||
toolkit.MarkIntegrationTest(t)
|
|
||||||
|
|
||||||
rootDir, err := os.MkdirTemp("", "isle-network-it-test.*")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Logf("Temporary test directory: %q", rootDir)
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if t.Failed() {
|
|
||||||
t.Logf("Temp directory for failed test not deleted: %q", rootDir)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("Deleting temp directory %q", rootDir)
|
|
||||||
assert.NoError(t, os.RemoveAll(rootDir))
|
|
||||||
})
|
|
||||||
|
|
||||||
return &integrationHarness{
|
|
||||||
ctx: context.Background(),
|
|
||||||
logger: mlog.NewLogger(&mlog.LoggerOpts{
|
|
||||||
MessageHandler: mlog.NewTestMessageHandler(t),
|
|
||||||
}),
|
|
||||||
rootDir: toolkit.Dir{Path: rootDir},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *integrationHarness) mkDir(t *testing.T, name string) toolkit.Dir {
|
|
||||||
fullName := fmt.Sprintf("%s-%d", name, atomic.AddUint64(&h.dirCounter, 1))
|
|
||||||
|
|
||||||
t.Logf("Creating directory %q", fullName)
|
|
||||||
d, err := h.rootDir.MkChildDir(fullName, false)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
type networkConfigOpts struct {
|
|
||||||
hasPublicAddr bool
|
|
||||||
numStorageAllocs int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *networkConfigOpts) withDefaults() *networkConfigOpts {
|
|
||||||
if o == nil {
|
|
||||||
o = new(networkConfigOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *integrationHarness) mkNetworkConfig(
|
|
||||||
t *testing.T,
|
|
||||||
opts *networkConfigOpts,
|
|
||||||
) daecommon.NetworkConfig {
|
|
||||||
if opts == nil {
|
|
||||||
opts = new(networkConfigOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
publicAddr := ""
|
|
||||||
if opts.hasPublicAddr {
|
|
||||||
publicAddr = newPublicAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
allocs := make([]map[string]any, opts.numStorageAllocs)
|
|
||||||
for i := range allocs {
|
|
||||||
allocs[i] = map[string]any{
|
|
||||||
"data_path": h.mkDir(t, "data").Path,
|
|
||||||
"meta_path": h.mkDir(t, "meta").Path,
|
|
||||||
"capacity": 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allocsJSON, err := json.Marshal(allocs)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
return mustParseNetworkConfigf(`
|
|
||||||
vpn:
|
|
||||||
public_addr: %q
|
|
||||||
tun:
|
|
||||||
device: %q
|
|
||||||
storage:
|
|
||||||
allocations: %s
|
|
||||||
`,
|
|
||||||
publicAddr,
|
|
||||||
newTunDevice(),
|
|
||||||
allocsJSON,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *integrationHarness) mkChildrenOpts(
|
|
||||||
t *testing.T, runtimeDir toolkit.Dir,
|
|
||||||
) *children.Opts {
|
|
||||||
var (
|
|
||||||
childrenLogFilePath = filepath.Join(runtimeDir.Path, "children.log")
|
|
||||||
childrenOpts children.Opts
|
|
||||||
)
|
|
||||||
|
|
||||||
childrenLogFile, err := os.Create(childrenLogFilePath)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
if os.Getenv("ISLE_INTEGRATION_TEST_CHILDREN_LOG_STDOUT") == "" {
|
|
||||||
childrenOpts = children.Opts{
|
|
||||||
Stdout: childrenLogFile,
|
|
||||||
Stderr: childrenLogFile,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
childrenOpts = children.Opts{
|
|
||||||
Stdout: io.MultiWriter(os.Stdout, childrenLogFile),
|
|
||||||
Stderr: io.MultiWriter(os.Stdout, childrenLogFile),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &childrenOpts
|
|
||||||
}
|
|
||||||
|
|
||||||
type createNetworkOpts struct {
|
|
||||||
creationParams bootstrap.CreationParams
|
|
||||||
manualShutdown bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *createNetworkOpts) withDefaults() *createNetworkOpts {
|
|
||||||
if o == nil {
|
|
||||||
o = new(createNetworkOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.creationParams == (bootstrap.CreationParams{}) {
|
|
||||||
o.creationParams = bootstrap.NewCreationParams("test", "test.localnet")
|
|
||||||
}
|
|
||||||
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
type integrationHarnessNetwork struct {
|
|
||||||
Network
|
|
||||||
|
|
||||||
hostName nebula.HostName
|
|
||||||
creationParams bootstrap.CreationParams
|
|
||||||
networkConfig daecommon.NetworkConfig
|
|
||||||
stateDir, runtimeDir toolkit.Dir
|
|
||||||
opts *Opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *integrationHarness) createNetwork(
|
|
||||||
t *testing.T,
|
|
||||||
hostNameStr string,
|
|
||||||
opts *createNetworkOpts,
|
|
||||||
) integrationHarnessNetwork {
|
|
||||||
t.Logf("Creating as %q", hostNameStr)
|
|
||||||
opts = opts.withDefaults()
|
|
||||||
|
|
||||||
var (
|
|
||||||
networkConfig = h.mkNetworkConfig(t, &networkConfigOpts{
|
|
||||||
hasPublicAddr: true,
|
|
||||||
numStorageAllocs: 3,
|
|
||||||
})
|
|
||||||
|
|
||||||
stateDir = h.mkDir(t, "state")
|
|
||||||
runtimeDir = h.mkDir(t, "runtime")
|
|
||||||
|
|
||||||
ipNet = newIPNet()
|
|
||||||
hostName = nebula.HostName(hostNameStr)
|
|
||||||
|
|
||||||
networkOpts = &Opts{
|
|
||||||
ChildrenOpts: h.mkChildrenOpts(t, runtimeDir),
|
|
||||||
GarageAdminToken: "admin_token",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
network, err := Create(
|
|
||||||
h.ctx,
|
|
||||||
h.logger.WithNamespace("network").WithNamespace(hostNameStr),
|
|
||||||
networkConfig,
|
|
||||||
getEnvBinDirPath(),
|
|
||||||
stateDir,
|
|
||||||
runtimeDir,
|
|
||||||
opts.creationParams,
|
|
||||||
ipNet,
|
|
||||||
hostName,
|
|
||||||
networkOpts,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("creating Network: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.manualShutdown {
|
|
||||||
t.Cleanup(func() {
|
|
||||||
t.Logf("Shutting down Network %q", hostNameStr)
|
|
||||||
if err := network.Shutdown(); err != nil {
|
|
||||||
t.Logf("Shutting down Network %q failed: %v", hostNameStr, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return integrationHarnessNetwork{
|
|
||||||
network,
|
|
||||||
hostName,
|
|
||||||
opts.creationParams,
|
|
||||||
networkConfig,
|
|
||||||
stateDir,
|
|
||||||
runtimeDir,
|
|
||||||
networkOpts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type joinNetworkOpts struct {
|
|
||||||
*networkConfigOpts
|
|
||||||
canCreateHosts bool
|
|
||||||
manualShutdown bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *joinNetworkOpts) withDefaults() *joinNetworkOpts {
|
|
||||||
if o == nil {
|
|
||||||
o = new(joinNetworkOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
o.networkConfigOpts = o.networkConfigOpts.withDefaults()
|
|
||||||
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *integrationHarness) joinNetwork(
|
|
||||||
t *testing.T,
|
|
||||||
network integrationHarnessNetwork,
|
|
||||||
hostNameStr string,
|
|
||||||
opts *joinNetworkOpts,
|
|
||||||
) integrationHarnessNetwork {
|
|
||||||
opts = opts.withDefaults()
|
|
||||||
hostName := nebula.HostName(hostNameStr)
|
|
||||||
|
|
||||||
t.Logf("Creating bootstrap for %q", hostNameStr)
|
|
||||||
joiningBootstrap, err := network.CreateHost(h.ctx, hostName, CreateHostOpts{
|
|
||||||
CanCreateHosts: opts.canCreateHosts,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("creating host joining bootstrap: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
networkConfig = h.mkNetworkConfig(t, opts.networkConfigOpts)
|
|
||||||
stateDir = h.mkDir(t, "state")
|
|
||||||
runtimeDir = h.mkDir(t, "runtime")
|
|
||||||
networkOpts = &Opts{
|
|
||||||
ChildrenOpts: h.mkChildrenOpts(t, runtimeDir),
|
|
||||||
GarageAdminToken: "admin_token",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
t.Logf("Joining as %q", hostNameStr)
|
|
||||||
joinedNetwork, err := Join(
|
|
||||||
h.ctx,
|
|
||||||
h.logger.WithNamespace("network").WithNamespace(hostNameStr),
|
|
||||||
networkConfig,
|
|
||||||
joiningBootstrap,
|
|
||||||
getEnvBinDirPath(),
|
|
||||||
stateDir,
|
|
||||||
runtimeDir,
|
|
||||||
networkOpts,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("joining network: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.manualShutdown {
|
|
||||||
t.Cleanup(func() {
|
|
||||||
t.Logf("Shutting down Network %q", hostNameStr)
|
|
||||||
if err := joinedNetwork.Shutdown(); err != nil {
|
|
||||||
t.Logf("Shutting down Network %q failed: %v", hostNameStr, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return integrationHarnessNetwork{
|
|
||||||
joinedNetwork,
|
|
||||||
hostName,
|
|
||||||
network.creationParams,
|
|
||||||
networkConfig,
|
|
||||||
stateDir,
|
|
||||||
runtimeDir,
|
|
||||||
networkOpts,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,9 @@
|
|||||||
package dnsmasq
|
package dnsmasq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"fmt"
|
||||||
"context"
|
"os"
|
||||||
"io"
|
|
||||||
"isle/toolkit"
|
|
||||||
"slices"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfDataHost describes a host which can be resolved by dnsmasq.
|
// ConfDataHost describes a host which can be resolved by dnsmasq.
|
||||||
@ -49,23 +44,22 @@ server={{ . }}
|
|||||||
`))
|
`))
|
||||||
|
|
||||||
// WriteConfFile renders a dnsmasq.conf using the given data to a new
|
// WriteConfFile renders a dnsmasq.conf using the given data to a new
|
||||||
// file at the given path, returning true if the file changed or didn't
|
// file at the given path.
|
||||||
// previously exist.
|
func WriteConfFile(path string, data ConfData) error {
|
||||||
func WriteConfFile(
|
|
||||||
ctx context.Context, logger *mlog.Logger, path string, data ConfData,
|
|
||||||
) (
|
|
||||||
bool, error,
|
|
||||||
) {
|
|
||||||
slices.SortFunc(data.Hosts, func(i, j ConfDataHost) int {
|
|
||||||
return cmp.Or(
|
|
||||||
cmp.Compare(i.IP, j.IP),
|
|
||||||
cmp.Compare(i.Name, j.Name),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return toolkit.WriteFileCheckChanged(
|
file, err := os.OpenFile(
|
||||||
ctx, logger, path, 0600, func(w io.Writer) error {
|
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0640,
|
||||||
return confTpl.Execute(w, data)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if err := confTpl.Execute(file, data); err != nil {
|
||||||
|
return fmt.Errorf("rendering template to file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,6 @@ type AdminClient struct {
|
|||||||
c *http.Client
|
c *http.Client
|
||||||
addr string
|
addr string
|
||||||
adminToken string
|
adminToken string
|
||||||
|
|
||||||
transport *http.Transport
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAdminClient initializes and returns an AdminClient which will use the
|
// NewAdminClient initializes and returns an AdminClient which will use the
|
||||||
@ -56,25 +54,16 @@ type AdminClient struct {
|
|||||||
//
|
//
|
||||||
// If Logger is nil then logs will be suppressed.
|
// If Logger is nil then logs will be suppressed.
|
||||||
func NewAdminClient(logger *mlog.Logger, addr, adminToken string) *AdminClient {
|
func NewAdminClient(logger *mlog.Logger, addr, adminToken string) *AdminClient {
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
||||||
|
|
||||||
return &AdminClient{
|
return &AdminClient{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
c: &http.Client{
|
c: &http.Client{
|
||||||
Transport: transport,
|
Transport: http.DefaultTransport.(*http.Transport).Clone(),
|
||||||
},
|
},
|
||||||
addr: addr,
|
addr: addr,
|
||||||
adminToken: adminToken,
|
adminToken: adminToken,
|
||||||
transport: transport,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close cleans up all resources held by the lient.
|
|
||||||
func (c *AdminClient) Close() error {
|
|
||||||
c.transport.CloseIdleConnections()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// do performs an HTTP request with the given method (GET, POST) and path, and
|
// do performs an HTTP request with the given method (GET, POST) and path, and
|
||||||
// using the json marshaling of the given body as the request body (unless body
|
// using the json marshaling of the given body as the request body (unless body
|
||||||
// is nil). It will JSON unmarshal the response into rcv, unless rcv is nil.
|
// is nil). It will JSON unmarshal the response into rcv, unless rcv is nil.
|
||||||
@ -273,23 +262,10 @@ func (c *AdminClient) GrantBucketPermissions(
|
|||||||
// PeerLayout describes the properties of a garage peer in the context of the
|
// PeerLayout describes the properties of a garage peer in the context of the
|
||||||
// layout of the cluster.
|
// layout of the cluster.
|
||||||
type PeerLayout struct {
|
type PeerLayout struct {
|
||||||
ID string `json:"id"`
|
ID string
|
||||||
Capacity int `json:"capacity"` // Gb (SI units)
|
Capacity int // Gb (SI units)
|
||||||
Zone string `json:"zone"`
|
Zone string
|
||||||
Tags []string `json:"tags"`
|
Tags []string
|
||||||
}
|
|
||||||
|
|
||||||
// ClusterLayout describes the layout of the cluster as a whole.
|
|
||||||
type ClusterLayout struct {
|
|
||||||
Peers []PeerLayout `json:"roles"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLayout returns the currently applied ClusterLayout.
|
|
||||||
func (c *AdminClient) GetLayout(ctx context.Context) (ClusterLayout, error) {
|
|
||||||
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/GetLayout
|
|
||||||
var res ClusterLayout
|
|
||||||
err := c.do(ctx, &res, "GET", "/v1/layout", nil)
|
|
||||||
return res, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyLayout modifies the layout of the garage cluster. Only layout of the
|
// ApplyLayout modifies the layout of the garage cluster. Only layout of the
|
||||||
@ -297,9 +273,21 @@ func (c *AdminClient) GetLayout(ctx context.Context) (ClusterLayout, error) {
|
|||||||
func (c *AdminClient) ApplyLayout(
|
func (c *AdminClient) ApplyLayout(
|
||||||
ctx context.Context, peers []PeerLayout,
|
ctx context.Context, peers []PeerLayout,
|
||||||
) error {
|
) error {
|
||||||
|
type peerLayout struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Capacity int `json:"capacity"`
|
||||||
|
Zone string `json:"zone"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/ApplyLayout
|
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/ApplyLayout
|
||||||
err := c.do(ctx, nil, "POST", "/v1/layout", peers)
|
clusterLayout := make([]peerLayout, len(peers))
|
||||||
|
for i := range peers {
|
||||||
|
clusterLayout[i] = peerLayout(peers[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.do(ctx, nil, "POST", "/v1/layout", clusterLayout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("staging layout changes: %w", err)
|
return fmt.Errorf("staging layout changes: %w", err)
|
||||||
}
|
}
|
||||||
@ -308,7 +296,7 @@ func (c *AdminClient) ApplyLayout(
|
|||||||
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/GetLayout
|
// https://garagehq.deuxfleurs.fr/api/garage-admin-v1.html#tag/Layout/operation/GetLayout
|
||||||
var clusterLayout struct {
|
var clusterLayout struct {
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
StagedRoleChanges []PeerLayout `json:"stagedRoleChanges"`
|
StagedRoleChanges []peerLayout `json:"stagedRoleChanges"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.do(ctx, &clusterLayout, "GET", "/v1/layout", nil); err != nil {
|
if err := c.do(ctx, &clusterLayout, "GET", "/v1/layout", nil); err != nil {
|
||||||
|
@ -3,7 +3,6 @@ package garage
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
@ -17,16 +16,7 @@ func IsKeyNotFound(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// S3APIClient is a client used to interact with garage's S3 API.
|
// S3APIClient is a client used to interact with garage's S3 API.
|
||||||
type S3APIClient struct {
|
type S3APIClient = *minio.Client
|
||||||
*minio.Client
|
|
||||||
transport *http.Transport
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close cleans up all resources held by the client.
|
|
||||||
func (c *S3APIClient) Close() error {
|
|
||||||
c.transport.CloseIdleConnections()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// S3APICredentials describe data fields necessary for authenticating with a
|
// S3APICredentials describe data fields necessary for authenticating with a
|
||||||
// garage S3 API endpoint.
|
// garage S3 API endpoint.
|
||||||
@ -37,21 +27,16 @@ type S3APICredentials struct {
|
|||||||
|
|
||||||
// NewS3APIClient returns a minio client configured to use the given garage S3 API
|
// NewS3APIClient returns a minio client configured to use the given garage S3 API
|
||||||
// endpoint.
|
// endpoint.
|
||||||
func NewS3APIClient(addr string, creds S3APICredentials) *S3APIClient {
|
func NewS3APIClient(addr string, creds S3APICredentials) S3APIClient {
|
||||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
||||||
|
|
||||||
client, err := minio.New(addr, &minio.Options{
|
client, err := minio.New(addr, &minio.Options{
|
||||||
Creds: credentials.NewStaticV4(creds.ID, creds.Secret, ""),
|
Creds: credentials.NewStaticV4(creds.ID, creds.Secret, ""),
|
||||||
Region: Region,
|
Region: Region,
|
||||||
Transport: transport,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("initializing minio client at addr %q and with creds %+v", addr, creds))
|
panic(fmt.Sprintf("initializing minio client at addr %q and with creds %+v", addr, creds))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &S3APIClient{
|
return client
|
||||||
Client: client,
|
|
||||||
transport: transport,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
package garagesrv
|
package garagesrv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"fmt"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"isle/garage"
|
"isle/garage"
|
||||||
"isle/toolkit"
|
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GarageTomlData describes all fields needed for rendering a garage.toml
|
// GarageTomlData describes all fields needed for rendering a garage.toml
|
||||||
@ -28,6 +24,7 @@ type GarageTomlData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var garageTomlTpl = template.Must(template.New("").Parse(`
|
var garageTomlTpl = template.Must(template.New("").Parse(`
|
||||||
|
|
||||||
metadata_dir = "{{ .MetaPath }}"
|
metadata_dir = "{{ .MetaPath }}"
|
||||||
data_dir = "{{ .DataPath }}"
|
data_dir = "{{ .DataPath }}"
|
||||||
|
|
||||||
@ -48,6 +45,7 @@ s3_region = "garage"
|
|||||||
[admin]
|
[admin]
|
||||||
api_bind_addr = "{{ .AdminAddr }}"
|
api_bind_addr = "{{ .AdminAddr }}"
|
||||||
admin_token = "{{ .AdminToken }}"
|
admin_token = "{{ .AdminToken }}"
|
||||||
|
|
||||||
`))
|
`))
|
||||||
|
|
||||||
// RenderGarageToml renders a garage.toml using the given data into the writer.
|
// RenderGarageToml renders a garage.toml using the given data into the writer.
|
||||||
@ -56,26 +54,22 @@ func RenderGarageToml(into io.Writer, data GarageTomlData) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WriteGarageTomlFile renders a garage.toml using the given data to a new file
|
// WriteGarageTomlFile renders a garage.toml using the given data to a new file
|
||||||
// at the given path, returning true if the file changed or didn't
|
// at the given path.
|
||||||
// previously exist.
|
func WriteGarageTomlFile(path string, data GarageTomlData) error {
|
||||||
func WriteGarageTomlFile(
|
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
|
||||||
path string,
|
|
||||||
data GarageTomlData,
|
|
||||||
) (
|
|
||||||
bool, error,
|
|
||||||
) {
|
|
||||||
slices.SortFunc(data.BootstrapPeers, func(i, j garage.RemotePeer) int {
|
|
||||||
return cmp.Or(
|
|
||||||
cmp.Compare(i.IP, j.IP),
|
|
||||||
cmp.Compare(i.RPCPort, j.RPCPort),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return toolkit.WriteFileCheckChanged(
|
file, err := os.OpenFile(
|
||||||
ctx, logger, path, 0600, func(w io.Writer) error {
|
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600,
|
||||||
return garageTomlTpl.Execute(w, data)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
if err := garageTomlTpl.Execute(file, data); err != nil {
|
||||||
|
return fmt.Errorf("rendering template to file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,19 @@ go 1.22
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4
|
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4
|
||||||
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233
|
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20240511135822-4ab1176672d7
|
||||||
github.com/adrg/xdg v0.4.0
|
github.com/adrg/xdg v0.4.0
|
||||||
|
github.com/imdario/mergo v0.3.12
|
||||||
github.com/jxskiss/base62 v1.1.0
|
github.com/jxskiss/base62 v1.1.0
|
||||||
github.com/minio/minio-go/v7 v7.0.28
|
github.com/minio/minio-go/v7 v7.0.28
|
||||||
github.com/sergi/go-diff v1.3.1
|
|
||||||
github.com/slackhq/nebula v1.6.1
|
github.com/slackhq/nebula v1.6.1
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.9.0
|
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c
|
||||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.0 // indirect
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/google/uuid v1.1.1 // indirect
|
github.com/google/uuid v1.1.1 // indirect
|
||||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
@ -31,7 +29,6 @@ require (
|
|||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
|
||||||
github.com/rs/xid v1.2.1 // indirect
|
github.com/rs/xid v1.2.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
github.com/smartystreets/assertions v1.13.0 // indirect
|
github.com/smartystreets/assertions v1.13.0 // indirect
|
||||||
|
18
go/go.sum
18
go/go.sum
@ -1,7 +1,7 @@
|
|||||||
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4 h1:n4pGP12kgdH5kCTF4TZYpOjWuAR/zZLpM9j3neeVMEk=
|
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4 h1:n4pGP12kgdH5kCTF4TZYpOjWuAR/zZLpM9j3neeVMEk=
|
||||||
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4/go.mod h1:WlEWacLREVfIQl1IlBjKzuDgL56DFRvyl7YiL5gGP4w=
|
code.betamike.com/micropelago/pmux v0.0.0-20240719134913-f5fce902e8c4/go.mod h1:WlEWacLREVfIQl1IlBjKzuDgL56DFRvyl7YiL5gGP4w=
|
||||||
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233 h1:Ea4HixNfDNDPh7zMngPpEeDf8gpociSPEROBFBedqIY=
|
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20240511135822-4ab1176672d7 h1:wKQ3bXzG+KQDtRAN/xaRZ4aQtJe1pccleG6V43MvFxw=
|
||||||
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20241023182613-55984cdf5233/go.mod h1:nP+AtQWrc3k5qq5y3ABiBLkOfUPlk/FO9fpTFpF+jgs=
|
dev.mediocregopher.com/mediocre-go-lib.git v0.0.0-20240511135822-4ab1176672d7/go.mod h1:nP+AtQWrc3k5qq5y3ABiBLkOfUPlk/FO9fpTFpF+jgs=
|
||||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -18,6 +18,8 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
|||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
|
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||||
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
@ -29,7 +31,6 @@ github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e
|
|||||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@ -52,8 +53,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
|
||||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
|
||||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
|
github.com/slackhq/nebula v1.6.1 h1:/OCTR3abj0Sbf2nGoLUrdDXImrCv0ZVFpVPP5qa0DsM=
|
||||||
@ -67,10 +66,9 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c h1:u6SKchux2yDvFQnDHS3lPnIRmfVJ5Sxy3ao2SIdysLQ=
|
||||||
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
|
github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM=
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
|
||||||
@ -90,12 +88,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
|||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
|
||||||
cd "$SCRIPT_DIR" || exit
|
|
||||||
|
|
||||||
this_user="$(whoami)"
|
|
||||||
|
|
||||||
nix-shell -A testShell ../default.nix --run "
|
|
||||||
echo \"Requesting sudo in order to set thread capabilities, will drop back down to user '$this_user' immediately\"
|
|
||||||
sudo -E capsh \\
|
|
||||||
--caps=\"cap_net_admin,cap_net_bind_service+eip cap_setpcap,cap_setuid,cap_setgid+ep\" \\
|
|
||||||
--keep=1 \\
|
|
||||||
--user=\"$this_user\" \\
|
|
||||||
--addamb=cap_net_admin \\
|
|
||||||
--addamb=cap_net_bind_service \\
|
|
||||||
-- -c 'ISLE_INTEGRATION_TEST=1 go test $*'
|
|
||||||
"
|
|
@ -1,35 +1,13 @@
|
|||||||
package nebula
|
package nebula
|
||||||
|
|
||||||
import (
|
import "github.com/slackhq/nebula/cert"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/slackhq/nebula/cert"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Certificate wraps a NebulaCertificate to provide convenient (and consistent)
|
// Certificate wraps a NebulaCertificate to provide convenient (and consistent)
|
||||||
// text (un)marshaling methods as well as normalization for equality checking.
|
// text (un)marshaling methods.
|
||||||
type Certificate struct {
|
type Certificate struct {
|
||||||
inner cert.NebulaCertificate
|
inner cert.NebulaCertificate
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificate returns a Certificate wrapping the given one.
|
|
||||||
func NewCertificate(inner cert.NebulaCertificate) (Certificate, error) {
|
|
||||||
// normalize the inner cert by marshaling to and unmarshaling from the PEM.
|
|
||||||
// This allows equality checking in tests to work between certs which have
|
|
||||||
// never been written to disk and those which have.
|
|
||||||
b, err := inner.MarshalToPEM()
|
|
||||||
if err != nil {
|
|
||||||
return Certificate{}, fmt.Errorf("marshaling to PEM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
normInner, _, err := cert.UnmarshalNebulaCertificateFromPEM(b)
|
|
||||||
if err != nil {
|
|
||||||
return Certificate{}, fmt.Errorf("unmarshaling from PEM: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Certificate{inner: *normInner}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap returns the wrapped NebulaCertificate type.
|
// Unwrap returns the wrapped NebulaCertificate type.
|
||||||
func (c Certificate) Unwrap() *cert.NebulaCertificate {
|
func (c Certificate) Unwrap() *cert.NebulaCertificate {
|
||||||
return &c.inner
|
return &c.inner
|
||||||
|
@ -90,12 +90,7 @@ func NewHostCert(
|
|||||||
return Certificate{}, fmt.Errorf("signing host cert with ca.key: %w", err)
|
return Certificate{}, fmt.Errorf("signing host cert with ca.key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := NewCertificate(hostCert)
|
return Certificate{hostCert}, nil
|
||||||
if err != nil {
|
|
||||||
return Certificate{}, fmt.Errorf("wrapping cert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHostCredentials generates a new key/cert for a nebula host using the CA
|
// NewHostCredentials generates a new key/cert for a nebula host using the CA
|
||||||
@ -141,7 +136,7 @@ func NewCACredentials(domain string, subnet IPNet) (CACredentials, error) {
|
|||||||
expireAt = now.Add(2 * 365 * 24 * time.Hour)
|
expireAt = now.Add(2 * 365 * 24 * time.Hour)
|
||||||
)
|
)
|
||||||
|
|
||||||
caCertInner := cert.NebulaCertificate{
|
caCert := cert.NebulaCertificate{
|
||||||
Details: cert.NebulaCertificateDetails{
|
Details: cert.NebulaCertificateDetails{
|
||||||
Name: fmt.Sprintf("%s isle root cert", domain),
|
Name: fmt.Sprintf("%s isle root cert", domain),
|
||||||
Subnets: []*net.IPNet{(*net.IPNet)(&subnet)},
|
Subnets: []*net.IPNet{(*net.IPNet)(&subnet)},
|
||||||
@ -152,18 +147,13 @@ func NewCACredentials(domain string, subnet IPNet) (CACredentials, error) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := signCert(&caCertInner, signingPrivKey); err != nil {
|
if err := signCert(&caCert, signingPrivKey); err != nil {
|
||||||
return CACredentials{}, fmt.Errorf("signing caCert: %w", err)
|
return CACredentials{}, fmt.Errorf("signing caCert: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
caCert, err := NewCertificate(caCertInner)
|
|
||||||
if err != nil {
|
|
||||||
return CACredentials{}, fmt.Errorf("wrapping caCert: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CACredentials{
|
return CACredentials{
|
||||||
Public: CAPublicCredentials{
|
Public: CAPublicCredentials{
|
||||||
Cert: caCert,
|
Cert: Certificate{caCert},
|
||||||
SigningKey: signingPubKey,
|
SigningKey: signingPubKey,
|
||||||
},
|
},
|
||||||
SigningPrivateKey: signingPrivKey,
|
SigningPrivateKey: signingPrivKey,
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
package toolkit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/sha256"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
||||||
"github.com/sergi/go-diff/diffmatchpatch"
|
|
||||||
)
|
|
||||||
|
|
||||||
var differ = diffmatchpatch.New()
|
|
||||||
|
|
||||||
// WriteFileCheckChanged uses the given callback to collect data to be written
|
|
||||||
// to the given filepath with the given permission bits set. If the file's
|
|
||||||
// contents have been modified (or if the file didn't previously exist) then
|
|
||||||
// true is returned.
|
|
||||||
func WriteFileCheckChanged(
|
|
||||||
ctx context.Context,
|
|
||||||
logger *mlog.Logger,
|
|
||||||
path string,
|
|
||||||
mode os.FileMode,
|
|
||||||
fn func(io.Writer) error,
|
|
||||||
) (
|
|
||||||
bool, error,
|
|
||||||
) {
|
|
||||||
var (
|
|
||||||
buf = new(bytes.Buffer)
|
|
||||||
h = sha256.New()
|
|
||||||
w = io.MultiWriter(buf, h)
|
|
||||||
|
|
||||||
changed bool
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := fn(w); err != nil {
|
|
||||||
return false, fmt.Errorf("callback returned: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
changed = true
|
|
||||||
// fine, we'll just write the file
|
|
||||||
} else if err != nil {
|
|
||||||
return false, fmt.Errorf("reading contents of existing file: %w", err)
|
|
||||||
} else {
|
|
||||||
var (
|
|
||||||
existingBuf = new(bytes.Buffer)
|
|
||||||
existingH = sha256.New()
|
|
||||||
existingW = io.MultiWriter(existingBuf, existingH)
|
|
||||||
_, copyErr = io.Copy(existingW, f)
|
|
||||||
closeErr = f.Close()
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := errors.Join(closeErr, copyErr); err != nil {
|
|
||||||
return false, fmt.Errorf("hashing existing file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changed = !bytes.Equal(h.Sum(nil), existingH.Sum(nil))
|
|
||||||
|
|
||||||
if changed && logger.MaxLevel() >= mlog.LevelDebug.Int() {
|
|
||||||
var (
|
|
||||||
ctx = mctx.Annotate(ctx, "path", path)
|
|
||||||
diff = differ.DiffPrettyText(
|
|
||||||
differ.DiffMain(existingBuf.String(), buf.String(), false),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.Debug(ctx, "WriteFileCheckChanged diff:\n"+diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if changed {
|
|
||||||
if err := os.WriteFile(path, buf.Bytes(), mode); err != nil {
|
|
||||||
return false, fmt.Errorf("writing file: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed, nil
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package toolkit
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MarkIntegrationTest marks a test as being an integration test. It will be
|
|
||||||
// skipped if the ISLE_INTEGRATION_TEST envvar isn't set.
|
|
||||||
func MarkIntegrationTest(t *testing.T) {
|
|
||||||
if os.Getenv("ISLE_INTEGRATION_TEST") == "" {
|
|
||||||
t.Skip("Skipped because ISLE_INTEGRATION_TEST isn't set")
|
|
||||||
}
|
|
||||||
}
|
|
23
shell.nix
23
shell.nix
@ -1 +1,22 @@
|
|||||||
{}@args: ((import ./default.nix) args).devShell
|
{
|
||||||
|
|
||||||
|
buildSystem ? builtins.currentSystem,
|
||||||
|
pkgsNix ? (import ./nix/pkgs.nix),
|
||||||
|
|
||||||
|
}: let
|
||||||
|
|
||||||
|
pkgs = pkgsNix.default {
|
||||||
|
inherit buildSystem;
|
||||||
|
hostSystem = buildSystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
in pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.go
|
||||||
|
pkgs.golangci-lint
|
||||||
|
(pkgs.callPackage ./nix/gowrap.nix {})
|
||||||
|
];
|
||||||
|
shellHook = ''
|
||||||
|
true # placeholder
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user