isle/go/daemon/network/glm/glm.go

169 lines
4.7 KiB
Go
Raw Normal View History

2025-01-07 14:39:26 +00:00
// Package glm, "garage layout manager", implements the transition of garage
// layout states based on the currently active layout and the desired layout
// defined by the user.
package glm
import (
"context"
"errors"
"fmt"
"io/fs"
"isle/daemon/daecommon"
"isle/garage"
"isle/jsonutil"
"isle/toolkit"
"net/netip"
"path/filepath"
)
// GarageLayoutManager (GLM) tracks the currently active set of storage
// allocations and calculates actions required to transition from the active set
// into a target set.
//
// GLM will make sure that when allocations are removed they are properly
// drained prior to being fully removed from the cluster.
type GarageLayoutManager interface {
// GetActiveAllocations returns the currently active set of allocations, as
// recorded by the last call to CommitStateTransition. Returns an empty set
// if CommitStateTransition has never been called.
GetActiveAllocations(context.Context) (
[]daecommon.ConfigStorageAllocation, error,
)
// SetActiveAllocations overwrites the stored active allocations, if any, to
// the given one.
SetActiveAllocations(context.Context, []daecommon.ConfigStorageAllocation) error
// Validate checks the target allocation set for any inconsistencies with
// the currently active one, and returns an error if one is found.
Validate(
_ context.Context, targetAllocs []daecommon.ConfigStorageAllocation,
) error
// CalculateStateTransition accepts a set of known nodes from an up-to-date
// [garage.ClusterStatus] and the target allocation set for the host, and
// returns a StateTransition describing the actions which should be taken.
//
// If the host is not running any garage instances, and therefore cannot
// determine the known nodes, nil should be passed instead.
CalculateStateTransition(
_ context.Context,
knownNodes []garage.KnownNode,
targetAllocs []daecommon.ConfigStorageAllocation,
) (
StateTransition, error,
)
// CommitStateTransition should be called after the CalculateStateTransition
// returns a StateTransition, and the StateTransition's prescribed actions
// have been successfully carried out.
CommitStateTransition(context.Context, StateTransition) error
}
type garageLayoutManager struct {
dir toolkit.Dir
hostIP netip.Addr
}
// NewGarageLayoutManager initializes and returns a GarageLayoutManager which
// will use the given directory to store state, and which is managing the layout
// for the host with the given IP.
func NewGarageLayoutManager(
dir toolkit.Dir, hostIP netip.Addr,
) GarageLayoutManager {
return &garageLayoutManager{dir, hostIP}
}
const glmStateFile = "glm.json"
type glmState struct {
ActiveAllocations []daecommon.ConfigStorageAllocation
}
func (glm *garageLayoutManager) get() (glmState, error) {
var (
path = filepath.Join(glm.dir.Path, glmStateFile)
state glmState
)
err := jsonutil.LoadFile(&state, path)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return glmState{}, err
}
return state, nil
}
func (glm *garageLayoutManager) set(state glmState) error {
path := filepath.Join(glm.dir.Path, glmStateFile)
return jsonutil.WriteFile(state, path, 0600)
}
func (glm *garageLayoutManager) GetActiveAllocations(context.Context) (
[]daecommon.ConfigStorageAllocation, error,
) {
state, err := glm.get()
return state.ActiveAllocations, err
}
func (glm *garageLayoutManager) SetActiveAllocations(
_ context.Context,
allocs []daecommon.ConfigStorageAllocation,
) error {
return glm.set(glmState{allocs})
}
func (glm *garageLayoutManager) Validate(
_ context.Context, targetAllocs []daecommon.ConfigStorageAllocation,
) error {
state, err := glm.get()
if err != nil {
return fmt.Errorf("reading state: %w", err)
}
return validateTargetAllocs(
state.ActiveAllocations,
targetAllocs,
)
}
func (glm *garageLayoutManager) CalculateStateTransition(
_ context.Context,
knownNodes []garage.KnownNode,
targetAllocs []daecommon.ConfigStorageAllocation,
) (
StateTransition, error,
) {
state, err := glm.get()
if err != nil {
return StateTransition{}, fmt.Errorf("reading state: %w", err)
}
err = validateTargetAllocs(state.ActiveAllocations, targetAllocs)
if err != nil {
return StateTransition{}, fmt.Errorf(
"validating target allocations: %w", err,
)
}
allKnownNodes, knownNodes := knownNodes, knownNodes[:0]
for _, node := range allKnownNodes {
if node.Addr.Addr() == glm.hostIP {
knownNodes = append(knownNodes, node)
}
}
return calcStateTransition(
state.ActiveAllocations, knownNodes, targetAllocs,
), nil
}
func (glm *garageLayoutManager) CommitStateTransition(
_ context.Context, stateTransition StateTransition,
) error {
return glm.set(glmState{
ActiveAllocations: stateTransition.ActiveAllocations(),
})
}