From 132d0a74a044047d1143d6cffbe7ed498f5b3471 Mon Sep 17 00:00:00 2001 From: fracappa Date: Tue, 3 Sep 2024 13:14:36 +0200 Subject: [PATCH] cleaning up dependencies among FLUIDOS Node CRDs solving linting issues solving linting issues --- .../v1alpha1/peeringcandidate_types.go | 8 +- .../v1alpha1/zz_generated.deepcopy.go | 5 + apis/nodecore/v1alpha1/allocation_types.go | 4 - apis/nodecore/v1alpha1/solver_types.go | 11 - .../v1alpha1/zz_generated.deepcopy.go | 3 - .../advertisement.fluidos.eu_discoveries.yaml | 8 +- ...tisement.fluidos.eu_peeringcandidates.yaml | 8 +- .../crds/nodecore.fluidos.eu_allocations.yaml | 7 - .../crds/nodecore.fluidos.eu_solvers.yaml | 46 --- deployments/node/samples/allocation.yaml | 6 +- deployments/node/samples/reservation.yaml | 14 +- deployments/node/samples/solver.yaml | 4 +- .../reservation_controller.go | 3 - .../discovery-manager/peeringcandidate_wh.go | 22 +- pkg/rear-controller/gateway/provider.go | 2 +- pkg/rear-manager/solver_controller.go | 270 ++++++++++++------ pkg/utils/common/common.go | 1 - pkg/utils/resourceforge/forge.go | 10 +- 18 files changed, 223 insertions(+), 209 deletions(-) diff --git a/apis/advertisement/v1alpha1/peeringcandidate_types.go b/apis/advertisement/v1alpha1/peeringcandidate_types.go index 3d277a1..857693a 100644 --- a/apis/advertisement/v1alpha1/peeringcandidate_types.go +++ b/apis/advertisement/v1alpha1/peeringcandidate_types.go @@ -25,11 +25,9 @@ import ( // PeeringCandidateSpec defines the desired state of PeeringCandidate. type PeeringCandidateSpec struct { - SolverID string `json:"solverID"` - - Flavor nodecorev1alpha1.Flavor `json:"flavor"` - - Available bool `json:"available"` + InterestedSolverIDs []string `json:"interestedSolverIDs"` + Flavor nodecorev1alpha1.Flavor `json:"flavor"` + Available bool `json:"available"` } // PeeringCandidateStatus defines the observed state of PeeringCandidate. diff --git a/apis/advertisement/v1alpha1/zz_generated.deepcopy.go b/apis/advertisement/v1alpha1/zz_generated.deepcopy.go index 1f85594..1aea354 100644 --- a/apis/advertisement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/advertisement/v1alpha1/zz_generated.deepcopy.go @@ -181,6 +181,11 @@ func (in *PeeringCandidateList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PeeringCandidateSpec) DeepCopyInto(out *PeeringCandidateSpec) { *out = *in + if in.InterestedSolverIDs != nil { + in, out := &in.InterestedSolverIDs, &out.InterestedSolverIDs + *out = make([]string, len(*in)) + copy(*out, *in) + } in.Flavor.DeepCopyInto(&out.Flavor) } diff --git a/apis/nodecore/v1alpha1/allocation_types.go b/apis/nodecore/v1alpha1/allocation_types.go index 4ae104e..daaee27 100644 --- a/apis/nodecore/v1alpha1/allocation_types.go +++ b/apis/nodecore/v1alpha1/allocation_types.go @@ -32,10 +32,6 @@ const ( // AllocationSpec defines the desired state of Allocation. type AllocationSpec struct { - // This is the ID of the intent for which the allocation was created. - // It is used by the Node Orchestrator to identify the correct allocation for a given intent - IntentID string `json:"intentID"` - // 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"` diff --git a/apis/nodecore/v1alpha1/solver_types.go b/apis/nodecore/v1alpha1/solver_types.go index 6124575..2a6a5ba 100644 --- a/apis/nodecore/v1alpha1/solver_types.go +++ b/apis/nodecore/v1alpha1/solver_types.go @@ -95,17 +95,6 @@ type SolverStatus struct { // SolverPhase describes the status of the Solver generated by the Node Orchestrator. // It is useful to understand if the solver is still running or if it has finished or failed. SolverPhase PhaseStatus `json:"solverPhase,omitempty"` - - // Allocation contains the allocation that the solver has eventually created for the intent. - // It can correspond to a virtual node - // The Node Orchestrator will use this allocation to fulfill the intent. - Allocation GenericRef `json:"allocation,omitempty"` - - // Contract contains the Contract that the Contract Manager has eventually created with the candidate. - Contract GenericRef `json:"contract,omitempty"` - - // Credentials contains the LiqoCredentials found in the Contract. - Credentials LiqoCredentials `json:"credentials,omitempty"` } //+kubebuilder:object:root=true diff --git a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go index 7bc6dd8..b001d59 100644 --- a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go +++ b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go @@ -802,9 +802,6 @@ func (in *SolverSpec) DeepCopy() *SolverSpec { func (in *SolverStatus) DeepCopyInto(out *SolverStatus) { *out = *in out.SolverPhase = in.SolverPhase - out.Allocation = in.Allocation - out.Contract = in.Contract - out.Credentials = in.Credentials } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolverStatus. diff --git a/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml b/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml index 8d6b362..5c0bfba 100644 --- a/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml +++ b/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml @@ -314,12 +314,14 @@ spec: - lastUpdateTime type: object type: object - solverID: - type: string + interestedSolverIDs: + items: + type: string + type: array required: - available - flavor - - solverID + - interestedSolverIDs type: object status: description: PeeringCandidateStatus defines the observed diff --git a/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml b/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml index 2fc765a..2e8a05f 100644 --- a/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml +++ b/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml @@ -192,12 +192,14 @@ spec: - lastUpdateTime type: object type: object - solverID: - type: string + interestedSolverIDs: + items: + type: string + type: array required: - available - flavor - - solverID + - interestedSolverIDs type: object status: description: PeeringCandidateStatus defines the observed state of PeeringCandidate. diff --git a/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml b/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml index 5fd1d3d..aa5c621 100644 --- a/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml +++ b/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml @@ -60,13 +60,6 @@ spec: 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 type: boolean - intentID: - description: |- - This is the ID of the intent for which the allocation was created. - It is used by the Node Orchestrator to identify the correct allocation for a given intent - type: string - required: - - intentID type: object status: description: AllocationStatus defines the observed state of Allocation. diff --git a/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml b/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml index 838e38c..8fc4fdd 100644 --- a/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml +++ b/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml @@ -115,57 +115,11 @@ spec: status: description: SolverStatus defines the observed state of Solver. properties: - allocation: - description: |- - Allocation contains the allocation that the solver has eventually created for the intent. - It can correspond to a virtual node - The Node Orchestrator will use this allocation to fulfill the intent. - properties: - name: - description: The name of the resource to be referenced. - type: string - namespace: - description: |- - The namespace containing the resource to be referenced. It should be left - empty in case of cluster-wide resources. - type: string - type: object consumePhase: description: |- ConsumePhase describes the status of the Consume phase where the VFM (Liqo) is enstablishing a peering with the candidate node. type: string - contract: - description: Contract contains the Contract that the Contract Manager - has eventually created with the candidate. - properties: - name: - description: The name of the resource to be referenced. - type: string - namespace: - description: |- - The namespace containing the resource to be referenced. It should be left - empty in case of cluster-wide resources. - type: string - type: object - credentials: - description: Credentials contains the LiqoCredentials found in the - Contract. - properties: - clusterID: - type: string - clusterName: - type: string - endpoint: - type: string - token: - type: string - required: - - clusterID - - clusterName - - endpoint - - token - type: object discoveryPhase: description: |- DiscoveryPhase describes the status of the Discovery where the Discovery Manager diff --git a/deployments/node/samples/allocation.yaml b/deployments/node/samples/allocation.yaml index 2976e8f..084073c 100644 --- a/deployments/node/samples/allocation.yaml +++ b/deployments/node/samples/allocation.yaml @@ -1,12 +1,12 @@ apiVersion: nodecore.fluidos.eu/v1alpha1 kind: Allocation metadata: - name: allocation-sample + name: allocation-sample-whatever namespace: fluidos spec: # Get it from the solver - intentID: intent-sample + # intentID: intent-sample # Retrieve information from the reservation and the contract bou d to it contract: - name: contract-fluidos.eu-k8slice-07eadba8bf32d8d6f142b6726592956a-43a4 + name: contract-fluidos.eu-k8slice-7413576d05fe8e93c719e13eab156c5c-ae7e namespace: fluidos \ No newline at end of file diff --git a/deployments/node/samples/reservation.yaml b/deployments/node/samples/reservation.yaml index 249d6ca..8be7048 100644 --- a/deployments/node/samples/reservation.yaml +++ b/deployments/node/samples/reservation.yaml @@ -1,10 +1,10 @@ apiVersion: reservation.fluidos.eu/v1alpha1 kind: Reservation metadata: - name: reservation-sample + name: reservation-solver-sample-2 namespace: fluidos spec: - solverID: solver-sample-1 + solverID: solver-sample-2 # Set it as you want, following needs and requests in the solver. # Optional configuration: @@ -17,7 +17,7 @@ spec: pods: "110" # Retrieve from PeeringCandidate chosen to reserve peeringCandidate: - name: peeringcandidate-fluidos.eu-k8slice-24f77877ba11ce29a25f95ec33a244c5 + name: peeringcandidate-fluidos.eu-k8slice-7413576d05fe8e93c719e13eab156c5c namespace: fluidos # Set it to reserve reserve: true @@ -26,10 +26,10 @@ spec: # Retrieve from PeeringCandidate Flavor Owner field seller: domain: fluidos.eu - ip: 172.18.0.7:30001 - nodeID: ac7jspin96 + ip: 172.19.0.2:30001 + nodeID: ejbbzmacn0 # Retrieve from configmap buyer: domain: fluidos.eu - ip: 172.18.0.5:30000 - nodeID: 4sqa7o2wsh \ No newline at end of file + ip: 172.19.0.5:30000 + nodeID: e9d2mzsbcf \ No newline at end of file diff --git a/deployments/node/samples/solver.yaml b/deployments/node/samples/solver.yaml index 15a4a91..2da4f03 100644 --- a/deployments/node/samples/solver.yaml +++ b/deployments/node/samples/solver.yaml @@ -1,7 +1,7 @@ apiVersion: nodecore.fluidos.eu/v1alpha1 kind: Solver metadata: - name: solver-sample + name: solver-sample-3 namespace: fluidos spec: # This is the Selector used to find a Flavor (FLUIDOS node) that matches the requirements @@ -37,7 +37,7 @@ spec: data: value: 110 # The intentID is the ID of the intent that the solver should satisfy - intentID: "intent-sample" + intentID: "intent-sample-3" # This flag is used to indicate that the solver should find a candidate (FLUIDOS node) findCandidate: true # This flag is used to indicate that the solver should reserve and buy the resources from the candidate (FLUIDOS node) diff --git a/pkg/rear-controller/contract-manager/reservation_controller.go b/pkg/rear-controller/contract-manager/reservation_controller.go index f629690..5861db2 100644 --- a/pkg/rear-controller/contract-manager/reservation_controller.go +++ b/pkg/rear-controller/contract-manager/reservation_controller.go @@ -164,7 +164,6 @@ func (r *ReservationReconciler) handleReserve(ctx context.Context, // Set the peering candidate as not available peeringCandidate.Spec.Available = false - peeringCandidate.Spec.SolverID = reservation.Spec.SolverID if err := r.Update(ctx, peeringCandidate); err != nil { klog.Errorf("Error when updating PeeringCandidate %s status before reconcile: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -187,7 +186,6 @@ func (r *ReservationReconciler) handleReserve(ctx context.Context, // Set the peering candidate as available again peeringCandidate.Spec.Available = true - peeringCandidate.Spec.SolverID = "" if err := r.Update(ctx, peeringCandidate); err != nil { klog.Errorf("Error when updating PeeringCandidate %s status before reconcile: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -341,7 +339,6 @@ func (r *ReservationReconciler) handlePurchase(ctx context.Context, } peeringCandidate.Spec.Available = true - peeringCandidate.Spec.SolverID = "" if err := r.Update(ctx, &peeringCandidate); err != nil { klog.Errorf("Error when updating PeeringCandidate %s status before reconcile: %s", req.NamespacedName, err) return ctrl.Result{}, err diff --git a/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go b/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go index 4e8c0fb..4d47842 100644 --- a/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go +++ b/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go @@ -69,12 +69,12 @@ func (v *PCValidator) HandleCreate(_ context.Context, req admission.Request) adm return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to decode peering candidate: %w", err)) } - if !pc.Spec.Available && pc.Spec.SolverID == "" { - return admission.Denied("If peering candidate is not available, solver ID must be set") + if pc.Spec.Available && len(pc.Spec.InterestedSolverIDs) == 0 { + return admission.Denied("Can't create a peering candidate wihout a triggering solver") } - if pc.Spec.Available && pc.Spec.SolverID != "" { - return admission.Denied("If peering candidate is available, solver ID must not be set") + if !pc.Spec.Available { + return admission.Denied("Can't create a peering candidate with Available flag set to false") } return admission.Allowed("") @@ -108,20 +108,12 @@ func (v *PCValidator) HandleUpdate(_ context.Context, req admission.Request) adm return admission.Errored(http.StatusBadRequest, fmt.Errorf("failed to decode peering old candidate: %w", err)) } - // PC can be updated only if: - // - both Reserved flag and SolverID are not set (i.e. it is not reserved) - // - both Reserved flag and SolverID are set and you want to clear both in the same time - - if !pcOld.Spec.Available && pcOld.Spec.SolverID == "" { - return admission.Allowed("") - } - - if !pcOld.Spec.Available && pcOld.Spec.SolverID != "" && pc.Spec.Available && pc.Spec.SolverID == "" { - return admission.Allowed("") + if !pcOld.Spec.Available && !pc.Spec.Available { + return admission.Denied("Peering candidate can be updated if Available flag is changed from false to true") } //nolint:lll // This is a long line - return admission.Denied("peering candidate can be updated only if it is not reserved or if both Reserved flag and SolverID are set and you want to clear both in the same time") + return admission.Allowed("") } // DecodePeeringCandidate decodes the PeeringCandidate. diff --git a/pkg/rear-controller/gateway/provider.go b/pkg/rear-controller/gateway/provider.go index 320cb34..9306372 100644 --- a/pkg/rear-controller/gateway/provider.go +++ b/pkg/rear-controller/gateway/provider.go @@ -349,7 +349,7 @@ func (g *Gateway) purchaseFlavor(w http.ResponseWriter, r *http.Request) { // Create allocation klog.Infof("Creating allocation...") - allocation := *resourceforge.ForgeAllocation(&contract, "") + allocation := *resourceforge.ForgeAllocation(&contract) err = g.client.Create(context.Background(), &allocation) if err != nil { klog.Errorf("Error creating the Allocation: %s", err) diff --git a/pkg/rear-manager/solver_controller.go b/pkg/rear-manager/solver_controller.go index ca7875b..de5ad2f 100644 --- a/pkg/rear-manager/solver_controller.go +++ b/pkg/rear-manager/solver_controller.go @@ -147,7 +147,20 @@ func (r *SolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr if solver.Spec.EstablishPeering { if reserveAndBuyStatus == nodecorev1alpha1.PhaseSolved && findCandidateStatus == nodecorev1alpha1.PhaseSolved && peeringStatus != nodecorev1alpha1.PhaseSolved { - return r.handlePeering(ctx, req, &solver) + pc, err := r.searchReservedPeeringCandidate(ctx, &solver) + if err != nil { + klog.Errorf("Error when searching and booking a candidate for Solver %s: %s", req.NamespacedName.Name, err) + return ctrl.Result{}, err + } + + // Get Contract by PeeringCandidate + contract, err := r.getContractByPeeringCandidate(ctx, pc) + if err != nil { + klog.Errorf("Error when getting Contract for Solver %s: %s", req.NamespacedName.Name, err) + return ctrl.Result{}, err + } + + return r.handlePeering(ctx, req, &solver, contract) } } else { klog.Infof("Solver %s Solved : No need to establish a peering", req.NamespacedName.Name) @@ -189,11 +202,19 @@ func (r *SolverReconciler) handleFindCandidate(ctx context.Context, req ctrl.Req // If some PeeringCandidates are available, select one and book it if len(pc) > 0 { // If some PeeringCandidates are available, select one and book it - selectedPc, err := r.selectAndBookPeeringCandidate(pc) + selectedPc, err := r.selectAvaiablePeeringCandidate(pc) if err != nil { klog.Errorf("Error when selecting and booking a candidate for Solver %s: %s", req.NamespacedName.Name, err) return ctrl.Result{}, err } + + // Update the PeeringCandidate InterestedSolverIDs + selectedPc.Spec.InterestedSolverIDs = append(selectedPc.Spec.InterestedSolverIDs, solver.Name) + if err := r.Client.Update(ctx, selectedPc); err != nil { + klog.Errorf("Error when updating PeeringCandidate %s for Solver %s: %s", selectedPc.Name, solver.Name, err) + return ctrl.Result{}, err + } + klog.Infof("Solver %s has selected and booked candidate %s", req.NamespacedName.Name, selectedPc.Name) solver.SetFindCandidateStatus(nodecorev1alpha1.PhaseSolved) solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Solver has found a candidate") @@ -265,6 +286,34 @@ func (r *SolverReconciler) handleFindCandidate(ctx context.Context, req ctrl.Req } } +func (r *SolverReconciler) getContractByPeeringCandidate(ctx context.Context, pc *advertisementv1alpha1.PeeringCandidate) ( + *reservationv1alpha1.Contract, error) { + contract := &reservationv1alpha1.Contract{} + // Get the contract list + contractList := &reservationv1alpha1.ContractList{} + if err := r.List(ctx, contractList, client.InNamespace(pc.Namespace)); err != nil { + return nil, err + } + + flavorName := pc.Spec.Flavor.Name + for i := 0; i < len(contractList.Items); i++ { + c := contractList.Items[i] + if c.Spec.Flavor.Name == flavorName { + contract = &c + break + } + } + + if contract.Name == "" { + return nil, errors.NewNotFound(schema.GroupResource{ + Group: reservationv1alpha1.GroupVersion.Group, + Resource: "contracts", + }, pc.Name) + } + + return contract, nil +} + func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Request, solver *nodecorev1alpha1.Solver) (ctrl.Result, error) { reserveAndBuyStatus := solver.Status.ReserveAndBuy switch reserveAndBuyStatus { @@ -285,88 +334,85 @@ func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Req for i := range pcList { if pcList[i].Spec.Available { pc = pcList[i] - // Update the SolverID in the PeeringCandidate selected if it is different from the current Solver - if pc.Spec.SolverID != solver.Name { - pc.Spec.SolverID = solver.Name + if !contains(pc.Spec.InterestedSolverIDs, solver.Name) { + pc.Spec.InterestedSolverIDs = append(pc.Spec.InterestedSolverIDs, solver.Name) if err := r.Client.Update(ctx, &pc); err != nil { klog.Errorf("Error when updating PeeringCandidate %s for Solver %s: %s", pc.Name, solver.Name, err) return ctrl.Result{}, err } } - break } - } - if pc.Name == "" { - klog.Errorf("No PeeringCandidate found for Solver %s:", solver.Name) - return ctrl.Result{}, nil - } - - if solver.Spec.Selector != nil { - // Forge the Partition - - // Parse Solver Selector - solverTypeIdentifier, solverTypeData, err := nodecorev1alpha1.ParseSolverSelector(solver.Spec.Selector) - if err != nil { - klog.Errorf("Error when parsing Solver Selector for Solver %s: %s", solver.Name, err) - return ctrl.Result{}, err + if pc.Name == "" { + klog.Errorf("No PeeringCandidate found for Solver %s:", solver.Name) + return ctrl.Result{}, nil } - if solverTypeIdentifier == nodecorev1alpha1.TypeK8Slice { - // Check if the SolverTypeData is not nil, so if the Solver specifies in the selector even filters - if solverTypeData != nil { - // Forge partition - k8sliceSelector := solverTypeData.(nodecorev1alpha1.K8SliceSelector) - klog.Infof("K8S Slice Selector: %v", k8sliceSelector) - - // Parse flavor from PeeringCandidate - flavorTypeIdentifier, flavorData, err := nodecorev1alpha1.ParseFlavorType(&pc.Spec.Flavor) - if err != nil { - klog.Errorf("Error when parsing Flavor for Solver %s: %s", solver.Name, err) - return ctrl.Result{}, err - } - if flavorTypeIdentifier != nodecorev1alpha1.TypeK8Slice { - klog.Errorf("Flavor type is different from K8Slice as expected by the Solver selector for Solver %s", solver.Name) - } - // Force casting - k8slice := flavorData.(nodecorev1alpha1.K8Slice) + if solver.Spec.Selector != nil { + // Forge the Partition - k8slicepartition := resourceforge.ForgeK8SliceConfiguration(k8sliceSelector, &k8slice) - // Convert the K8SlicePartition to JSON - k8slicepartitionJSON, err := json.Marshal(k8slicepartition) - if err != nil { - klog.Errorf("Error when marshaling K8SlicePartition for Solver %s: %s", solver.Name, err) - return ctrl.Result{}, err - } - configuration = &nodecorev1alpha1.Configuration{ - ConfigurationTypeIdentifier: solverTypeIdentifier, - // Partition Data is the raw data of the k8slicepartion - ConfigurationData: runtime.RawExtension{Raw: k8slicepartitionJSON}, + // Parse Solver Selector + solverTypeIdentifier, solverTypeData, err := nodecorev1alpha1.ParseSolverSelector(solver.Spec.Selector) + if err != nil { + klog.Errorf("Error when parsing Solver Selector for Solver %s: %s", solver.Name, err) + return ctrl.Result{}, err + } + if solverTypeIdentifier == nodecorev1alpha1.TypeK8Slice { + // Check if the SolverTypeData is not nil, so if the Solver specifies in the selector even filters + if solverTypeData != nil { + // Forge partition + k8sliceSelector := solverTypeData.(nodecorev1alpha1.K8SliceSelector) + klog.Infof("K8S Slice Selector: %v", k8sliceSelector) + + // Parse flavor from PeeringCandidate + flavorTypeIdentifier, flavorData, err := nodecorev1alpha1.ParseFlavorType(&pc.Spec.Flavor) + if err != nil { + klog.Errorf("Error when parsing Flavor for Solver %s: %s", solver.Name, err) + return ctrl.Result{}, err + } + if flavorTypeIdentifier != nodecorev1alpha1.TypeK8Slice { + klog.Errorf("Flavor type is different from K8Slice as expected by the Solver selector for Solver %s", solver.Name) + } + + // Force casting + k8slice := flavorData.(nodecorev1alpha1.K8Slice) + + k8slicepartition := resourceforge.ForgeK8SliceConfiguration(k8sliceSelector, &k8slice) + // Convert the K8SlicePartition to JSON + k8slicepartitionJSON, err := json.Marshal(k8slicepartition) + if err != nil { + klog.Errorf("Error when marshaling K8SlicePartition for Solver %s: %s", solver.Name, err) + return ctrl.Result{}, err + } + configuration = &nodecorev1alpha1.Configuration{ + ConfigurationTypeIdentifier: solverTypeIdentifier, + // Partition Data is the raw data of the k8slicepartion + ConfigurationData: runtime.RawExtension{Raw: k8slicepartitionJSON}, + } } } + // TODO: If not K8Slice, implement the other types partitions if possible } - // TODO: If not K8Slice, implement the other types partitions if possible - } - // Get the NodeIdentity - nodeIdentity := getters.GetNodeIdentity(ctx, r.Client) + // Get the NodeIdentity + nodeIdentity := getters.GetNodeIdentity(ctx, r.Client) - // Forge the Reservation - reservation := resourceforge.ForgeReservation(&pc, configuration, *nodeIdentity) - if err := r.Client.Create(ctx, reservation); err != nil { - klog.Errorf("Error when creating Reservation for Solver %s: %s", solver.Name, err) - return ctrl.Result{}, err - } + // Forge the Reservation + reservation := resourceforge.ForgeReservation(&pc, configuration, *nodeIdentity, solver.Name) + if err := r.Client.Create(ctx, reservation); err != nil { + klog.Errorf("Error when creating Reservation for Solver %s: %s", solver.Name, err) + return ctrl.Result{}, err + } - klog.Infof("Reservation %s created", reservation.Name) + klog.Infof("Reservation %s created", reservation.Name) - solver.SetReserveAndBuyStatus(nodecorev1alpha1.PhaseRunning) - solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Reservation created") - if err := r.updateSolverStatus(ctx, solver); err != nil { - klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) - return ctrl.Result{}, err + solver.SetReserveAndBuyStatus(nodecorev1alpha1.PhaseRunning) + solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Reservation created") + if err := r.updateSolverStatus(ctx, solver); err != nil { + klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) + return ctrl.Result{}, err + } } - return ctrl.Result{}, nil case nodecorev1alpha1.PhaseRunning: // Check solver expiration if tools.CheckExpirationSinceTime(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { @@ -419,15 +465,30 @@ func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Req } return ctrl.Result{}, nil } + return ctrl.Result{}, nil } -func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, solver *nodecorev1alpha1.Solver) (ctrl.Result, error) { +func contains(slice []string, s string) bool { + for _, str := range slice { + if str == s { + return true + } + } + return false +} + +func (r *SolverReconciler) handlePeering( + ctx context.Context, + req ctrl.Request, + solver *nodecorev1alpha1.Solver, + contract *reservationv1alpha1.Contract, +) (ctrl.Result, error) { peeringStatus := solver.Status.Peering switch peeringStatus { case nodecorev1alpha1.PhaseIdle: klog.Infof("Solver %s is trying to establish a peering", req.NamespacedName.Name) - contractNamespaceName := types.NamespacedName{Name: solver.Status.Contract.Name, Namespace: solver.Status.Contract.Namespace} + contractNamespaceName := types.NamespacedName{Name: contract.Name, Namespace: contract.Namespace} contract := reservationv1alpha1.Contract{} err := r.Client.Get(ctx, contractNamespaceName, &contract) if err != nil { @@ -438,19 +499,15 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, vnName := namings.ForgeVirtualNodeName(contract.Spec.PeeringTargetCredentials.ClusterName) klog.Infof("Virtual Node Name: %s", vnName) - allocation := resourceforge.ForgeAllocation(&contract, solver.Name) + allocation := resourceforge.ForgeAllocation(&contract) if err := r.Client.Create(ctx, allocation); err != nil { klog.Errorf("Error when creating Allocation for Solver %s: %s", solver.Name, err) return ctrl.Result{}, err } klog.Infof("Allocation %s created", allocation.Name) - solver.Status.Allocation = nodecorev1alpha1.GenericRef{ - Name: allocation.Name, - Namespace: allocation.Namespace, - } + solver.SetPeeringStatus(nodecorev1alpha1.PhaseRunning) solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Allocation created") - solver.Status.Credentials = contract.Spec.PeeringTargetCredentials if err := r.updateSolverStatus(ctx, solver); err != nil { klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -460,14 +517,14 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, case nodecorev1alpha1.PhaseRunning: klog.Info("Checking peering status") - fc, err := fcutils.GetForeignClusterByID(ctx, r.Client, solver.Status.Credentials.ClusterID) + fc, err := fcutils.GetForeignClusterByID(ctx, r.Client, contract.Spec.PeeringTargetCredentials.ClusterID) if err != nil { if errors.IsNotFound(err) { - klog.Infof("ForeignCluster %s not found", solver.Status.Credentials.ClusterID) + klog.Infof("ForeignCluster %s not found", contract.Spec.PeeringTargetCredentials.ClusterID) // Retry later return ctrl.Result{Requeue: true}, nil } - klog.Errorf("Error when getting ForeignCluster %s: %v", solver.Status.Credentials.ClusterID, err) + klog.Errorf("Error when getting ForeignCluster %s: %v", contract.Spec.PeeringTargetCredentials.ClusterID, err) solver.SetPeeringStatus(nodecorev1alpha1.PhaseFailed) if err := r.updateSolverStatus(ctx, solver); err != nil { klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) @@ -475,7 +532,7 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, } return ctrl.Result{}, nil } - klog.Infof("ForeignCluster %s found", solver.Status.Credentials.ClusterID) + klog.Infof("ForeignCluster %s found", contract.Spec.PeeringTargetCredentials.ClusterID) if fcutils.IsOutgoingJoined(fc) && fcutils.IsAuthenticated(fc) && fcutils.IsNetworkingEstablishedOrExternal(fc) && @@ -491,7 +548,7 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, } klog.Infof("Solver %s is still peering", req.NamespacedName.Name) // Check solver expiration - if tools.CheckExpirationSinceTime(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { + if tools.CheckExpirationSinceTime(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationSolver) { klog.Infof("Solver %s has expired", req.NamespacedName.Name) solver.SetPhase(nodecorev1alpha1.PhaseTimeout, "Solver has expired before reserving the resources") solver.SetPeeringStatus(nodecorev1alpha1.PhaseFailed) @@ -524,6 +581,42 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, } } +func (r *SolverReconciler) searchReservedPeeringCandidate(ctx context.Context, + solver *nodecorev1alpha1.Solver) (*advertisementv1alpha1.PeeringCandidate, error) { + var reservationList reservationv1alpha1.ReservationList + + // Get the Reservation from the Solver + reservation := &reservationv1alpha1.Reservation{} + + // Get reservation list and check the one that is related to the Solver + if err := r.List(ctx, &reservationList); err != nil { + klog.Errorf("Error when listing Reservations: %s", err) + return nil, err + } + + for i := range reservationList.Items { + if reservationList.Items[i].Spec.SolverID == solver.Name { + reservation = &reservationList.Items[i] + break + } + } + + if reservation.Name == "" { + klog.Infof("No Reservation found for Solver %s", solver.Name) + return nil, errors.NewNotFound(schema.GroupResource{Group: "reservation", Resource: "Reservation"}, "Reservation") + } + + // Get the PeeringCandidate from the Reservation + pc := &advertisementv1alpha1.PeeringCandidate{} + pcNamespaceName := types.NamespacedName{Name: reservation.Spec.PeeringCandidate.Name, Namespace: reservation.Namespace} + if err := r.Get(ctx, pcNamespaceName, pc); err != nil { + klog.Errorf("Error when getting PeeringCandidate for Solver %s: %s", solver.Name, err) + return nil, err + } + + return pc, nil +} + func (r *SolverReconciler) searchPeeringCandidates(ctx context.Context, solver *nodecorev1alpha1.Solver) ([]advertisementv1alpha1.PeeringCandidate, error) { pc := advertisementv1alpha1.PeeringCandidateList{} @@ -565,17 +658,14 @@ func (r *SolverReconciler) searchPeeringCandidates(ctx context.Context, return result, nil } -// TODO: unify this logic with the one of the discovery controller. -func (r *SolverReconciler) selectAndBookPeeringCandidate( - pcList []advertisementv1alpha1.PeeringCandidate) (*advertisementv1alpha1.PeeringCandidate, error) { - // Select the first PeeringCandidate - +func (r *SolverReconciler) selectAvaiablePeeringCandidate( + pcList []advertisementv1alpha1.PeeringCandidate) (*advertisementv1alpha1.PeeringCandidate, + error) { var pc *advertisementv1alpha1.PeeringCandidate for i := range pcList { pc = &pcList[i] - // Select the first PeeringCandidate that is not reserved - // TODO: A better default logic should be implemented. + if pc.Spec.Available { return pc, nil } @@ -585,7 +675,8 @@ func (r *SolverReconciler) selectAndBookPeeringCandidate( return nil, errors.NewNotFound(schema.GroupResource{Group: "advertisement", Resource: "PeeringCandidate"}, "PeeringCandidate") } -func (r *SolverReconciler) createOrGetDiscovery(ctx context.Context, solver *nodecorev1alpha1.Solver) (*advertisementv1alpha1.Discovery, error) { +func (r *SolverReconciler) createOrGetDiscovery(ctx context.Context, + solver *nodecorev1alpha1.Solver) (*advertisementv1alpha1.Discovery, error) { discovery := &advertisementv1alpha1.Discovery{} // Get the Discovery @@ -647,8 +738,7 @@ func allocationPredicate(c client.Client) predicate.Predicate { UpdateFunc: func(e event.UpdateEvent) bool { return (e.ObjectNew.(*nodecorev1alpha1.Allocation).Status.Status == nodecorev1alpha1.Active || e.ObjectNew.(*nodecorev1alpha1.Allocation).Status.Status == nodecorev1alpha1.Released) && - !IsProvider(context.Background(), e.ObjectNew.(*nodecorev1alpha1.Allocation), c) && - e.ObjectNew.(*nodecorev1alpha1.Allocation).Spec.IntentID != "" + !IsProvider(context.Background(), e.ObjectNew.(*nodecorev1alpha1.Allocation), c) }, } } @@ -678,7 +768,7 @@ func (r *SolverReconciler) reservationToSolver(_ context.Context, o client.Objec } func (r *SolverReconciler) allocationToSolver(_ context.Context, o client.Object) []reconcile.Request { - solverName := o.(*nodecorev1alpha1.Allocation).Spec.IntentID + solverName := o.(*nodecorev1alpha1.Allocation).Spec.Contract.Name return []reconcile.Request{ { NamespacedName: types.NamespacedName{ diff --git a/pkg/utils/common/common.go b/pkg/utils/common/common.go index e4926b0..1849e69 100644 --- a/pkg/utils/common/common.go +++ b/pkg/utils/common/common.go @@ -285,7 +285,6 @@ func ReservationStatusCheck(solver *nodecorev1alpha1.Solver, reservation *reserv klog.Infof("Reservation %s has reserved and purchase the flavor %s", reservation.Name, flavorName) solver.Status.ReservationPhase = nodecorev1alpha1.PhaseSolved solver.Status.ReserveAndBuy = nodecorev1alpha1.PhaseSolved - solver.Status.Contract = reservation.Status.Contract solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Reservation: Flavor reserved and purchased") } if reservation.Status.Phase.Phase == nodecorev1alpha1.PhaseFailed { diff --git a/pkg/utils/resourceforge/forge.go b/pkg/utils/resourceforge/forge.go index 2e52134..2259230 100644 --- a/pkg/utils/resourceforge/forge.go +++ b/pkg/utils/resourceforge/forge.go @@ -73,15 +73,16 @@ func ForgePeeringCandidate(flavorPeeringCandidate *nodecorev1alpha1.Flavor, Available: available, }, } - pc.Spec.SolverID = solverID + pc.Spec.InterestedSolverIDs = append(pc.Spec.InterestedSolverIDs, solverID) return } // ForgeReservation creates a Reservation CR from a PeeringCandidate. func ForgeReservation(pc *advertisementv1alpha1.PeeringCandidate, configuration *nodecorev1alpha1.Configuration, - ni nodecorev1alpha1.NodeIdentity) *reservationv1alpha1.Reservation { - solverID := pc.Spec.SolverID + ni nodecorev1alpha1.NodeIdentity, + reservingSolver string) *reservationv1alpha1.Reservation { + solverID := reservingSolver reservation := &reservationv1alpha1.Reservation{ ObjectMeta: metav1.ObjectMeta{ Name: namings.ForgeReservationName(solverID), @@ -825,14 +826,13 @@ func ForgeK8SliceConfiguration(selector nodecorev1alpha1.K8SliceSelector, flavor } // ForgeAllocation creates an Allocation from a Contract. -func ForgeAllocation(contract *reservationv1alpha1.Contract, intentID string) *nodecorev1alpha1.Allocation { +func ForgeAllocation(contract *reservationv1alpha1.Contract) *nodecorev1alpha1.Allocation { return &nodecorev1alpha1.Allocation{ ObjectMeta: metav1.ObjectMeta{ Name: namings.ForgeAllocationName(contract.Spec.Flavor.Name), Namespace: flags.FluidosNamespace, }, Spec: nodecorev1alpha1.AllocationSpec{ - IntentID: intentID, Forwarding: false, Contract: nodecorev1alpha1.GenericRef{ Name: contract.Name,