mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
fix: new Compose deployment with volumes and --recreate flag (fixes #176)
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/psviderski/uncloud/pkg/api"
|
||||
"github.com/psviderski/uncloud/pkg/client/deploy/scheduler"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
@@ -25,6 +26,8 @@ type Deployment struct {
|
||||
Strategy Strategy
|
||||
cli Client
|
||||
plan *Plan
|
||||
// state is an optional current and planned cluster state used for scheduling decisions.
|
||||
state *scheduler.ClusterState
|
||||
}
|
||||
|
||||
type Plan struct {
|
||||
@@ -47,6 +50,16 @@ func NewDeployment(cli Client, spec api.ServiceSpec, strategy Strategy) *Deploym
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeploymentWithClusterState creates a new deployment like NewDeployment but also with a provided current cluster
|
||||
// state used for scheduling decisions.
|
||||
func NewDeploymentWithClusterState(
|
||||
cli Client, spec api.ServiceSpec, strategy Strategy, state *scheduler.ClusterState,
|
||||
) *Deployment {
|
||||
d := NewDeployment(cli, spec, strategy)
|
||||
d.state = state
|
||||
return d
|
||||
}
|
||||
|
||||
// Plan returns a plan of operations to reconcile the service to the desired state.
|
||||
// If a plan has already been created, the same plan will be returned.
|
||||
func (d *Deployment) Plan(ctx context.Context) (Plan, error) {
|
||||
@@ -73,7 +86,14 @@ func (d *Deployment) Plan(ctx context.Context) (Plan, error) {
|
||||
return Plan{}, fmt.Errorf("resolve service spec: %w", err)
|
||||
}
|
||||
|
||||
plan, err := d.Strategy.Plan(ctx, d.cli, d.Service, resolvedSpec)
|
||||
if d.state == nil {
|
||||
d.state, err = scheduler.InspectClusterState(ctx, d.cli)
|
||||
if err != nil {
|
||||
return Plan{}, fmt.Errorf("inspect cluster state: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
plan, err := d.Strategy.Plan(d.state, d.Service, resolvedSpec)
|
||||
if err != nil {
|
||||
return Plan{}, fmt.Errorf("create plan using %s strategy: %w", d.Strategy.Type(), err)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// - If a volume already exists on a machine, it must be used instead of creating a new one.
|
||||
// - A missing volume must only be created on one machine.
|
||||
type VolumeScheduler struct {
|
||||
// state is the current state of machines and their resources in the cluster.
|
||||
// state is the current and planned state of machines and their resources in the cluster.
|
||||
state *ClusterState
|
||||
// serviceSpecs is a list of service specifications included in the deployment.
|
||||
serviceSpecs []api.ServiceSpec
|
||||
@@ -106,6 +106,7 @@ func NewVolumeScheduler(state *ClusterState, specs []api.ServiceSpec) (*VolumeSc
|
||||
// Schedule determines what missing volumes should be created and where for services in the multi-service deployment.
|
||||
// It returns a map of machine IDs to a list of api.VolumeSpec that should be created on that machine,
|
||||
// or an error if services can't be scheduled due to scheduling constraints.
|
||||
// It also updates the state of the machines in the cluster state to reflect the scheduled volumes.
|
||||
func (s *VolumeScheduler) Schedule() (map[string][]api.VolumeSpec, error) {
|
||||
if len(s.serviceSpecs) == 0 {
|
||||
// No services with volume mounts, nothing to schedule.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
@@ -18,31 +17,31 @@ type Strategy interface {
|
||||
// Type returns the type of the deployment strategy, e.g. "rolling", "blue-green".
|
||||
Type() string
|
||||
// Plan returns the operation to reconcile the service to the desired state.
|
||||
// If the service does not exist (new deployment), svc will be nil.
|
||||
Plan(ctx context.Context, cli scheduler.Client, svc *api.Service, spec api.ServiceSpec) (Plan, error)
|
||||
// If the service does not exist (new deployment), svc will be nil. state provides the current and planned state
|
||||
// of the cluster for scheduling decisions.
|
||||
Plan(state *scheduler.ClusterState, svc *api.Service, spec api.ServiceSpec) (Plan, error)
|
||||
}
|
||||
|
||||
// RollingStrategy implements a rolling update deployment pattern where containers are updated one at a time
|
||||
// to minimize service disruption.
|
||||
type RollingStrategy struct {
|
||||
State *scheduler.ClusterState
|
||||
// ForceRecreate indicates whether all containers should be recreated during the deployment,
|
||||
// regardless of whether their specifications have changed.
|
||||
ForceRecreate bool
|
||||
|
||||
// state is the current and planned state of the cluster used for scheduling decisions.
|
||||
state *scheduler.ClusterState
|
||||
}
|
||||
|
||||
func (s *RollingStrategy) Type() string {
|
||||
return "rolling"
|
||||
}
|
||||
|
||||
func (s *RollingStrategy) Plan(
|
||||
ctx context.Context, cli scheduler.Client, svc *api.Service, spec api.ServiceSpec,
|
||||
) (Plan, error) {
|
||||
if s.State == nil {
|
||||
state, err := scheduler.InspectClusterState(ctx, cli)
|
||||
if err != nil {
|
||||
return Plan{}, fmt.Errorf("inspect cluster state: %w", err)
|
||||
}
|
||||
s.State = state
|
||||
func (s *RollingStrategy) Plan(state *scheduler.ClusterState, svc *api.Service, spec api.ServiceSpec) (Plan, error) {
|
||||
if state == nil {
|
||||
return Plan{}, fmt.Errorf("cluster state must be provided")
|
||||
}
|
||||
s.state = state
|
||||
|
||||
// We can assume that the spec is valid at this point because it has been validated by the deployment.
|
||||
switch spec.Mode {
|
||||
@@ -65,7 +64,7 @@ func (s *RollingStrategy) planReplicated(svc *api.Service, spec api.ServiceSpec)
|
||||
return plan, err
|
||||
}
|
||||
|
||||
sched := scheduler.NewServiceScheduler(s.State, spec)
|
||||
sched := scheduler.NewServiceScheduler(s.state, spec)
|
||||
// TODO: return a detailed report on required constraints and which ones are satisfied?
|
||||
availableMachines, err := sched.EligibleMachines()
|
||||
if err != nil {
|
||||
@@ -221,7 +220,7 @@ func (s *RollingStrategy) planGlobal(svc *api.Service, spec api.ServiceSpec) (Pl
|
||||
}
|
||||
}
|
||||
|
||||
sched := scheduler.NewServiceScheduler(s.State, spec)
|
||||
sched := scheduler.NewServiceScheduler(s.state, spec)
|
||||
availableMachines, err := sched.EligibleMachines()
|
||||
if err != nil {
|
||||
return plan, err
|
||||
|
||||
Reference in New Issue
Block a user