diff --git a/pkg/client/deploy/scheduler/constraint.go b/pkg/client/deploy/scheduler/constraint.go new file mode 100644 index 00000000..5bd9e1ea --- /dev/null +++ b/pkg/client/deploy/scheduler/constraint.go @@ -0,0 +1,37 @@ +package scheduler + +import ( + "strings" + + "github.com/psviderski/uncloud/pkg/api" +) + +// Constraint is the base interface for all scheduling constraints. +type Constraint interface { + // Evaluate determines if a machine satisfies the constraint. + Evaluate(machine *Machine) bool + + // Description returns a human-readable description of the constraint. + Description() string +} + +func constraintsFromSpec(spec api.ServiceSpec) []Constraint { + return []Constraint{} +} + +type PlacementConstraint struct { + Machines []string +} + +func (c *PlacementConstraint) Evaluate(machine *Machine) bool { + for _, nameOrID := range c.Machines { + if machine.Info.Id == nameOrID || machine.Info.Name == nameOrID { + return true + } + } + return false +} + +func (c *PlacementConstraint) Description() string { + return "Placement constraint by machines: " + strings.Join(c.Machines, ", ") +} diff --git a/pkg/client/deploy/scheduler/machine.go b/pkg/client/deploy/scheduler/machine.go new file mode 100644 index 00000000..25f28ade --- /dev/null +++ b/pkg/client/deploy/scheduler/machine.go @@ -0,0 +1,50 @@ +package scheduler + +import ( + "context" + "fmt" + + "github.com/docker/docker/api/types/volume" + "github.com/psviderski/uncloud/internal/machine/api/pb" + "github.com/psviderski/uncloud/pkg/api" +) + +type Client interface { + api.MachineClient + api.VolumeClient +} + +type Machine struct { + Info *pb.MachineInfo + Volumes []volume.Volume +} + +// InspectMachines retrieves the list of available machines and their details required for scheduling purposes. +// TODO: refactor to get all the details in one broadcast call to machine API. +func InspectMachines(ctx context.Context, cli Client) ([]*Machine, error) { + machineMembers, err := cli.ListMachines(ctx, &api.MachineFilter{Available: true}) + if err != nil { + return nil, fmt.Errorf("list machines: %w", err) + } + volumes, err := cli.ListVolumes(ctx, nil) + if err != nil { + return nil, fmt.Errorf("list volumes: %w", err) + } + + var machines []*Machine + for _, m := range machineMembers { + machine := &Machine{ + Info: m.Machine, + } + + for _, v := range volumes { + if v.MachineID == m.Machine.Id { + machine.Volumes = append(machine.Volumes, v.Volume) + } + } + + machines = append(machines, machine) + } + + return machines, nil +} diff --git a/pkg/client/deploy/scheduler/service.go b/pkg/client/deploy/scheduler/service.go new file mode 100644 index 00000000..981eca3c --- /dev/null +++ b/pkg/client/deploy/scheduler/service.go @@ -0,0 +1,51 @@ +package scheduler + +import ( + "errors" + + "github.com/psviderski/uncloud/internal/machine/api/pb" + "github.com/psviderski/uncloud/pkg/api" +) + +type ServiceScheduler struct { + machines []*Machine + spec api.ServiceSpec + constraints []Constraint +} + +func NewServiceScheduler(machines []*Machine, spec api.ServiceSpec, constraints []Constraint) *ServiceScheduler { + specConstraints := constraintsFromSpec(spec) + specConstraints = append(specConstraints, constraints...) + + return &ServiceScheduler{ + machines: machines, + spec: spec, + constraints: specConstraints, + } +} + +func (s *ServiceScheduler) AvailableMachines() ([]*Machine, error) { + var available []*Machine + for _, machine := range s.machines { + if s.evaluateConstraints(machine) { + available = append(available, machine) + } + } + if len(available) == 0 { + return nil, errors.New("no machines available that satisfy all constraints") + } + return available, nil +} + +func (s *ServiceScheduler) evaluateConstraints(machine *Machine) bool { + for _, c := range s.constraints { + if !c.Evaluate(machine) { + return false + } + } + return true +} + +func (s *ServiceScheduler) ScheduleContainer() ([]*pb.MachineInfo, error) { + return nil, errors.New("not implemented") +}