455 lines
10 KiB
Go
455 lines
10 KiB
Go
//go:generate mockery --name Loader --inpackage --filename loader_mock.go
|
|
|
|
package network
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"isle/bootstrap"
|
|
"isle/daemon/children"
|
|
"isle/daemon/daecommon"
|
|
"isle/nebula"
|
|
"isle/toolkit"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
)
|
|
|
|
func networkStateDir(
|
|
networksStateDir toolkit.Dir, networkID string, mayExist bool,
|
|
) (
|
|
toolkit.Dir, error,
|
|
) {
|
|
return networksStateDir.MkChildDir(networkID, mayExist)
|
|
}
|
|
|
|
func networkRuntimeDir(
|
|
networksRuntimeDir toolkit.Dir, networkID string, mayExist bool,
|
|
) (
|
|
toolkit.Dir, error,
|
|
) {
|
|
return networksRuntimeDir.MkChildDir(networkID, mayExist)
|
|
}
|
|
|
|
func cleanupDirs(
|
|
networkStateDir, networkRuntimeDir toolkit.Dir,
|
|
) error {
|
|
var errs []error
|
|
|
|
if err := os.RemoveAll(networkStateDir.Path); err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"removing %q: %w", networkStateDir.Path, err,
|
|
))
|
|
}
|
|
|
|
if err := os.RemoveAll(networkRuntimeDir.Path); err != nil {
|
|
errs = append(errs, fmt.Errorf(
|
|
"removing %q: %w", networkRuntimeDir.Path, err,
|
|
))
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
func networkDirs(
|
|
networksStateDir, networksRuntimeDir toolkit.Dir,
|
|
networkID string,
|
|
mayExist bool,
|
|
) (
|
|
stateDir, runtimeDir toolkit.Dir, err error,
|
|
) {
|
|
h := new(toolkit.MkDirHelper)
|
|
stateDir, _ = h.Maybe(
|
|
networkStateDir(networksStateDir, networkID, mayExist),
|
|
)
|
|
runtimeDir, _ = h.Maybe(
|
|
networkRuntimeDir(networksRuntimeDir, networkID, mayExist),
|
|
)
|
|
err = h.Err()
|
|
return
|
|
}
|
|
|
|
// Loader is responsible for joining/creating Networks and making them loadable
|
|
// later.
|
|
type Loader interface {
|
|
|
|
// Loadable returns the CreationParams for all Networks which can be Loaded.
|
|
Loadable(context.Context) ([]bootstrap.CreationParams, error)
|
|
|
|
// StoredConfig returns the NetworkConfig currently stored for the network
|
|
// with the given ID, or the default NetworkConfig if none is stored.
|
|
StoredConfig(
|
|
ctx context.Context, networkID string,
|
|
) (
|
|
daecommon.NetworkConfig, error,
|
|
)
|
|
|
|
// Load initializes and returns a Network instance for a network which was
|
|
// previously joined or created, and which has the given CreationParams.
|
|
Load(
|
|
context.Context,
|
|
*mlog.Logger,
|
|
bootstrap.CreationParams,
|
|
*Opts,
|
|
) (
|
|
Network, error,
|
|
)
|
|
|
|
// Join initializes and returns a Network instance for an existing network
|
|
// which was not previously joined to on this host. Once Join has been
|
|
// called for a particular network it will error on subsequent calls for
|
|
// that same network, Load should be used instead.
|
|
Join(
|
|
context.Context,
|
|
*mlog.Logger,
|
|
JoiningBootstrap,
|
|
*Opts,
|
|
) (
|
|
Network, error,
|
|
)
|
|
|
|
// Create initializes and returns a Network for a brand new network which
|
|
// uses the given creation parameters.
|
|
//
|
|
// - name: Human-readable name of the network.
|
|
// - domain: Primary domain name that network services are served under.
|
|
// - ipNet: An IP subnet, in CIDR form, which will be the overall range of
|
|
// possible IPs in the network. The first IP in this network range will
|
|
// become this first host's IP.
|
|
// - hostName: The name of this first host in the network.
|
|
//
|
|
// Errors:
|
|
// - ErrInvalidConfig - If the Opts.Config field is not valid. It must be
|
|
// non-nil and have at least 3 storage allocations.
|
|
Create(
|
|
context.Context,
|
|
*mlog.Logger,
|
|
bootstrap.CreationParams,
|
|
nebula.IPNet,
|
|
nebula.HostName,
|
|
*Opts,
|
|
) (
|
|
Network, error,
|
|
)
|
|
|
|
// Leave marks a previously loadable Network as being no longer loadable.
|
|
Leave(context.Context, bootstrap.CreationParams) error
|
|
}
|
|
|
|
// LoaderOpts are optional parameters which can be passed in when initializing a
|
|
// new Loader instance. A nil LoaderOpts is equivalent to a zero value.
|
|
type LoaderOpts struct {
|
|
// Defaults to that returned by daecommon.GetEnvVars.
|
|
EnvVars daecommon.EnvVars
|
|
|
|
constructors constructors // defaults to newConstructors()
|
|
nowFunc func() time.Time // defaults to time.Now
|
|
}
|
|
|
|
func (o *LoaderOpts) withDefaults() *LoaderOpts {
|
|
if o == nil {
|
|
o = new(LoaderOpts)
|
|
}
|
|
|
|
if o.EnvVars == (daecommon.EnvVars{}) {
|
|
o.EnvVars = daecommon.GetEnvVars()
|
|
}
|
|
|
|
if o.constructors == nil {
|
|
o.constructors = newConstructors()
|
|
}
|
|
|
|
if o.nowFunc == nil {
|
|
o.nowFunc = time.Now
|
|
}
|
|
|
|
return o
|
|
}
|
|
|
|
type loader struct {
|
|
opts *LoaderOpts
|
|
envBinDirPath string
|
|
networksStateDir toolkit.Dir
|
|
networksRuntimeDir toolkit.Dir
|
|
nebulaDeviceNamer *children.NebulaDeviceNamer
|
|
}
|
|
|
|
// NewLoader returns a new Loader which will use the given directories to load
|
|
// and create Network instances.
|
|
func NewLoader(
|
|
ctx context.Context,
|
|
logger *mlog.Logger,
|
|
envBinDirPath string,
|
|
opts *LoaderOpts,
|
|
) (
|
|
Loader, error,
|
|
) {
|
|
opts = opts.withDefaults()
|
|
|
|
if err := migrateToMultiNetworkStateDirectory(
|
|
ctx,
|
|
logger.WithNamespace("migration-multi-network-state-dir"),
|
|
opts.EnvVars.StateDir,
|
|
); err != nil {
|
|
return nil, fmt.Errorf(
|
|
"migrating to multi-network state directory: %w", err,
|
|
)
|
|
}
|
|
|
|
h := new(toolkit.MkDirHelper)
|
|
networksStateDir, _ := h.Maybe(
|
|
opts.EnvVars.StateDir.MkChildDir("networks", true),
|
|
)
|
|
|
|
networksRuntimeDir, _ := h.Maybe(
|
|
opts.EnvVars.RuntimeDir.MkChildDir("networks", true),
|
|
)
|
|
|
|
if err := h.Err(); err != nil {
|
|
return nil, fmt.Errorf("creating networks sub-directories: %w", err)
|
|
}
|
|
|
|
return &loader{
|
|
opts,
|
|
envBinDirPath,
|
|
networksStateDir,
|
|
networksRuntimeDir,
|
|
children.NewNebulaDeviceNamer(),
|
|
}, nil
|
|
}
|
|
|
|
func (l *loader) Loadable(
|
|
ctx context.Context,
|
|
) (
|
|
[]bootstrap.CreationParams, error,
|
|
) {
|
|
networkStateDirs, err := l.networksStateDir.ChildDirs()
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"listing children of %q: %w", l.networksStateDir.Path, err,
|
|
)
|
|
}
|
|
|
|
creationParams := make([]bootstrap.CreationParams, 0, len(networkStateDirs))
|
|
|
|
for _, networkStateDir := range networkStateDirs {
|
|
if n := filepath.Base(networkStateDir.Path); strings.HasPrefix(n, ".") {
|
|
continue
|
|
}
|
|
|
|
thisCreationParams, err := loadCreationParams(networkStateDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"loading creation params from %q: %w",
|
|
networkStateDir.Path,
|
|
err,
|
|
)
|
|
}
|
|
creationParams = append(creationParams, thisCreationParams)
|
|
}
|
|
|
|
return creationParams, nil
|
|
}
|
|
|
|
func (l *loader) StoredConfig(
|
|
_ context.Context, networkID string,
|
|
) (
|
|
daecommon.NetworkConfig, error,
|
|
) {
|
|
networkStateDir, err := networkStateDir(l.networksStateDir, networkID, true)
|
|
if err != nil {
|
|
return daecommon.NetworkConfig{}, fmt.Errorf(
|
|
"getting network state dir: %w", err,
|
|
)
|
|
}
|
|
|
|
return loadConfig(networkStateDir)
|
|
}
|
|
|
|
func (l *loader) isJoined(ctx context.Context, networkID string) (bool, error) {
|
|
allJoinedCreationParams, err := l.Loadable(ctx)
|
|
if err != nil {
|
|
return false, fmt.Errorf("getting already joined networks: %w", err)
|
|
}
|
|
|
|
for _, joinedCreationParams := range allJoinedCreationParams {
|
|
if joinedCreationParams.ID == networkID {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (l *loader) Load(
|
|
ctx context.Context,
|
|
logger *mlog.Logger,
|
|
creationParams bootstrap.CreationParams,
|
|
opts *Opts,
|
|
) (
|
|
Network, error,
|
|
) {
|
|
networkID := creationParams.ID
|
|
|
|
if isJoined, err := l.isJoined(ctx, networkID); err != nil {
|
|
return nil, fmt.Errorf("checking if network is already joined: %w", err)
|
|
} else if !isJoined {
|
|
return nil, errors.New("network is not yet joined")
|
|
}
|
|
|
|
networkStateDir, networkRuntimeDir, err := networkDirs(
|
|
l.networksStateDir, l.networksRuntimeDir, networkID, true,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"creating sub-directories for network %q: %w", networkID, err,
|
|
)
|
|
}
|
|
|
|
return l.opts.constructors.load(
|
|
ctx,
|
|
logger,
|
|
l.envBinDirPath,
|
|
l.nebulaDeviceNamer,
|
|
networkStateDir,
|
|
networkRuntimeDir,
|
|
opts,
|
|
)
|
|
}
|
|
|
|
func (l *loader) Join(
|
|
ctx context.Context,
|
|
logger *mlog.Logger,
|
|
joiningBootstrap JoiningBootstrap,
|
|
opts *Opts,
|
|
) (
|
|
Network, error,
|
|
) {
|
|
var (
|
|
creationParams = joiningBootstrap.Bootstrap.NetworkCreationParams
|
|
networkID = creationParams.ID
|
|
)
|
|
|
|
networkStateDir, networkRuntimeDir, err := networkDirs(
|
|
l.networksStateDir, l.networksRuntimeDir, networkID, false,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"creating sub-directories for network %q: %w", networkID, err,
|
|
)
|
|
}
|
|
|
|
n, err := l.opts.constructors.join(
|
|
ctx,
|
|
logger,
|
|
l.envBinDirPath,
|
|
l.nebulaDeviceNamer,
|
|
joiningBootstrap,
|
|
networkStateDir,
|
|
networkRuntimeDir,
|
|
opts,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, errors.Join(
|
|
err, cleanupDirs(networkStateDir, networkRuntimeDir),
|
|
)
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func (l *loader) Create(
|
|
ctx context.Context,
|
|
logger *mlog.Logger,
|
|
creationParams bootstrap.CreationParams,
|
|
ipNet nebula.IPNet,
|
|
hostName nebula.HostName,
|
|
opts *Opts,
|
|
) (
|
|
Network, error,
|
|
) {
|
|
networkID := creationParams.ID
|
|
|
|
networkStateDir, networkRuntimeDir, err := networkDirs(
|
|
l.networksStateDir, l.networksRuntimeDir, networkID, false,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"creating sub-directories for network %q: %w", networkID, err,
|
|
)
|
|
}
|
|
|
|
n, err := l.opts.constructors.create(
|
|
ctx,
|
|
logger,
|
|
l.envBinDirPath,
|
|
l.nebulaDeviceNamer,
|
|
networkStateDir,
|
|
networkRuntimeDir,
|
|
creationParams,
|
|
ipNet,
|
|
hostName,
|
|
opts,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, errors.Join(
|
|
err, cleanupDirs(networkStateDir, networkRuntimeDir),
|
|
)
|
|
}
|
|
|
|
return n, nil
|
|
}
|
|
|
|
func (l *loader) Leave(
|
|
ctx context.Context, creationParams bootstrap.CreationParams,
|
|
) error {
|
|
networkID := creationParams.ID
|
|
|
|
if isJoined, err := l.isJoined(ctx, networkID); err != nil {
|
|
return fmt.Errorf("checking if network is already joined: %w", err)
|
|
} else if !isJoined {
|
|
return errors.New("network is not yet joined")
|
|
}
|
|
|
|
networkStateDir, networkRuntimeDir, err := networkDirs(
|
|
l.networksStateDir, l.networksRuntimeDir, networkID, true,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf(
|
|
"creating sub-directories for network %q: %w", networkID, err,
|
|
)
|
|
}
|
|
|
|
var (
|
|
newNetworkStateDirName = fmt.Sprintf(
|
|
".%s.%s.bak",
|
|
creationParams.ID,
|
|
l.opts.nowFunc().UTC().Format("20060102-150405"),
|
|
)
|
|
newNetworkStateDirPath = filepath.Join(
|
|
l.networksStateDir.Path,
|
|
newNetworkStateDirName,
|
|
)
|
|
)
|
|
|
|
var errs []error
|
|
|
|
if err := os.Rename(
|
|
networkStateDir.Path, newNetworkStateDirPath,
|
|
); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
if err := os.RemoveAll(networkRuntimeDir.Path); err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
|
|
return errors.Join(errs...)
|
|
}
|