From 187c244de49be51118766bd86843a8f4bcce3ad0 Mon Sep 17 00:00:00 2001 From: cannarelladev Date: Sat, 21 Oct 2023 19:29:59 +0200 Subject: [PATCH] Allocation controller --- apis/nodecore/v1alpha1/allocation_status.go | 50 +++++++ apis/nodecore/v1alpha1/allocation_types.go | 31 +++-- .../v1alpha1/zz_generated.deepcopy.go | 8 +- .../crds/nodecore.fluidos.eu_allocations.yaml | 30 +++-- pkg/rear-manager/allocation_controller.go | 126 ++++++++++++++++++ pkg/utils/consts/doc.go | 2 +- pkg/utils/resourceforge/forge.go | 22 +++ pkg/utils/services/flavours_services.go | 8 +- 8 files changed, 248 insertions(+), 29 deletions(-) create mode 100644 apis/nodecore/v1alpha1/allocation_status.go diff --git a/apis/nodecore/v1alpha1/allocation_status.go b/apis/nodecore/v1alpha1/allocation_status.go new file mode 100644 index 0000000..051acc3 --- /dev/null +++ b/apis/nodecore/v1alpha1/allocation_status.go @@ -0,0 +1,50 @@ +// Copyright 2022-2023 FLUIDOS Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import "github.com/fluidos-project/node/pkg/utils/tools" + +// SetStatus sets the status of the allocation. +func (allocation *Allocation) SetStatus(status Status, msg string) { + allocation.Status.Status = status + allocation.Status.LastUpdateTime = tools.GetTimeNow() + allocation.Status.Message = msg +} + +/* +// SetPurchasePhase sets the ReserveAndBuy phase of the solver +func (allocation *Allocation) SetReserveAndBuyStatus(phase Phase) { + solver.Status.ReserveAndBuy = phase + solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +} + +// SetFindCandidateStatus sets the FindCandidate phase of the solver +func (allocation *Allocation) SetFindCandidateStatus(phase Phase) { + solver.Status.FindCandidate = phase + solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +} + +// SetDiscoveryStatus sets the discovery phase of the solver +func (allocation *Allocation) SetDiscoveryStatus(phase Phase) { + solver.Status.DiscoveryPhase = phase + solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +} + +// SetReservationStatus sets the reservation phase of the solver +func (allocation *Allocation) SetReservationStatus(phase Phase) { + solver.Status.ReservationPhase = phase + solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +} +*/ diff --git a/apis/nodecore/v1alpha1/allocation_types.go b/apis/nodecore/v1alpha1/allocation_types.go index 69b76ed..a926f5c 100644 --- a/apis/nodecore/v1alpha1/allocation_types.go +++ b/apis/nodecore/v1alpha1/allocation_types.go @@ -20,6 +20,7 @@ import ( type NodeType string type Status string +type Destination string const ( Node NodeType = "Node" @@ -31,6 +32,12 @@ const ( Reserved Status = "Reserved" Released Status = "Released" Inactive Status = "Inactive" + Error Status = "Error" +) + +const ( + Remote Destination = "Remote" + Local Destination = "Local" ) // AllocationSpec defines the desired state of Allocation @@ -47,33 +54,39 @@ type AllocationSpec struct { // This specifies the type of the node: Node (Physical node of the cluster) or VirtualNode (Remote node owned by a different cluster) Type NodeType `json:"type"` + // This specifies if the destination of the allocation is local or remote so if the allocation will be used locally or from a remote cluster + Destination Destination `json:"destination"` + // This flag indicates if the allocation is a forwarding allocation, if true it represents only a placeholder to undertand that the cluster is just a proxy to another cluster Forwarding bool `json:"forwarding,omitempty"` // This Flavour describes the characteristics of the allocation, it is based on the Flavour CRD from which it was created Flavour Flavour `json:"flavour"` - // This is the dimension of the allocation, it is based on the Flavour CRD from which it was created - Partition *Partition `json:"partition,omitempty"` + // This flags indicates if the Flavour from which the allocation was created was partitioned or not + Partitioned bool `json:"partitioned"` + + // This is the dimension of the allocation + Resources *Partition `json:"partition,omitempty"` } -// AllocationStatus defines the observed state of Allocation +// AllocationStatus defines the observed state of Allocation. type AllocationStatus struct { // This allow to know the current status of the allocation - Status Status `json:"status"` - - // The creation time of the allocation object - CreationTime metav1.Time `json:"creationTime"` + Status Status `json:"status,omitempty"` // The last time the allocation was updated - LastUpdateTime metav1.Time `json:"lastUpdateTime"` + LastUpdateTime string `json:"lastUpdateTime,omitempty"` + + // Message contains the last message of the allocation + Message string `json:"message,omitempty"` } //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// Allocation is the Schema for the allocations API +// Allocation is the Schema for the allocations API. type Allocation struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go index 16cded4..7c56f2b 100644 --- a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go +++ b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go @@ -43,7 +43,7 @@ func (in *Allocation) DeepCopyInto(out *Allocation) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Allocation. @@ -100,8 +100,8 @@ func (in *AllocationList) DeepCopyObject() runtime.Object { func (in *AllocationSpec) DeepCopyInto(out *AllocationSpec) { *out = *in in.Flavour.DeepCopyInto(&out.Flavour) - if in.Partition != nil { - in, out := &in.Partition, &out.Partition + if in.Resources != nil { + in, out := &in.Resources, &out.Resources *out = new(Partition) (*in).DeepCopyInto(*out) } @@ -120,8 +120,6 @@ func (in *AllocationSpec) DeepCopy() *AllocationSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AllocationStatus) DeepCopyInto(out *AllocationStatus) { *out = *in - in.CreationTime.DeepCopyInto(&out.CreationTime) - in.LastUpdateTime.DeepCopyInto(&out.LastUpdateTime) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllocationStatus. diff --git a/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml b/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml index 0e11ca7..f9f918f 100644 --- a/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml +++ b/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml @@ -17,7 +17,7 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: Allocation is the Schema for the allocations API + description: Allocation is the Schema for the allocations API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -34,6 +34,11 @@ spec: spec: description: AllocationSpec defines the desired state of Allocation properties: + destination: + description: This specifies if the destination of the allocation is + local or remote so if the allocation will be used locally or from + a remote cluster + type: string flavour: description: This Flavour describes the characteristics of the allocation, it is based on the Flavour CRD from which it was created @@ -291,8 +296,7 @@ spec: description: This is the corresponding Node or VirtualNode name type: string partition: - description: This is the dimension of the allocation, it is based - on the Flavour CRD from which it was created + description: This is the dimension of the allocation properties: architecture: type: string @@ -331,35 +335,35 @@ spec: - cpu - memory type: object + partitioned: + description: This flags indicates if the Flavour from which the allocation + was created was partitioned or not + type: boolean type: description: 'This specifies the type of the node: Node (Physical node of the cluster) or VirtualNode (Remote node owned by a different cluster)' type: string required: + - destination - flavour - intentID - nodeName + - partitioned - type type: object status: - description: AllocationStatus defines the observed state of Allocation + description: AllocationStatus defines the observed state of Allocation. properties: - creationTime: - description: The creation time of the allocation object - format: date-time - type: string lastUpdateTime: description: The last time the allocation was updated - format: date-time + type: string + message: + description: Message contains the last message of the allocation type: string status: description: This allow to know the current status of the allocation type: string - required: - - creationTime - - lastUpdateTime - - status type: object type: object served: true diff --git a/pkg/rear-manager/allocation_controller.go b/pkg/rear-manager/allocation_controller.go index 17fed20..15af1b0 100644 --- a/pkg/rear-manager/allocation_controller.go +++ b/pkg/rear-manager/allocation_controller.go @@ -23,6 +23,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" + "github.com/fluidos-project/node/pkg/utils/resourceforge" + "github.com/fluidos-project/node/pkg/utils/services" ) // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations,verbs=get;list;watch;create;update;patch;delete @@ -48,9 +50,133 @@ func (r *AllocationReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } + if allocation.Status.Status != nodecorev1alpha1.Active && + allocation.Status.Status != nodecorev1alpha1.Reserved && + allocation.Status.Status != nodecorev1alpha1.Released && + allocation.Status.Status != nodecorev1alpha1.Inactive { + allocation.SetStatus(nodecorev1alpha1.Inactive, "Allocation has been set to Inactive") + + if err := r.updateAllocationStatus(ctx, &allocation); err != nil { + klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + klog.Infof("Reconciling Solver %s", req.NamespacedName) + + if allocation.Status.Status == nodecorev1alpha1.Error { + klog.Infof("Allocation %s is in error state", req.NamespacedName) + return ctrl.Result{}, nil + } + + if allocation.Spec.Type == nodecorev1alpha1.Node { + allocStatus := allocation.Status.Status + switch allocStatus { + case nodecorev1alpha1.Active: + // We need to check if the ForeignCluster is still ready + // If the ForeignCluster is not ready we need to set the Allocation to Released + klog.Infof("Allocation %s is active", req.NamespacedName) + case nodecorev1alpha1.Reserved: + if allocation.Spec.Destination == nodecorev1alpha1.Remote { + // We need to check the status of the ForeignCluster + // If the ForeignCluster is Ready the Allocation can be set to Active + // else we need to wait for the ForeignCluster to be Ready + klog.Infof("Allocation %s is reserved", req.NamespacedName) + } else { + // We can set the Allocation to Active + klog.Infof("Allocation %s is reserved", req.NamespacedName) + } + case nodecorev1alpha1.Released: + // The Allocation is released, + klog.Infof("Allocation %s is released", req.NamespacedName) + case nodecorev1alpha1.Inactive: + // Alloction Type is Node, so we need to invalidate the Flavour + // and eventually create a new one detaching the right Partition from the old one + klog.Infof("Allocation %s is inactive", req.NamespacedName) + + flavour, err := services.GetFlavourByID(allocation.Spec.Flavour.Name, r.Client) + if err != nil { + klog.Errorf("Error when getting Flavour %s: %v", allocation.Spec.Flavour.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting Flavour") + if err := r.updateAllocationStatus(ctx, &allocation); err != nil { + klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + flavour.Spec.OptionalFields.Availability = false + if err := r.Client.Update(ctx, flavour); err != nil { + klog.Errorf("Error when updating Flavour %s: %v", flavour.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when updating Flavour") + if err := r.updateAllocationStatus(ctx, &allocation); err != nil { + klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + + partitioned := allocation.Spec.Partitioned + if partitioned { + // We need to create a new Flavour with the right Partition + flavourRes := allocation.Spec.Flavour.Spec.Characteristics + allocationRes := allocation.Spec.Resources + + newCharacteristics := computeCharacteristics(&flavourRes, allocationRes) + newFlavour := resourceforge.ForgeFlavourFromRef(flavour, newCharacteristics) + + if err := r.Client.Create(ctx, newFlavour); err != nil { + klog.Errorf("Error when creating Flavour %s: %v", newFlavour.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when creating Flavour") + if err := r.updateAllocationStatus(ctx, &allocation); err != nil { + klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + } + } + + allocation.SetStatus(nodecorev1alpha1.Reserved, "Allocation has been set to Reserved") + if err := r.updateAllocationStatus(ctx, &allocation); err != nil { + klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, nil + default: + klog.Infof("Allocation %s is in an unknown state", req.NamespacedName) + } + } + return ctrl.Result{}, nil } +func computeCharacteristics(old *nodecorev1alpha1.Characteristics, p *nodecorev1alpha1.Partition) *nodecorev1alpha1.Characteristics { + newCPU := old.Cpu.DeepCopy() + newMemory := old.Memory.DeepCopy() + newStorage := old.PersistentStorage.DeepCopy() + newGpu := old.Gpu.DeepCopy() + newEphemeralStorage := old.EphemeralStorage.DeepCopy() + newCPU.Sub(p.CPU) + newMemory.Sub(p.Memory) + newStorage.Sub(p.Storage) + newGpu.Sub(p.Gpu) + newEphemeralStorage.Sub(p.EphemeralStorage) + return &nodecorev1alpha1.Characteristics{ + Architecture: old.Architecture, + Cpu: newCPU, + Memory: newMemory, + Gpu: newGpu, + PersistentStorage: newStorage, + EphemeralStorage: newEphemeralStorage, + } +} + +func (r *AllocationReconciler) updateAllocationStatus(ctx context.Context, allocation *nodecorev1alpha1.Allocation) error { + return r.Status().Update(ctx, allocation) +} + +// SetupWithManager sets up the controller with the Manager. func (r *AllocationReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&nodecorev1alpha1.Allocation{}). diff --git a/pkg/utils/consts/doc.go b/pkg/utils/consts/doc.go index e8a786b..588750f 100644 --- a/pkg/utils/consts/doc.go +++ b/pkg/utils/consts/doc.go @@ -12,5 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// package consts contains the constants used in the FLUIDOS and some miscellaneous ones. +// Package consts contains the constants used in the FLUIDOS and some miscellaneous ones. package consts diff --git a/pkg/utils/resourceforge/forge.go b/pkg/utils/resourceforge/forge.go index 6ed3292..0171d83 100644 --- a/pkg/utils/resourceforge/forge.go +++ b/pkg/utils/resourceforge/forge.go @@ -193,6 +193,28 @@ func ForgeFlavourFromMetrics(node models.NodeInfo, ni nodecorev1alpha1.NodeIdent } } +// ForgeFlavourFromRef creates a new flavour starting from a Reference Flavour and the new Characteristics. +func ForgeFlavourFromRef(f *nodecorev1alpha1.Flavour, char *nodecorev1alpha1.Characteristics) (flavour *nodecorev1alpha1.Flavour) { + return &nodecorev1alpha1.Flavour{ + ObjectMeta: metav1.ObjectMeta{ + Name: namings.ForgeFlavourName(f.Spec.OptionalFields.WorkerID, f.Spec.Owner.Domain), + Namespace: flags.FLUIDOS_NAMESPACE, + }, + Spec: nodecorev1alpha1.FlavourSpec{ + ProviderID: f.Spec.ProviderID, + Type: f.Spec.Type, + Characteristics: *char, + Policy: f.Spec.Policy, + Owner: f.Spec.Owner, + Price: f.Spec.Price, + OptionalFields: nodecorev1alpha1.OptionalFields{ + Availability: true, + WorkerID: f.Spec.OptionalFields.WorkerID, + }, + }, + } +} + // FORGER FUNCTIONS FROM OBJECTS // ForgeTransactionObj creates a new Transaction object. diff --git a/pkg/utils/services/flavours_services.go b/pkg/utils/services/flavours_services.go index 1346336..bc25b7d 100644 --- a/pkg/utils/services/flavours_services.go +++ b/pkg/utils/services/flavours_services.go @@ -16,6 +16,7 @@ package services import ( "context" + "sync" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" @@ -24,9 +25,14 @@ import ( "github.com/fluidos-project/node/pkg/utils/flags" ) +type FlavourService interface { + sync.Mutex + GetAllFlavours() ([]nodecorev1alpha1.Flavour, error) + GetFlavourByID(flavourID string) (*nodecorev1alpha1.Flavour, error) +} + // GetAllFlavours returns all the Flavours in the cluster func GetAllFlavours(cl client.Client) ([]nodecorev1alpha1.Flavour, error) { - var flavourList nodecorev1alpha1.FlavourList // List all Flavour CRs