From e63b910a6414727d25b4a3d3b821218150deb046 Mon Sep 17 00:00:00 2001 From: Andrea Colli-Vignarelli <48754766+andreacv98@users.noreply.github.com> Date: Wed, 10 Jul 2024 13:51:44 +0200 Subject: [PATCH] REARv0.0.1 implementation in FLUIDOS Node workflow and data structures (#98) --- Makefile | 1 + PROJECT | 18 +- .../v1alpha1/discovery_status.go | 4 +- .../advertisement/v1alpha1/discovery_types.go | 14 +- .../v1alpha1/groupversion_info.go | 6 +- .../v1alpha1/peeringcandidate_types.go | 12 +- .../v1alpha1/zz_generated.deepcopy.go | 6 +- apis/nodecore/v1alpha1/allocation_status.go | 2 +- apis/nodecore/v1alpha1/allocation_types.go | 37 +- apis/nodecore/v1alpha1/common.go | 41 +- apis/nodecore/v1alpha1/flavor_types.go | 175 ++++ apis/nodecore/v1alpha1/flavor_types_common.go | 41 + apis/nodecore/v1alpha1/flavor_webhook.go | 109 +++ apis/nodecore/v1alpha1/flavour_types.go | 189 ----- apis/nodecore/v1alpha1/groupversion_info.go | 6 +- .../v1alpha1/k8slice_configuration.go | 31 + apis/nodecore/v1alpha1/k8slice_flavor.go | 142 ++++ apis/nodecore/v1alpha1/k8slice_selector.go | 92 ++ apis/nodecore/v1alpha1/network_intent.go | 95 +++ apis/nodecore/v1alpha1/selector.go | 152 ++++ apis/nodecore/v1alpha1/solver_status.go | 51 +- apis/nodecore/v1alpha1/solver_types.go | 111 +-- apis/nodecore/v1alpha1/solver_webhook.go | 108 +++ .../v1alpha1/zz_generated.deepcopy.go | 561 ++++++++++--- apis/reservation/v1alpha1/contract_types.go | 21 +- apis/reservation/v1alpha1/contract_webhook.go | 110 +++ .../reservation/v1alpha1/groupversion_info.go | 6 +- .../v1alpha1/reservation_status.go | 8 +- .../reservation/v1alpha1/reservation_types.go | 14 +- .../v1alpha1/reservation_webhook.go | 85 ++ .../reservation/v1alpha1/transaction_types.go | 28 +- .../v1alpha1/transaction_webhook.go | 85 ++ .../v1alpha1/zz_generated.deepcopy.go | 26 +- cmd/local-resource-manager/doc.go | 2 +- cmd/local-resource-manager/main.go | 108 ++- cmd/rear-controller/doc.go | 2 +- cmd/rear-controller/main.go | 43 +- cmd/rear-manager/doc.go | 2 +- cmd/rear-manager/main.go | 36 +- deployments/node/README.md | 15 +- .../advertisement.fluidos.eu_discoveries.yaml | 388 ++------- ...tisement.fluidos.eu_peeringcandidates.yaml | 230 ++--- .../crds/nodecore.fluidos.eu_allocations.yaml | 21 +- .../crds/nodecore.fluidos.eu_flavors.yaml | 173 ++++ .../crds/nodecore.fluidos.eu_flavours.yaml | 302 ------- .../crds/nodecore.fluidos.eu_solvers.yaml | 157 +--- .../reservation.fluidos.eu_contracts.yaml | 332 +++----- .../reservation.fluidos.eu_reservations.yaml | 68 +- .../reservation.fluidos.eu_transactions.yaml | 86 +- ...de-local-resource-manager-ClusterRole.yaml | 2 +- .../node-rear-controller-ClusterRole.yaml | 6 +- .../files/node-rear-manager-ClusterRole.yaml | 6 +- deployments/node/samples/allocation.yaml | 10 +- .../node/samples/nginx-deployment.yaml | 4 +- deployments/node/samples/reservation.yaml | 48 +- deployments/node/samples/solver-custom.yaml | 44 +- deployments/node/samples/solver.yaml | 44 +- .../node/templates/fluidos-cert-issuer.yaml | 14 + ...dos-local-resource-manager-deployment.yaml | 20 +- .../fluidos-pre-install-hook-authz.yaml | 6 +- .../fluidos-pre-install-hook-cm.yaml | 2 +- .../fluidos-pre-install-hook-job.yaml | 2 +- .../fluidos-rear-controller-deployment.yaml | 10 + .../fluidos-rear-manager-deployment.yaml | 11 + ...os-local-resource-manager-certificate.yaml | 18 + ...cal-resource-manager-mutating-webhook.yaml | 29 + ...l-resource-manager-validating-webhook.yaml | 51 ++ ...ocal-resource-manager-webhook-service.yaml | 15 + .../fluidos-rear-controller-certificate.yaml | 17 + ...idos-rear-controller-mutating-webhook.yaml | 68 ++ ...os-rear-controller-validating-webhook.yaml | 69 ++ ...uidos-rear-controller-webhook-service.yaml | 15 + .../fluidos-rear-manager-certificate.yaml} | 21 +- ...fluidos-rear-manager-mutating-webhook.yaml | 29 + ...uidos-rear-manager-validating-webhook.yaml | 51 ++ .../fluidos-rear-manager-webhook-service.yaml | 15 + deployments/node/values.yaml | 27 +- docs/usage/usage.md | 126 ++- go.mod | 12 +- go.sum | 32 +- hack/boilerplate.go.txt | 2 +- .../controller_manager.go | 195 ----- pkg/local-resource-manager/doc.go | 2 +- pkg/local-resource-manager/node_controller.go | 183 ++++ pkg/local-resource-manager/node_services.go | 83 +- pkg/rear-controller/contract-manager/doc.go | 2 +- .../contract-manager/models.go | 21 +- .../reservation_controller.go | 76 +- .../discovery-manager/const.go | 27 - .../discovery-manager/discovery_controller.go | 23 +- pkg/rear-controller/discovery-manager/doc.go | 2 +- .../discovery-manager/peeringcandidate_wh.go | 2 +- pkg/rear-controller/doc.go | 4 +- pkg/rear-controller/gateway/client.go | 111 +-- pkg/rear-controller/gateway/doc.go | 2 +- pkg/rear-controller/gateway/gateway.go | 45 +- pkg/rear-controller/gateway/provider.go | 274 +++--- pkg/rear-controller/gateway/routes.go | 41 + pkg/rear-controller/gateway/services.go | 69 +- pkg/rear-controller/gateway/utils.go | 402 ++++++++- pkg/rear-controller/grpc/doc.go | 2 +- .../grpc/liqo-resource-manager.go | 4 +- pkg/rear-controller/grpc/service.go | 98 ++- pkg/rear-manager/allocation_controller.go | 393 ++++++--- pkg/rear-manager/allocation_wh.go | 2 +- pkg/rear-manager/doc.go | 2 +- pkg/rear-manager/solver_controller.go | 150 +++- pkg/utils/common/common.go | 286 ++++--- pkg/utils/common/doc.go | 2 +- pkg/utils/consts/consts.go | 2 +- pkg/utils/consts/doc.go | 2 +- pkg/utils/flags/doc.go | 2 +- pkg/utils/flags/flags.go | 4 +- pkg/utils/getters/doc.go | 2 +- pkg/utils/getters/getters.go | 8 +- pkg/utils/models/doc.go | 2 +- pkg/utils/models/gateway.go | 24 +- pkg/utils/models/k8slice-models.go | 86 ++ pkg/utils/models/local-resource-manager.go | 12 +- pkg/utils/models/models.go | 303 +++++-- pkg/utils/models/network-identity-models.go | 68 ++ pkg/utils/models/reservation.go | 79 +- pkg/utils/namings/doc.go | 2 +- pkg/utils/namings/namings.go | 43 +- pkg/utils/parseutil/doc.go | 2 +- pkg/utils/parseutil/parseutil.go | 627 +++++++++++--- pkg/utils/resourceforge/doc.go | 2 +- pkg/utils/resourceforge/forge.go | 785 +++++++++++++----- pkg/utils/services/doc.go | 2 +- pkg/utils/services/flavours_services.go | 42 +- pkg/utils/tools/doc.go | 2 +- pkg/utils/tools/tools.go | 25 +- pkg/virtual-fabric-manager/doc.go | 2 +- pkg/virtual-fabric-manager/services.go | 2 +- ...-nolrm.yaml => consumer-values-no-ad.yaml} | 14 +- quickstart/utils/consumer-values.yaml | 12 + ...-nolrm.yaml => provider-values-no-ad.yaml} | 15 +- quickstart/utils/provider-values.yaml | 12 + testbed/kind/README.md | 10 +- tools/scripts/install_liqo.sh | 3 + tools/scripts/installation.sh | 12 +- tools/scripts/setup.sh | 14 +- 142 files changed, 6550 insertions(+), 3353 deletions(-) create mode 100644 apis/nodecore/v1alpha1/flavor_types.go create mode 100644 apis/nodecore/v1alpha1/flavor_types_common.go create mode 100644 apis/nodecore/v1alpha1/flavor_webhook.go delete mode 100644 apis/nodecore/v1alpha1/flavour_types.go create mode 100644 apis/nodecore/v1alpha1/k8slice_configuration.go create mode 100644 apis/nodecore/v1alpha1/k8slice_flavor.go create mode 100644 apis/nodecore/v1alpha1/k8slice_selector.go create mode 100644 apis/nodecore/v1alpha1/network_intent.go create mode 100644 apis/nodecore/v1alpha1/selector.go create mode 100644 apis/nodecore/v1alpha1/solver_webhook.go create mode 100644 apis/reservation/v1alpha1/contract_webhook.go create mode 100644 apis/reservation/v1alpha1/reservation_webhook.go create mode 100644 apis/reservation/v1alpha1/transaction_webhook.go create mode 100644 deployments/node/crds/nodecore.fluidos.eu_flavors.yaml delete mode 100644 deployments/node/crds/nodecore.fluidos.eu_flavours.yaml create mode 100644 deployments/node/templates/fluidos-cert-issuer.yaml create mode 100644 deployments/node/templates/webhook/fluidos-local-resource-manager-certificate.yaml create mode 100644 deployments/node/templates/webhook/fluidos-local-resource-manager-mutating-webhook.yaml create mode 100644 deployments/node/templates/webhook/fluidos-local-resource-manager-validating-webhook.yaml create mode 100644 deployments/node/templates/webhook/fluidos-local-resource-manager-webhook-service.yaml create mode 100644 deployments/node/templates/webhook/fluidos-rear-controller-certificate.yaml create mode 100644 deployments/node/templates/webhook/fluidos-rear-controller-mutating-webhook.yaml create mode 100644 deployments/node/templates/webhook/fluidos-rear-controller-validating-webhook.yaml create mode 100644 deployments/node/templates/webhook/fluidos-rear-controller-webhook-service.yaml rename deployments/node/templates/{fluidos-pre-install-issuer-and-cert.yaml => webhook/fluidos-rear-manager-certificate.yaml} (59%) create mode 100644 deployments/node/templates/webhook/fluidos-rear-manager-mutating-webhook.yaml create mode 100644 deployments/node/templates/webhook/fluidos-rear-manager-validating-webhook.yaml create mode 100644 deployments/node/templates/webhook/fluidos-rear-manager-webhook-service.yaml delete mode 100644 pkg/local-resource-manager/controller_manager.go create mode 100644 pkg/local-resource-manager/node_controller.go delete mode 100644 pkg/rear-controller/discovery-manager/const.go create mode 100644 pkg/rear-controller/gateway/routes.go create mode 100644 pkg/utils/models/k8slice-models.go create mode 100644 pkg/utils/models/network-identity-models.go rename quickstart/utils/{consumer-values-nolrm.yaml => consumer-values-no-ad.yaml} (93%) rename quickstart/utils/{provider-values-nolrm.yaml => provider-values-no-ad.yaml} (93%) mode change 100644 => 100755 tools/scripts/setup.sh diff --git a/Makefile b/Makefile index 0d3826b..8f326fc 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ docs: helm-docs manifests: controller-gen rm -f deployments/node/crds/* $(CONTROLLER_GEN) paths="./apis/..." crd:generateEmbeddedObjectMeta=true output:crd:artifacts:config=deployments/node/crds + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases #Generate RBAC for each controller rbacs: controller-gen diff --git a/PROJECT b/PROJECT index 644a774..1505048 100644 --- a/PROJECT +++ b/PROJECT @@ -1,4 +1,8 @@ -domain: github.com/fluidos-project/ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +domain: fluidos.eu layout: - go.kubebuilder.io/v3 projectName: node @@ -8,7 +12,7 @@ resources: crdVersion: v1 namespaced: true controller: true - domain: github.com/fluidos-project/ + domain: fluidos.eu group: nodecore kind: Solver path: github.com/fluidos-project/node/api/v1alpha1 @@ -17,16 +21,20 @@ resources: crdVersion: v1 namespaced: true controller: true - domain: github.com/fluidos-project/ + domain: fluidos.eu group: nodecore - kind: Flavour + kind: Flavor path: github.com/fluidos-project/node/api/v1alpha1 version: v1alpha1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true controller: true - domain: github.com/fluidos-project/ + domain: fluidos.eu group: nodecore kind: Allocation path: github.com/fluidos-project/node/api/v1alpha1 diff --git a/apis/advertisement/v1alpha1/discovery_status.go b/apis/advertisement/v1alpha1/discovery_status.go index 8f2e5c5..18edfce 100644 --- a/apis/advertisement/v1alpha1/discovery_status.go +++ b/apis/advertisement/v1alpha1/discovery_status.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import ( "github.com/fluidos-project/node/pkg/utils/tools" ) -// SetPhase sets the phase of the discovery +// SetPhase sets the phase of the discovery. func (d *Discovery) SetPhase(phase nodecorev1alpha1.Phase, msg string) { d.Status.Phase.Phase = phase d.Status.Phase.LastChangeTime = tools.GetTimeNow() diff --git a/apis/advertisement/v1alpha1/discovery_types.go b/apis/advertisement/v1alpha1/discovery_types.go index 0338886..fca69d8 100644 --- a/apis/advertisement/v1alpha1/discovery_types.go +++ b/apis/advertisement/v1alpha1/discovery_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. -// DiscoverySpec defines the desired state of Discovery +// DiscoverySpec defines the desired state of Discovery. type DiscoverySpec struct { // This is the Solver ID of the solver that creates and so asks for the discovery. @@ -32,14 +32,14 @@ type DiscoverySpec struct { // This is the FlavourSelector that describes the characteristics of the intent that the solver is looking to satisfy // This pattern corresponds to what has been defined in the REAR Protocol to do a discovery with a selector - Selector *nodecorev1alpha1.FlavourSelector `json:"selector"` + Selector *nodecorev1alpha1.Selector `json:"selector"` // This flag indicates that needs to be established a subscription to the provider in case a match is found. - // In order to have periodic updates of the status of the matching Flavour + // In order to have periodic updates of the status of the matching Flavor Subscribe bool `json:"subscribe"` } -// DiscoveryStatus defines the observed state of Discovery +// DiscoveryStatus defines the observed state of Discovery. type DiscoveryStatus struct { // This is the current phase of the discovery @@ -52,13 +52,13 @@ type DiscoveryStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status +// Discovery is the Schema for the discoveries API. // +kubebuilder:printcolumn:name="Solver ID",type=string,JSONPath=`.spec.solverID` // +kubebuilder:printcolumn:name="Subscribe",type=boolean,JSONPath=`.spec.subscribe` // +kubebuilder:printcolumn:name="PC Namespace",type=string,JSONPath=`.status.peeringCandidate.namespace` // +kubebuilder:printcolumn:name="PC Name",type=string,JSONPath=`.status.peeringCandidate.name` // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase.phase` // +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.phase.message` -// Discovery is the Schema for the discoveries API type Discovery struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -69,7 +69,7 @@ type Discovery struct { //+kubebuilder:object:root=true -// DiscoveryList contains a list of Discovery +// DiscoveryList contains a list of Discovery. type DiscoveryList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/advertisement/v1alpha1/groupversion_info.go b/apis/advertisement/v1alpha1/groupversion_info.go index 3775192..ff5eddc 100644 --- a/apis/advertisement/v1alpha1/groupversion_info.go +++ b/apis/advertisement/v1alpha1/groupversion_info.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,10 +23,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "advertisement.fluidos.eu", Version: "v1alpha1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/apis/advertisement/v1alpha1/peeringcandidate_types.go b/apis/advertisement/v1alpha1/peeringcandidate_types.go index 94fdd29..3d277a1 100644 --- a/apis/advertisement/v1alpha1/peeringcandidate_types.go +++ b/apis/advertisement/v1alpha1/peeringcandidate_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,16 +23,16 @@ import ( // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. -// PeeringCandidateSpec defines the desired state of PeeringCandidate +// PeeringCandidateSpec defines the desired state of PeeringCandidate. type PeeringCandidateSpec struct { SolverID string `json:"solverID"` - Flavour nodecorev1alpha1.Flavour `json:"flavour"` + Flavor nodecorev1alpha1.Flavor `json:"flavor"` Available bool `json:"available"` } -// PeeringCandidateStatus defines the observed state of PeeringCandidate +// PeeringCandidateStatus defines the observed state of PeeringCandidate. type PeeringCandidateStatus struct { // This field represents the creation time of the PeeringCandidate. @@ -45,7 +45,7 @@ type PeeringCandidateStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// PeeringCandidate is the Schema for the peeringcandidates API +// PeeringCandidate is the Schema for the peeringcandidates API. type PeeringCandidate struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -56,7 +56,7 @@ type PeeringCandidate struct { //+kubebuilder:object:root=true -// PeeringCandidateList contains a list of PeeringCandidate +// PeeringCandidateList contains a list of PeeringCandidate. type PeeringCandidateList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/advertisement/v1alpha1/zz_generated.deepcopy.go b/apis/advertisement/v1alpha1/zz_generated.deepcopy.go index b41f871..1f85594 100644 --- a/apis/advertisement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/advertisement/v1alpha1/zz_generated.deepcopy.go @@ -1,6 +1,6 @@ //go:build !ignore_autogenerated -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -87,7 +87,7 @@ func (in *DiscoverySpec) DeepCopyInto(out *DiscoverySpec) { *out = *in if in.Selector != nil { in, out := &in.Selector, &out.Selector - *out = new(nodecorev1alpha1.FlavourSelector) + *out = new(nodecorev1alpha1.Selector) (*in).DeepCopyInto(*out) } } @@ -181,7 +181,7 @@ 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 - in.Flavour.DeepCopyInto(&out.Flavour) + in.Flavor.DeepCopyInto(&out.Flavor) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PeeringCandidateSpec. diff --git a/apis/nodecore/v1alpha1/allocation_status.go b/apis/nodecore/v1alpha1/allocation_status.go index 52f7e31..4c0f744 100644 --- a/apis/nodecore/v1alpha1/allocation_status.go +++ b/apis/nodecore/v1alpha1/allocation_status.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/apis/nodecore/v1alpha1/allocation_types.go b/apis/nodecore/v1alpha1/allocation_types.go index 9f52dfd..63a1d7a 100644 --- a/apis/nodecore/v1alpha1/allocation_types.go +++ b/apis/nodecore/v1alpha1/allocation_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,21 +18,9 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -//nolint:revive // Do not need to repeat the same comment -type NodeType string - //nolint:revive // Do not need to repeat the same comment type Status string -//nolint:revive // Do not need to repeat the same comment -type Destination string - -// NodeType is the type of the node: Node (Physical node of the cluster) or VirtualNode (Remote node owned by a different cluster). -const ( - Node NodeType = "Node" - VirtualNode NodeType = "VirtualNode" -) - // Status is the status of the allocation. const ( Active Status = "Active" @@ -42,31 +30,12 @@ const ( Error Status = "Error" ) -// Destination is the destination of the allocation: Local (the allocation will be used locally) -// or Remote (the allocation will be used from a remote cluster). -const ( - Remote Destination = "Remote" - Local Destination = "Local" -) - -// AllocationSpec defines the desired state of Allocation +// AllocationSpec defines the desired state of Allocation. type AllocationSpec struct { - // This is the ID of the cluster that owns the allocation. - RemoteClusterID string `json:"remoteClusterID,omitempty"` - // 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 is the corresponding Node or VirtualNode local name - NodeName string `json:"nodeName"` - - // 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"` @@ -102,7 +71,7 @@ type Allocation struct { //+kubebuilder:object:root=true -// AllocationList contains a list of Allocation +// AllocationList contains a list of Allocation. type AllocationList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/nodecore/v1alpha1/common.go b/apis/nodecore/v1alpha1/common.go index 6b23b17..763d3fc 100644 --- a/apis/nodecore/v1alpha1/common.go +++ b/apis/nodecore/v1alpha1/common.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,7 +14,12 @@ package v1alpha1 -import "k8s.io/apimachinery/pkg/api/resource" +import ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" +) // Set of constants for the phases of the FLUIDOS Node modules. const ( @@ -45,17 +50,15 @@ type NodeIdentity struct { Domain string `json:"domain"` NodeID string `json:"nodeID"` IP string `json:"ip"` + LiqoID string `json:"liqoID,omitempty"` } -// Partition is the partition of the flavour. -type Partition struct { - Architecture string `json:"architecture"` - CPU resource.Quantity `json:"cpu"` - Memory resource.Quantity `json:"memory"` - Pods resource.Quantity `json:"pods"` - Gpu resource.Quantity `json:"gpu,omitempty"` - EphemeralStorage resource.Quantity `json:"ephemeral-storage,omitempty"` - Storage resource.Quantity `json:"storage,omitempty"` +// Configuration represents the configuration of a FLUIDOS Node. +type Configuration struct { + // Identifier is the identifier of the configuration. + ConfigurationTypeIdentifier FlavorTypeIdentifier `json:"type"` + // ConfigurationData is the data of the configuration. + ConfigurationData runtime.RawExtension `json:"data"` } // LiqoCredentials contains the credentials of a Liqo cluster to enstablish a peering. @@ -65,3 +68,19 @@ type LiqoCredentials struct { Token string `json:"token"` Endpoint string `json:"endpoint"` } + +// ParseConfiguration parses the configuration data into the correct type. +// Returns the FlavorTypeIdentifier, aka the ConfigurationTypeIdentifier and the configuration data. +func ParseConfiguration(p *Configuration) (FlavorTypeIdentifier, interface{}, error) { + var validationError error + + switch p.ConfigurationTypeIdentifier { + case TypeK8Slice: + var partition K8SliceConfiguration + validationError = json.Unmarshal(p.ConfigurationData.Raw, &partition) + return TypeK8Slice, partition, validationError + // TODO: implement other type of partition (if any) + default: + return "", nil, fmt.Errorf("partition type %s not supported", p.ConfigurationTypeIdentifier) + } +} diff --git a/apis/nodecore/v1alpha1/flavor_types.go b/apis/nodecore/v1alpha1/flavor_types.go new file mode 100644 index 0000000..6c1de2c --- /dev/null +++ b/apis/nodecore/v1alpha1/flavor_types.go @@ -0,0 +1,175 @@ +// Copyright 2022-2024 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 ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + // TypeK8Slice is the type of a K8Slice Flavor. + TypeK8Slice FlavorTypeIdentifier = "K8Slice" + // TypeVM is the type of a VM Flavor. + TypeVM FlavorTypeIdentifier = "VM" + // TypeService is the type of a Service Flavor. + TypeService FlavorTypeIdentifier = "Service" + // TypeSensor is the type of a Sensor Flavor. + TypeSensor FlavorTypeIdentifier = "Sensor" +) + +// FlavorTypeIdentifier is the identifier of a Flavor type. +type FlavorTypeIdentifier string + +// FlavorType represents the type of a Flavor. +type FlavorType struct { + // Type of the Flavor. + TypeIdentifier FlavorTypeIdentifier `json:"typeIdentifier"` + // Raw is the raw value of the Flavor. + TypeData runtime.RawExtension `json:"typeData"` +} + +// Price represents the price of a Flavor. +type Price struct { + // Amount is the amount of the price. + Amount string `json:"amount"` + + // Currency is the currency of the price. + Currency string `json:"currency"` + + // Period is the period of the price. + Period string `json:"period"` +} + +// Location represents the location of a Flavor. +type Location struct { + // Latitude is the latitude of the location. + Latitude string `json:"latitude,omitempty"` + + // Longitude is the longitude of the location. + Longitude string `json:"longitude,omitempty"` + + // Country is the country of the location. + Country string `json:"country,omitempty"` + + // City is the city of the location. + City string `json:"city,omitempty"` + + // AdditionalNotes are additional notes of the location. + AdditionalNotes string `json:"additionalNotes,omitempty"` +} + +// FlavorSpec defines the desired state of Flavor. +type FlavorSpec struct { + // This specs are based on the REAR Protocol specifications. + + // ProviderID is the ID of the FLUIDOS Node ID that provides this Flavor. + // It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain + ProviderID string `json:"providerID"` + + // FlavorType is the type of the Flavor. + FlavorType FlavorType `json:"flavorType"` + + // Owner contains the identity info of the owner of the Flavor. It can be unknown if the Flavor is provided by a reseller or a third party. + Owner NodeIdentity `json:"owner"` + + // Price contains the price model of the Flavor. + Price Price `json:"price"` + + // Availability is the availability flag of the Flavor. + Availability bool `json:"availability"` + + // NetworkPropertyType is the network property type of the Flavor. + NetworkPropertyType string `json:"networkPropertyType,omitempty"` + + // Location is the location of the Flavor. + Location *Location `json:"location,omitempty"` +} + +// FlavorStatus defines the observed state of Flavor. +type FlavorStatus struct { + + // This field represents the expiration time of the Flavor. It is used to determine when the Flavor is no longer valid. + ExpirationTime string `json:"expirationTime"` + + // This field represents the creation time of the Flavor. + CreationTime string `json:"creationTime"` + + // This field represents the last update time of the Flavor. + LastUpdateTime string `json:"lastUpdateTime"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Flavor is the Schema for the flavors API. +// +kubebuilder:printcolumn:name="Provider ID",type=string,JSONPath=`.spec.providerID` +// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.flavorType.typeIdentifier` +// +kubebuilder:printcolumn:name="Owner Name",type=string,priority=1,JSONPath=`.spec.owner.nodeID` +// +kubebuilder:printcolumn:name="Available",type=boolean,JSONPath=`.spec.availability` +// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` +// +kubebuilder:printcolumn:name="Kubernetes Node Owner",type=string,JSONPath=`.metadata.ownerReferences[0].name` +// +kubebuilder:resource:shortName=fl +type Flavor struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FlavorSpec `json:"spec,omitempty"` + Status FlavorStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// FlavorList contains a list of Flavor. +type FlavorList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Flavor `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Flavor{}, &FlavorList{}) +} + +// ParseFlavorType parses a Flavor into a the type and the unmarsheled raw value. +func ParseFlavorType(flavor *Flavor) (FlavorTypeIdentifier, interface{}, error) { + var validationErr error + + switch flavor.Spec.FlavorType.TypeIdentifier { + case TypeK8Slice: + + var k8slice *K8Slice + // Parse K8Slice flavor + k8slice, err := ParseK8SliceFlavor(flavor.Spec.FlavorType) + if err != nil { + return "", nil, err + } + + return TypeK8Slice, *k8slice, validationErr + + case TypeVM: + // TODO: Implement VM flavor parsing + return "", nil, fmt.Errorf("flavor type %s not supported", flavor.Spec.FlavorType.TypeIdentifier) + + case TypeService: + // TODO: Implement Service flavor parsing + return "", nil, fmt.Errorf("flavor type %s not supported", flavor.Spec.FlavorType.TypeIdentifier) + + default: + return "", nil, fmt.Errorf("flavor type %s not supported", flavor.Spec.FlavorType.TypeIdentifier) + } +} diff --git a/apis/nodecore/v1alpha1/flavor_types_common.go b/apis/nodecore/v1alpha1/flavor_types_common.go new file mode 100644 index 0000000..5b576e0 --- /dev/null +++ b/apis/nodecore/v1alpha1/flavor_types_common.go @@ -0,0 +1,41 @@ +// Copyright 2022-2024 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 + +// CarbonFootprint represents the carbon footprint of a Flavor. +type CarbonFootprint struct { + Embodied int `json:"embodied"` + Operational []int `json:"operational"` +} + +// NetworkAuthorizations represents the network authorization of a Flavor. +type NetworkAuthorizations struct { + // DeniedCommunications represents the network communications that are denied by the K8Slice Flavor. + DeniedCommunications []NetworkIntent `json:"deniedCommunications"` + // MandatoryCommunications represents the network communications that are mandatory by the K8Slice Flavor. + MandatoryCommunications []NetworkIntent `json:"mandatoryCommunications"` +} + +// Properties represents the properties of a Flavor. +type Properties struct { + // Latency to reach the K8Slice Flavor + Latency int `json:"latency,omitempty"` + // Security standards complied by the K8Slice Flavor + SecurityStandards []string `json:"securityStandards,omitempty"` + // Carbon footprint of the K8Slice Flavor + CarbonFootprint *CarbonFootprint `json:"carbon-footprint,omitempty"` + // Network authorization policies of the K8Slice Flavor + NetworkAuthorizations *NetworkAuthorizations `json:"networkAuthorizations,omitempty"` +} diff --git a/apis/nodecore/v1alpha1/flavor_webhook.go b/apis/nodecore/v1alpha1/flavor_webhook.go new file mode 100644 index 0000000..e3a7a4c --- /dev/null +++ b/apis/nodecore/v1alpha1/flavor_webhook.go @@ -0,0 +1,109 @@ +// Copyright 2022-2024 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 ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var flavorlog = logf.Log.WithName("flavor-resource") + +// SetupWebhookWithManager setups the webhooks for the Flavor resource with the manager. +func (r *Flavor) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/mutate-nodecore-fluidos-eu-v1alpha1-flavor,mutating=true,failurePolicy=fail,sideEffects=None,groups=nodecore.fluidos.eu,resources=flavors,verbs=create;update,versions=v1alpha1,name=mflavor.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Flavor{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *Flavor) Default() { + flavorlog.Info("DEFAULT WEBHOOK") + flavorlog.Info("default", "name", r.Name) +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/validate-nodecore-fluidos-eu-v1alpha1-flavor,mutating=false,failurePolicy=fail,sideEffects=None,groups=nodecore.fluidos.eu,resources=flavors,verbs=create;update,versions=v1alpha1,name=vflavor.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Flavor{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *Flavor) ValidateCreate() (admission.Warnings, error) { + flavorlog.Info("VALIDATE CREATE WEBHOOK") + flavorlog.Info("validate create", "name", r.Name) + + // Validate creation of Flavor checking FlavorType->TypeIdenfier matches the struct inside the FlavorType->TypeData + typeIdenfier, _, err := ParseFlavorType(r) + if err != nil { + return nil, err + } + switch typeIdenfier { + case TypeK8Slice: + flavorlog.Info("FlavorTypeIdentifier is K8Slice") + case TypeVM: + flavorlog.Info("FlavorTypeIdentifier is VM") + case TypeService: + flavorlog.Info("FlavorTypeIdentifier is Service") + default: + flavorlog.Info("FlavorTypeIdentifier is not valid") + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *Flavor) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + flavorlog.Info("VALIDATE UPDATE WEBHOOK") + flavorlog.Info("validate update", "name", r.Name) + + flavorlog.Info("old", "old", old) + + // Validate creation of Flavor checking FlavorType->TypeIdenfier matches the struct inside the FlavorType->TypeData + typeIdenfier, _, err := ParseFlavorType(r) + if err != nil { + return nil, err + } + switch typeIdenfier { + case TypeK8Slice: + flavorlog.Info("FlavorTypeIdentifier is K8Slice") + case TypeVM: + flavorlog.Info("FlavorTypeIdentifier is VM") + case TypeService: + flavorlog.Info("FlavorTypeIdentifier is Service") + default: + flavorlog.Info("FlavorTypeIdentifier is not valid") + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *Flavor) ValidateDelete() (admission.Warnings, error) { + flavorlog.Info("VALIDATE DELETE WEBHOOK") + flavorlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} diff --git a/apis/nodecore/v1alpha1/flavour_types.go b/apis/nodecore/v1alpha1/flavour_types.go deleted file mode 100644 index 83ac7d4..0000000 --- a/apis/nodecore/v1alpha1/flavour_types.go +++ /dev/null @@ -1,189 +0,0 @@ -// 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 ( - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - K8S FlavourType = "k8s-fluidos" -) - -type FlavourType string - -type Characteristics struct { - - // Architecture is the architecture of the Flavour. - Architecture string `json:"architecture"` - - // CPU is the number of CPU cores of the Flavour. - Cpu resource.Quantity `json:"cpu"` - - // Memory is the amount of RAM of the Flavour. - Memory resource.Quantity `json:"memory"` - - // Pods is the maximum number of pods of the Flavour. - Pods resource.Quantity `json:"pods"` - - // GPU is the number of GPU cores of the Flavour. - Gpu resource.Quantity `json:"gpu,omitempty"` - - // EphemeralStorage is the amount of ephemeral storage of the Flavour. - EphemeralStorage resource.Quantity `json:"ephemeral-storage,omitempty"` - - // PersistentStorage is the amount of persistent storage of the Flavour. - PersistentStorage resource.Quantity `json:"persistent-storage,omitempty"` -} - -type Policy struct { - - // Partitionable contains the partitioning properties of the Flavour. - Partitionable *Partitionable `json:"partitionable,omitempty"` - - // Aggregatable contains the aggregation properties of the Flavour. - Aggregatable *Aggregatable `json:"aggregatable,omitempty"` -} - -// Partitionable represents the partitioning properties of a Flavour, such as the minimum and incremental values of CPU and RAM. -type Partitionable struct { - // CpuMin is the minimum requirable number of CPU cores of the Flavour. - CpuMin resource.Quantity `json:"cpuMin"` - - // MemoryMin is the minimum requirable amount of RAM of the Flavour. - MemoryMin resource.Quantity `json:"memoryMin"` - - // PodsMin is the minimum requirable number of pods of the Flavour. - PodsMin resource.Quantity `json:"podsMin"` - - // CpuStep is the incremental value of CPU cores of the Flavour. - CpuStep resource.Quantity `json:"cpuStep"` - - // MemoryStep is the incremental value of RAM of the Flavour. - MemoryStep resource.Quantity `json:"memoryStep"` - - // PodsStep is the incremental value of pods of the Flavour. - PodsStep resource.Quantity `json:"podsStep"` -} - -// Aggregatable represents the aggregation properties of a Flavour, such as the minimum instance count. -type Aggregatable struct { - // MinCount is the minimum requirable number of instances of the Flavour. - MinCount int `json:"minCount"` - - // MaxCount is the maximum requirable number of instances of the Flavour. - MaxCount int `json:"maxCount"` -} - -type Price struct { - - // Amount is the amount of the price. - Amount string `json:"amount"` - - // Currency is the currency of the price. - Currency string `json:"currency"` - - // Period is the period of the price. - Period string `json:"period"` -} - -type OptionalFields struct { - - // Availability is the availability flag of the Flavour. - // It is a field inherited from the REAR Protocol specifications. - Availability bool `json:"availability,omitempty"` - - // WorkerID is the ID of the worker that provides the Flavour. - WorkerID string `json:"workerID,omitempty"` -} - -// FlavourSpec defines the desired state of Flavour -type FlavourSpec struct { - // This specs are based on the REAR Protocol specifications. - - // ProviderID is the ID of the FLUIDOS Node ID that provides this Flavour. - // It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain - ProviderID string `json:"providerID"` - - // Type is the type of the Flavour. Currently, only K8S is supported. - Type FlavourType `json:"type"` - - // Characteristics contains the characteristics of the Flavour. - // They are based on the type of the Flavour and can change depending on it. In this case, the type is K8S so the characteristics are CPU, Memory, GPU and EphemeralStorage. - Characteristics Characteristics `json:"characteristics"` - - // Policy contains the policy of the Flavour. The policy describes the partitioning and aggregation properties of the Flavour. - Policy Policy `json:"policy"` - - // Owner contains the identity info of the owner of the Flavour. It can be unknown if the Flavour is provided by a reseller or a third party. - Owner NodeIdentity `json:"owner"` - - // Price contains the price model of the Flavour. - Price Price `json:"price"` - - // This field is used to specify the optional fields that can be retrieved from the Flavour. - // In the future it will be expanded to include more optional fields defined in the REAR Protocol or custom ones. - OptionalFields OptionalFields `json:"optionalFields"` - - // This field is used to represent the available quantity of transactions of the Flavour. - QuantityAvailable int `json:"quantityAvailable,omitempty"` -} - -// FlavourStatus defines the observed state of Flavour. -type FlavourStatus struct { - - // This field represents the expiration time of the Flavour. It is used to determine when the Flavour is no longer valid. - ExpirationTime string `json:"expirationTime"` - - // This field represents the creation time of the Flavour. - CreationTime string `json:"creationTime"` - - // This field represents the last update time of the Flavour. - LastUpdateTime string `json:"lastUpdateTime"` -} - -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status - -// +kubebuilder:printcolumn:name="Provider ID",type=string,JSONPath=`.spec.providerID` -// +kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type` -// +kubebuilder:printcolumn:name="CPU",type=string,priority=1,JSONPath=`.spec.characteristics.cpu` -// +kubebuilder:printcolumn:name="Memory",type=string,priority=1,JSONPath=`.spec.characteristics.memory` -// +kubebuilder:printcolumn:name="Owner Name",type=string,priority=1,JSONPath=`.spec.owner.nodeID` -// +kubebuilder:printcolumn:name="Owner Domain",type=string,priority=1,JSONPath=`.spec.owner.domain` -// +kubebuilder:printcolumn:name="Available",type=boolean,JSONPath=`.spec.optionalFields.availability` -// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// Flavour is the Schema for the flavours API. -type Flavour struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec FlavourSpec `json:"spec,omitempty"` - Status FlavourStatus `json:"status,omitempty"` -} - -//+kubebuilder:object:root=true - -// FlavourList contains a list of Flavour -type FlavourList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Flavour `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Flavour{}, &FlavourList{}) -} diff --git a/apis/nodecore/v1alpha1/groupversion_info.go b/apis/nodecore/v1alpha1/groupversion_info.go index 760cfc7..b551dfd 100644 --- a/apis/nodecore/v1alpha1/groupversion_info.go +++ b/apis/nodecore/v1alpha1/groupversion_info.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,10 +23,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "nodecore.fluidos.eu", Version: "v1alpha1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/apis/nodecore/v1alpha1/k8slice_configuration.go b/apis/nodecore/v1alpha1/k8slice_configuration.go new file mode 100644 index 0000000..aa0e646 --- /dev/null +++ b/apis/nodecore/v1alpha1/k8slice_configuration.go @@ -0,0 +1,31 @@ +// Copyright 2022-2024 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 "k8s.io/apimachinery/pkg/api/resource" + +// K8SliceConfiguration is the partition of the flavor K8Slice. +type K8SliceConfiguration struct { + // CPU is the CPU of the K8Slice partition. + CPU resource.Quantity `json:"cpu"` + // Memory is the Memory of the K8Slice partition. + Memory resource.Quantity `json:"memory"` + // Pods is the Pods of the K8Slice partition. + Pods resource.Quantity `json:"pods"` + // Gpu is the GPU of the K8Slice partition. + Gpu *GPU `json:"gpu,omitempty"` + // Storage is the Storage of the K8Slice partition. + Storage *resource.Quantity `json:"storage,omitempty"` +} diff --git a/apis/nodecore/v1alpha1/k8slice_flavor.go b/apis/nodecore/v1alpha1/k8slice_flavor.go new file mode 100644 index 0000000..98da38e --- /dev/null +++ b/apis/nodecore/v1alpha1/k8slice_flavor.go @@ -0,0 +1,142 @@ +// Copyright 2022-2024 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 ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/api/resource" +) + +// K8Slice represents a K8Slice Flavor. +type K8Slice struct { + // Characteristics of the K8Slice Flavor + Characteristics K8SliceCharacteristics `json:"characteristics"` + // Properties of the K8Slice Flavor + Properties Properties `json:"properties"` + // Policies of the K8Slice Flavor + Policies Policies `json:"policies"` +} + +// GetFlavorType returns the type of the Flavor. +func (k8s *K8Slice) GetFlavorType() FlavorTypeIdentifier { + return TypeK8Slice +} + +// K8SliceCharacteristics represents the characteristics of a K8Slice Flavor, such as the CPU, RAM, and storage. +type K8SliceCharacteristics struct { + // Architecture is the architecture of the K8Slice Flavor. + Architecture string `json:"architecture"` + // CPU is the number of CPU cores of the K8Slice Flavor. + CPU resource.Quantity `json:"cpu"` + // Memory is the amount of RAM of the K8Slice Flavor. + Memory resource.Quantity `json:"memory"` + // Pods is the maximum number of pods schedulable on this K8Slice Flavor. + Pods resource.Quantity `json:"pods"` + // GPU is the number of GPU cores of the K8Slice Flavor. + Gpu *GPU `json:"gpu,omitempty"` + // Storage is the amount of storage offered by this K8Slice Flavor. + Storage *resource.Quantity `json:"storage,omitempty"` +} + +// GPU represents the GPU characteristics of a K8Slice Flavor. +type GPU struct { + // Model of the GPU + Model string `json:"model"` + // Number of GPU cores + Cores resource.Quantity `json:"cores"` + // Memory of the GPU + Memory resource.Quantity `json:"memory"` +} + +// Policies represents the policies of a K8Slice Flavor, such as the partitionability of the K8Slice Flavor. +type Policies struct { + // Partitionability of the K8Slice Flavor + Partitionability Partitionability `json:"partitionability,omitempty"` +} + +// Partitionability represents the partitioning properties of a K8Slice Flavor, such as the minimum and incremental values of CPU and RAM. +type Partitionability struct { + // CPUMin is the minimum number of CPU cores in which the K8Slice Flavor can be partitioned. + CPUMin resource.Quantity `json:"cpuMin"` + // MemoryMin is the minimum amount of RAM in which the K8Slice Flavor can be partitioned. + MemoryMin resource.Quantity `json:"memoryMin"` + // PodsMin is the minimum number of pods in which the K8Slice Flavor can be partitioned. + PodsMin resource.Quantity `json:"podsMin"` + // GpuMin is the minimum number of GPU cores in which the K8Slice Flavor can be partitioned. + GpuMin resource.Quantity `json:"gpuMin,omitempty"` + // CPUStep is the incremental value of CPU cores in which the K8Slice Flavor can be partitioned. + CPUStep resource.Quantity `json:"cpuStep"` + // MemoryStep is the incremental value of RAM in which the K8Slice Flavor can be partitioned. + MemoryStep resource.Quantity `json:"memoryStep"` + // PodsStep is the incremental value of pods in which the K8Slice Flavor can be partitioned. + PodsStep resource.Quantity `json:"podsStep"` + // GpuStep is the incremental value of GPU cores in which the K8Slice Flavor can be partitioned. + GpuStep resource.Quantity `json:"gpuStep,omitempty"` +} + +// ParseK8SliceFlavor parses the K8Slice Flavor. +func ParseK8SliceFlavor(flavorType FlavorType) (*K8Slice, error) { + k8s := &K8Slice{} + // Check type of the Flavor + if flavorType.TypeIdentifier != TypeK8Slice { + return nil, fmt.Errorf("flavor type is not a K8Slice") + } + + // Unmarshal the raw data into the K8Slice struct + if err := json.Unmarshal(flavorType.TypeData.Raw, k8s); err != nil { + return nil, err + } + + // Parse the possible NetworkAuthorization to validate the ResourceSelectors + if k8s.Properties.NetworkAuthorizations != nil { + // Parse the NetworkAuthorizations + // Check DeniedCommunications + for i := range k8s.Properties.NetworkAuthorizations.DeniedCommunications { + deniedCommunicationItem := k8s.Properties.NetworkAuthorizations.DeniedCommunications[i] + source := deniedCommunicationItem.Source + destination := deniedCommunicationItem.Destination + // Parse the source resource selector + _, _, err := ParseResourceSelector(source.ResourceSelector) + if err != nil { + return nil, err + } + // Parse the destination resource selector + _, _, err = ParseResourceSelector(destination.ResourceSelector) + if err != nil { + return nil, err + } + } + // Check MandatoryCommunications + for i := range k8s.Properties.NetworkAuthorizations.MandatoryCommunications { + mandatoryCommunicationItem := k8s.Properties.NetworkAuthorizations.MandatoryCommunications[i] + source := mandatoryCommunicationItem.Source + destination := mandatoryCommunicationItem.Destination + // Parse the source resource selector + _, _, err := ParseResourceSelector(source.ResourceSelector) + if err != nil { + return nil, err + } + // Parse the destination resource selector + _, _, err = ParseResourceSelector(destination.ResourceSelector) + if err != nil { + return nil, err + } + } + } + + return k8s, nil +} diff --git a/apis/nodecore/v1alpha1/k8slice_selector.go b/apis/nodecore/v1alpha1/k8slice_selector.go new file mode 100644 index 0000000..f181d05 --- /dev/null +++ b/apis/nodecore/v1alpha1/k8slice_selector.go @@ -0,0 +1,92 @@ +// Copyright 2022-2024 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 "k8s.io/klog/v2" + +// K8SliceSelector is the selector for a K8Slice. +type K8SliceSelector struct { + // ArchitectureFilter is the Architecture filter of the K8SliceSelector. + ArchitectureFilter *StringFilter `json:"architectureFilter,omitempty"` + + // CPUFilter is the CPU filter of the K8SliceSelector. + CPUFilter *ResourceQuantityFilter `json:"cpuFilter,omitempty"` + + // MemoryFilter is the Memory filter of the K8SliceSelector. + MemoryFilter *ResourceQuantityFilter `json:"memoryFilter,omitempty"` + + // PodsFilter is the Pods filter of the K8SliceSelector. + PodsFilter *ResourceQuantityFilter `json:"podsFilter,omitempty"` + + // StorageFilter is the Storage filter of the K8SliceSelector. + StorageFilter *ResourceQuantityFilter `json:"storageFilter,omitempty"` +} + +// GetFlavorTypeSelector returns the type of the Flavor. +func (*K8SliceSelector) GetFlavorTypeSelector() FlavorTypeIdentifier { + return TypeK8Slice +} + +// ParseK8SliceSelector parses the K8SliceSelector into a map of filters. +func ParseK8SliceSelector(k8SliceSelector *K8SliceSelector) (map[FilterType]interface{}, error) { + filters := make(map[FilterType]interface{}) + if k8SliceSelector.ArchitectureFilter != nil { + klog.Info("Parsing Architecture filter") + // Parse Architecture filter + architectureFilterType, architectureFilterData, err := ParseStringFilter(k8SliceSelector.ArchitectureFilter) + if err != nil { + return nil, err + } + filters[architectureFilterType] = architectureFilterData + } + if k8SliceSelector.CPUFilter != nil { + klog.Info("Parsing CPU filter") + // Parse CPU filter + cpuFilterType, cpuFilterData, err := ParseResourceQuantityFilter(k8SliceSelector.CPUFilter) + if err != nil { + return nil, err + } + filters[cpuFilterType] = cpuFilterData + } + if k8SliceSelector.MemoryFilter != nil { + klog.Info("Parsing Memory filter") + // Parse Memory filter + memoryFilterType, memoryFilterData, err := ParseResourceQuantityFilter(k8SliceSelector.MemoryFilter) + if err != nil { + return nil, err + } + filters[memoryFilterType] = memoryFilterData + } + if k8SliceSelector.PodsFilter != nil { + klog.Info("Parsing Pods filter") + // Parse Pods filter + podsFilterType, podsFilterData, err := ParseResourceQuantityFilter(k8SliceSelector.PodsFilter) + if err != nil { + return nil, err + } + filters[podsFilterType] = podsFilterData + } + if k8SliceSelector.StorageFilter != nil { + klog.Info("Parsing Storage filter") + // Parse Storage filter + storageFilterType, storageFilterData, err := ParseResourceQuantityFilter(k8SliceSelector.StorageFilter) + if err != nil { + return nil, err + } + filters[storageFilterType] = storageFilterData + } + + return filters, nil +} diff --git a/apis/nodecore/v1alpha1/network_intent.go b/apis/nodecore/v1alpha1/network_intent.go new file mode 100644 index 0000000..f1b6137 --- /dev/null +++ b/apis/nodecore/v1alpha1/network_intent.go @@ -0,0 +1,95 @@ +// Copyright 2022-2024 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 ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" +) + +// PodNamespaceSelector represents the pod namespace selector of a SourceDestination. +type PodNamespaceSelector struct { + // Pod is the pod selector of the SourceDestination. + Pod map[string]string `json:"pod"` + // Namespace is the namespace selector of the SourceDestination. + Namespace map[string]string `json:"namespace"` +} + +// CIDRSelector represents the CIDR selector of a SourceDestination. +type CIDRSelector string + +// ResourceSelectorIdentifier represents the type of a ResourceSelector. +type ResourceSelectorIdentifier string + +const ( + // PodNamespaceSelectorType is the type of a PodNamespaceSelector. + PodNamespaceSelectorType ResourceSelectorIdentifier = "PodNamespaceSelector" + // CIDRSelectorType is the type of a CIDRSelector. + CIDRSelectorType ResourceSelectorIdentifier = "CIDRSelector" +) + +// ResourceSelector represents the resource selector of a SourceDestination. +type ResourceSelector struct { + // TypeIdentifier is the type of the resource selector. + TypeIdentifier ResourceSelectorIdentifier `json:"typeIdentifier"` + // Selector is the selector of the resource selector. + Selector runtime.RawExtension `json:"selector"` +} + +// SourceDestination can represent either the source or destination of a network intent. +type SourceDestination struct { + // IsHotCluster is true if the source/destination is a hot cluster. + IsHotCluster bool `json:"isHotCluster"` + // ResourceSelector is the resource selector of the source/destination. + ResourceSelector ResourceSelector `json:"resourceSelector"` +} + +// NetworkIntent represents the network intent of a Flavor. +type NetworkIntent struct { + // Name of the network intent + Name string `json:"name"` + // Source of the network intent + Source SourceDestination `json:"source"` + // Destination of the network intent + Destination SourceDestination `json:"destination"` + // DestinationPort of the network intent + DestinationPort string `json:"destinationPort"` + // ProtocolType of the network intent + ProtocolType string `json:"protocolType"` +} + +// ParseResourceSelector parses a ResourceSelector into a ResourceSelectorIdentifier and a selector. +func ParseResourceSelector(selector ResourceSelector) (ResourceSelectorIdentifier, interface{}, error) { + switch selector.TypeIdentifier { + case PodNamespaceSelectorType: + podNamespaceSelector := PodNamespaceSelector{} + err := json.Unmarshal(selector.Selector.Raw, &podNamespaceSelector) + if err != nil { + return "", nil, err + } + return PodNamespaceSelectorType, podNamespaceSelector, nil + case CIDRSelectorType: + cidrSelector := CIDRSelector("") + err := json.Unmarshal(selector.Selector.Raw, &cidrSelector) + if err != nil { + return "", nil, err + } + return CIDRSelectorType, cidrSelector, nil + default: + return "", nil, fmt.Errorf("unknown resource selector type: %s", selector.TypeIdentifier) + } +} diff --git a/apis/nodecore/v1alpha1/selector.go b/apis/nodecore/v1alpha1/selector.go new file mode 100644 index 0000000..9a7e310 --- /dev/null +++ b/apis/nodecore/v1alpha1/selector.go @@ -0,0 +1,152 @@ +// Copyright 2022-2024 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 ( + "encoding/json" + "fmt" + "regexp" + + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" +) + +const ( + // TypeMatchFilter is the name of the filter that matches a specific value. + TypeMatchFilter FilterType = "Match" + // TypeRangeFilter is the name of the filter that selects resources within a range. + TypeRangeFilter FilterType = "Range" +) + +// FilterType is the type of filter that can be applied to a resource quantity. +type FilterType string + +// StringFilter is a filter that can be applied to a string. +type StringFilter struct { + // Name indicates the type of the filter + Name FilterType `json:"name"` + // Filter data + Data runtime.RawExtension `json:"data"` +} + +// StringMatchSelector is a filter that selects strings that match a specific value. +type StringMatchSelector struct { + // Value is the value to match + Value string `json:"value"` +} + +// StringRangeSelector is a filter that selects strings within a range. +type StringRangeSelector struct { + // Regex is the regular expression to match + Regex string `json:"regex"` +} + +// ResourceQuantityFilter is a filter that can be applied to a resource quantity. +type ResourceQuantityFilter struct { + // Name indicates the type of the filter + Name FilterType `json:"name"` + // Filter data + Data runtime.RawExtension `json:"data"` +} + +// ResourceMatchSelector is a filter that selects resources that match a specific value. +type ResourceMatchSelector struct { + // Value is the value to match + Value resource.Quantity `json:"value"` +} + +// ResourceRangeSelector is a filter that selects resources within a range. +type ResourceRangeSelector struct { + // Min is the minimum value of the range + Min *resource.Quantity `json:"min,omitempty"` + // Max is the maximum value of the range + Max *resource.Quantity `json:"max,omitempty"` +} + +// ParseResourceQuantityFilter parses a ResourceQuantityFilter into a FilterType and the corresponding filter data. +// It also provides a set of validation rules for the filter data. +// Particularly for the ResourceRangeSelector, it checks that at least one of min or max is set and that min is less than max if both are set. +func ParseResourceQuantityFilter(rqf *ResourceQuantityFilter) (FilterType, interface{}, error) { + var validationErr error + + klog.Infof("Parsing ResourceQuantityFilter %v - Name: %s", rqf, rqf.Name) + + switch rqf.Name { + case TypeMatchFilter: + // Unmarshal the data into a ResourceMatchSelector + var rms ResourceMatchSelector + validationErr = json.Unmarshal(rqf.Data.Raw, &rms) + return TypeMatchFilter, rms, validationErr + case TypeRangeFilter: + // Unmarshal the data into a ResourceRangeSelector + var rrs ResourceRangeSelector + validationErr = json.Unmarshal(rqf.Data.Raw, &rrs) + + klog.Infof("ResourceRangeSelector: %v", rrs) + // Check that at least one of min or max is set + if rrs.Min == nil && rrs.Max == nil { + klog.Error("at least one of min or max must be set") + validationErr = fmt.Errorf("at least one of min or max must be set") + } else + // If both min and max are set, check that min is less than max + if rrs.Min != nil && rrs.Max != nil { + // Check that the min is less than the max + if rrs.Min != nil && rrs.Max != nil && rrs.Min.Cmp(*rrs.Max) > 0 { + klog.Errorf("min value %s is greater than max value %s", rrs.Min.String(), rrs.Max.String()) + validationErr = fmt.Errorf("min value %s is greater than max value %s", rrs.Min.String(), rrs.Max.String()) + } + } + return TypeRangeFilter, rrs, validationErr + default: + return "", nil, fmt.Errorf("unknown filter type %s", rqf.Name) + } +} + +// ParseStringFilter parses a StringFilter into a FilterType and the corresponding filter data. +// It also provides a set of validation rules for the filter data. +// Particularly for the StringRangeSelector, it checks that regex is set. +func ParseStringFilter(sf *StringFilter) (FilterType, interface{}, error) { + var validationErr error + + klog.Infof("Parsing StringFilter %v - Name: %s", sf, sf.Name) + + switch sf.Name { + case TypeMatchFilter: + // Unmarshal the data into a StringMatchSelector + var sms StringMatchSelector + validationErr = json.Unmarshal(sf.Data.Raw, &sms) + return TypeMatchFilter, sms, validationErr + case TypeRangeFilter: + // Unmarshal the data into a StringRangeSelector + var srs StringRangeSelector + validationErr = json.Unmarshal(sf.Data.Raw, &srs) + + klog.Infof("StringRangeSelector: %v", srs) + // Check that regex is set + if srs.Regex == "" { + klog.Error("regex must be set") + validationErr = fmt.Errorf("regex must be set") + } + // Check that regex is a valid regular expression + if _, err := regexp.Compile(srs.Regex); err != nil { + klog.Errorf("invalid regular expression %s: %v", srs.Regex, err) + validationErr = fmt.Errorf("invalid regular expression %s: %w", srs.Regex, err) + } + return TypeRangeFilter, srs, validationErr + default: + return "", nil, fmt.Errorf("unknown filter type %s", sf.Name) + } +} diff --git a/apis/nodecore/v1alpha1/solver_status.go b/apis/nodecore/v1alpha1/solver_status.go index 39e2beb..3c92f2e 100644 --- a/apis/nodecore/v1alpha1/solver_status.go +++ b/apis/nodecore/v1alpha1/solver_status.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,40 +18,41 @@ import ( "github.com/fluidos-project/node/pkg/utils/tools" ) -func (solver *Solver) SetPhase(phase Phase, msg string) { +// SetPhase sets the phase of the solver. +func (r *Solver) SetPhase(phase Phase, msg string) { t := tools.GetTimeNow() - solver.Status.SolverPhase.Phase = phase - solver.Status.SolverPhase.LastChangeTime = t - solver.Status.SolverPhase.Message = msg - solver.Status.SolverPhase.EndTime = t + r.Status.SolverPhase.Phase = phase + r.Status.SolverPhase.LastChangeTime = t + r.Status.SolverPhase.Message = msg + r.Status.SolverPhase.EndTime = t } // SetPeeringStatus sets the Peering phase of the solver. -func (solver *Solver) SetPeeringStatus(phase Phase) { - solver.Status.Peering = phase - solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +func (r *Solver) SetPeeringStatus(phase Phase) { + r.Status.Peering = phase + r.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() } -// SetPurchasePhase sets the ReserveAndBuy phase of the solver -func (solver *Solver) SetReserveAndBuyStatus(phase Phase) { - solver.Status.ReserveAndBuy = phase - solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +// SetReserveAndBuyStatus sets the ReserveAndBuy phase of the solver. +func (r *Solver) SetReserveAndBuyStatus(phase Phase) { + r.Status.ReserveAndBuy = phase + r.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() } -// SetFindCandidateStatus sets the FindCandidate phase of the solver -func (solver *Solver) SetFindCandidateStatus(phase Phase) { - solver.Status.FindCandidate = phase - solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +// SetFindCandidateStatus sets the FindCandidate phase of the solver. +func (r *Solver) SetFindCandidateStatus(phase Phase) { + r.Status.FindCandidate = phase + r.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() } -// SetDiscoveryStatus sets the discovery phase of the solver -func (solver *Solver) SetDiscoveryStatus(phase Phase) { - solver.Status.DiscoveryPhase = phase - solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +// SetDiscoveryStatus sets the discovery phase of the solver. +func (r *Solver) SetDiscoveryStatus(phase Phase) { + r.Status.DiscoveryPhase = phase + r.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() } -// SetReservationStatus sets the reservation phase of the solver -func (solver *Solver) SetReservationStatus(phase Phase) { - solver.Status.ReservationPhase = phase - solver.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() +// SetReservationStatus sets the reservation phase of the solver. +func (r *Solver) SetReservationStatus(phase Phase) { + r.Status.ReservationPhase = phase + r.Status.SolverPhase.LastChangeTime = tools.GetTimeNow() } diff --git a/apis/nodecore/v1alpha1/solver_types.go b/apis/nodecore/v1alpha1/solver_types.go index a9d39c0..a235c35 100644 --- a/apis/nodecore/v1alpha1/solver_types.go +++ b/apis/nodecore/v1alpha1/solver_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,10 +15,15 @@ package v1alpha1 import ( - resource "k8s.io/apimachinery/pkg/api/resource" + "encoding/json" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" ) +// Phase represents the phase of the solver. type Phase string // PhaseStatus represents the status of a phase of the solver. I.e. the status of the REAR phases. @@ -30,44 +35,21 @@ type PhaseStatus struct { EndTime string `json:"endTime,omitempty"` } -type FlavourSelector struct { - FlavourType string `json:"type"` - Architecture string `json:"architecture"` - RangeSelector *RangeSelector `json:"rangeSelector,omitempty"` - MatchSelector *MatchSelector `json:"matchSelector,omitempty"` -} - -// MatchSelector represents the criteria for selecting Flavours through a strict match. -type MatchSelector struct { - CPU resource.Quantity `json:"cpu,omitempty"` - Memory resource.Quantity `json:"memory,omitempty"` - Pods resource.Quantity `json:"pods,omitempty"` - Storage resource.Quantity `json:"storage,omitempty"` - EphemeralStorage resource.Quantity `json:"ephemeralStorage,omitempty"` - Gpu resource.Quantity `json:"gpu,omitempty"` -} +// Selector defines the constraints of the flavor that the solver is looking for. +// The FlavorType is compulsory, while the Filters are optional. +type Selector struct { + // FlavorType is the type of the Flavor that the solver is looking for. + FlavorType FlavorTypeIdentifier `json:"flavorType"` -// RangeSelector represents the criteria for selecting Flavours through a range. -type RangeSelector struct { - MinCpu resource.Quantity `json:"minCpu,omitempty"` - MinMemory resource.Quantity `json:"minMemory,omitempty"` - MinPods resource.Quantity `json:"minPods,omitempty"` - MinEph resource.Quantity `json:"minEph,omitempty"` - MinStorage resource.Quantity `json:"minStorage,omitempty"` - MinGpu resource.Quantity `json:"minGpu,omitempty"` - MaxCpu resource.Quantity `json:"MaxCpu,omitempty"` - MaxMemory resource.Quantity `json:"MaxMemory,omitempty"` - MaxPods resource.Quantity `json:"MaxPods,omitempty"` - MaxEph resource.Quantity `json:"MaxEph,omitempty"` - MaxStorage resource.Quantity `json:"MaxStorage,omitempty"` - MaxGpu resource.Quantity `json:"MaxGpu,omitempty"` + // Filters contains the filters that the solver is using to refining the research. + Filters *runtime.RawExtension `json:"filters,omitempty"` } -// SolverSpec defines the desired state of Solver +// SolverSpec defines the desired state of Solver. type SolverSpec struct { - // Selector contains the flavour requirements for the solver. - Selector *FlavourSelector `json:"selector,omitempty"` + // Selector contains the flavor requirements for the solver. + Selector *Selector `json:"selector,omitempty"` // IntentID is the ID of the intent that the Node Orchestrator is trying to solve. // It is used to link the solver with the intent. @@ -79,27 +61,27 @@ type SolverSpec struct { // ReserveAndBuy is a flag that indicates if the solver should reserve and buy the resources on the candidate. ReserveAndBuy bool `json:"reserveAndBuy,omitempty"` - // EnstablishPeering is a flag that indicates if the solver should enstablish a peering with the candidate. - EnstablishPeering bool `json:"enstablishPeering,omitempty"` + // EstablishPeering is a flag that indicates if the solver should enstablish a peering with the candidate. + EstablishPeering bool `json:"establishPeering,omitempty"` } -// SolverStatus defines the observed state of Solver +// SolverStatus defines the observed state of Solver. type SolverStatus struct { // FindCandidate describes the status of research of the candidate. - // Rear Manager is looking for the best candidate Flavour to solve the Node Orchestrator request. + // Rear Manager is looking for the best candidate Flavor to solve the Node Orchestrator request. FindCandidate Phase `json:"findCandidate,omitempty"` - // ReserveAndBuy describes the status of the reservation and purchase of selected Flavour. + // ReserveAndBuy describes the status of the reservation and purchase of selected Flavor. // Rear Manager is trying to reserve and purchase the resources on the candidate FLUIDOS Node. ReserveAndBuy Phase `json:"reserveAndBuy,omitempty"` // Peering describes the status of the peering with the candidate. - // Rear Manager is trying to enstablish a peering with the candidate FLUIDOS Node. + // Rear Manager is trying to establish a peering with the candidate FLUIDOS Node. Peering Phase `json:"peering,omitempty"` // DiscoveryPhase describes the status of the Discovery where the Discovery Manager - // is looking for matching flavours outside the FLUIDOS Node + // is looking for matching flavors outside the FLUIDOS Node DiscoveryPhase Phase `json:"discoveryPhase,omitempty"` // ReservationPhase describes the status of the Reservation where the Contract Manager @@ -111,12 +93,12 @@ type SolverStatus struct { ConsumePhase Phase `json:"consumePhase,omitempty"` // SolverPhase describes the status of the Solver generated by the Node Orchestrator. - // It is usefull to understand if the solver is still running or if it has finished or failed. + // 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 fullfill the intent. + // 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. @@ -133,14 +115,14 @@ type SolverStatus struct { // +kubebuilder:printcolumn:name="Intent ID",type=string,JSONPath=`.spec.intentID` // +kubebuilder:printcolumn:name="Find Candidate",type=boolean,JSONPath=`.spec.findCandidate` // +kubebuilder:printcolumn:name="Reserve and Buy",type=boolean,JSONPath=`.spec.reserveAndBuy` -// +kubebuilder:printcolumn:name="Peering",type=boolean,JSONPath=`.spec.enstablishPeering` +// +kubebuilder:printcolumn:name="Peering",type=boolean,JSONPath=`.spec.establishPeering` // +kubebuilder:printcolumn:name="Candidate Phase",type=string,priority=1,JSONPath=`.status.findCandidate` // +kubebuilder:printcolumn:name="Reserving Phase",type=string,priority=1,JSONPath=`.status.reserveAndBuy` // +kubebuilder:printcolumn:name="Peering Phase",type=string,priority=1,JSONPath=`.status.peering` // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.solverPhase.phase` // +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.solverPhase.message` // +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp` -// Solver is the Schema for the solvers API +// Solver is the Schema for the solvers API. type Solver struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -151,7 +133,7 @@ type Solver struct { //+kubebuilder:object:root=true -// SolverList contains a list of Solver +// SolverList contains a list of Solver. type SolverList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` @@ -161,3 +143,38 @@ type SolverList struct { func init() { SchemeBuilder.Register(&Solver{}, &SolverList{}) } + +// ParseSolverSelector is a utility function that extracts the SolverTypeIdentifier and the SolverTypeData from the Solver. +// It provides a set of validation for nested data, so the nested filters, if present, are validated as well. +// ATTENTION: This function can return a nil interface{} if the SolverTypeData is not present. +func ParseSolverSelector(s *Selector) (FlavorTypeIdentifier, interface{}, error) { + switch s.FlavorType { + case TypeK8Slice: + var k8sliceFilter K8SliceSelector + klog.Info("Parsing K8Slice selector") + if s.Filters == nil { + klog.Info("No specific filters found") + return TypeK8Slice, nil, nil + } + if err := json.Unmarshal(s.Filters.Raw, &k8sliceFilter); err != nil { + return "", nil, err + } + + // Parse the filters + filters, err := ParseK8SliceSelector(&k8sliceFilter) + if err != nil { + return "", nil, err + } + klog.Infof("K8Slice Selector owns %d filters", len(filters)) + + return TypeK8Slice, k8sliceFilter, nil + case TypeVM: + // TODO: Implement the function + return "", nil, fmt.Errorf("solver type %s not supported", s.FlavorType) + case TypeService: + // TODO: Implement the function + return "", nil, fmt.Errorf("solver type %s not supported", s.FlavorType) + default: + return "", nil, fmt.Errorf("solver type %s not supported", s.FlavorType) + } +} diff --git a/apis/nodecore/v1alpha1/solver_webhook.go b/apis/nodecore/v1alpha1/solver_webhook.go new file mode 100644 index 0000000..5ca436a --- /dev/null +++ b/apis/nodecore/v1alpha1/solver_webhook.go @@ -0,0 +1,108 @@ +// Copyright 2022-2024 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 ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var solverlog = logf.Log.WithName("solver-resource") + +// SetupWebhookWithManager sets up and registers the webhook with the manager. +func (r *Solver) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/mutate-nodecore-fluidos-eu-v1alpha1-solver,mutating=true,failurePolicy=fail,sideEffects=None,groups=nodecore.fluidos.eu,resources=solvers,verbs=create;update,versions=v1alpha1,name=msolver.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Solver{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *Solver) Default() { + solverlog.Info("DEFAULT WEBHOOK") + solverlog.Info("default", "name", r.Name) +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/validate-nodecore-fluidos-eu-v1alpha1-solver,mutating=false,failurePolicy=fail,sideEffects=None,groups=nodecore.fluidos.eu,resources=solvers,verbs=create;update,versions=v1alpha1,name=vsolver.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Solver{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *Solver) ValidateCreate() (admission.Warnings, error) { + solverlog.Info("VALIDATE CREATE WEBHOOK") + solverlog.Info("validate create", "name", r.Name) + + if err := validateSelector(r.Spec.Selector); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *Solver) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + solverlog.Info("VALIDATE UPDATE WEBHOOK") + solverlog.Info("validate update", "name", r.Name) + + solverlog.Info("old", "old", old) + + if err := validateSelector(r.Spec.Selector); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *Solver) ValidateDelete() (admission.Warnings, error) { + solverlog.Info("VALIDATE DELETE WEBHOOK") + solverlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} + +func validateSelector(selector *Selector) error { + if selector == nil { + return nil + } + // Validate creation of Solver checking SolverType->typeIdentifier matches the struct inside the SolverType->TypeData + typeIdentifier, _, err := ParseSolverSelector(selector) + if err != nil { + return err + } + switch typeIdentifier { + case TypeK8Slice: + solverlog.Info("Selector Flavor Type is K8Slice") + case TypeVM: + solverlog.Info("Selector Flavor Type is VM") + case TypeService: + solverlog.Info("Selector Flavor Type is Service") + default: + solverlog.Info("Selector Flavor Type is not valid") + } + + return nil +} diff --git a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go index a97e177..7bc6dd8 100644 --- a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go +++ b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go @@ -1,6 +1,6 @@ //go:build !ignore_autogenerated -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,24 +19,9 @@ package v1alpha1 import ( - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Aggregatable) DeepCopyInto(out *Aggregatable) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Aggregatable. -func (in *Aggregatable) DeepCopy() *Aggregatable { - if in == nil { - return nil - } - out := new(Aggregatable) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Allocation) DeepCopyInto(out *Allocation) { *out = *in @@ -128,28 +113,43 @@ func (in *AllocationStatus) DeepCopy() *AllocationStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Characteristics) DeepCopyInto(out *Characteristics) { +func (in *CarbonFootprint) DeepCopyInto(out *CarbonFootprint) { *out = *in - out.Cpu = in.Cpu.DeepCopy() - out.Memory = in.Memory.DeepCopy() - out.Pods = in.Pods.DeepCopy() - out.Gpu = in.Gpu.DeepCopy() - out.EphemeralStorage = in.EphemeralStorage.DeepCopy() - out.PersistentStorage = in.PersistentStorage.DeepCopy() + if in.Operational != nil { + in, out := &in.Operational, &out.Operational + *out = make([]int, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CarbonFootprint. +func (in *CarbonFootprint) DeepCopy() *CarbonFootprint { + if in == nil { + return nil + } + out := new(CarbonFootprint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Configuration) DeepCopyInto(out *Configuration) { + *out = *in + in.ConfigurationData.DeepCopyInto(&out.ConfigurationData) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Characteristics. -func (in *Characteristics) DeepCopy() *Characteristics { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration. +func (in *Configuration) DeepCopy() *Configuration { if in == nil { return nil } - out := new(Characteristics) + out := new(Configuration) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Flavour) DeepCopyInto(out *Flavour) { +func (in *Flavor) DeepCopyInto(out *Flavor) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) @@ -157,18 +157,18 @@ func (in *Flavour) DeepCopyInto(out *Flavour) { out.Status = in.Status } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Flavour. -func (in *Flavour) DeepCopy() *Flavour { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Flavor. +func (in *Flavor) DeepCopy() *Flavor { if in == nil { return nil } - out := new(Flavour) + out := new(Flavor) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Flavour) DeepCopyObject() runtime.Object { +func (in *Flavor) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -176,31 +176,31 @@ func (in *Flavour) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlavourList) DeepCopyInto(out *FlavourList) { +func (in *FlavorList) DeepCopyInto(out *FlavorList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]Flavour, len(*in)) + *out = make([]Flavor, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavourList. -func (in *FlavourList) DeepCopy() *FlavourList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorList. +func (in *FlavorList) DeepCopy() *FlavorList { if in == nil { return nil } - out := new(FlavourList) + out := new(FlavorList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *FlavourList) DeepCopyObject() runtime.Object { +func (in *FlavorList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -208,61 +208,72 @@ func (in *FlavourList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlavourSelector) DeepCopyInto(out *FlavourSelector) { +func (in *FlavorSpec) DeepCopyInto(out *FlavorSpec) { *out = *in - if in.RangeSelector != nil { - in, out := &in.RangeSelector, &out.RangeSelector - *out = new(RangeSelector) - (*in).DeepCopyInto(*out) + in.FlavorType.DeepCopyInto(&out.FlavorType) + out.Owner = in.Owner + out.Price = in.Price + if in.Location != nil { + in, out := &in.Location, &out.Location + *out = new(Location) + **out = **in } - if in.MatchSelector != nil { - in, out := &in.MatchSelector, &out.MatchSelector - *out = new(MatchSelector) - (*in).DeepCopyInto(*out) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorSpec. +func (in *FlavorSpec) DeepCopy() *FlavorSpec { + if in == nil { + return nil } + out := new(FlavorSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FlavorStatus) DeepCopyInto(out *FlavorStatus) { + *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavourSelector. -func (in *FlavourSelector) DeepCopy() *FlavourSelector { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorStatus. +func (in *FlavorStatus) DeepCopy() *FlavorStatus { if in == nil { return nil } - out := new(FlavourSelector) + out := new(FlavorStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlavourSpec) DeepCopyInto(out *FlavourSpec) { +func (in *FlavorType) DeepCopyInto(out *FlavorType) { *out = *in - in.Characteristics.DeepCopyInto(&out.Characteristics) - in.Policy.DeepCopyInto(&out.Policy) - out.Owner = in.Owner - out.Price = in.Price - out.OptionalFields = in.OptionalFields + in.TypeData.DeepCopyInto(&out.TypeData) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavourSpec. -func (in *FlavourSpec) DeepCopy() *FlavourSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavorType. +func (in *FlavorType) DeepCopy() *FlavorType { if in == nil { return nil } - out := new(FlavourSpec) + out := new(FlavorType) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FlavourStatus) DeepCopyInto(out *FlavourStatus) { +func (in *GPU) DeepCopyInto(out *GPU) { *out = *in + out.Cores = in.Cores.DeepCopy() + out.Memory = in.Memory.DeepCopy() } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FlavourStatus. -func (in *FlavourStatus) DeepCopy() *FlavourStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GPU. +func (in *GPU) DeepCopy() *GPU { if in == nil { return nil } - out := new(FlavourStatus) + out := new(GPU) in.DeepCopyInto(out) return out } @@ -282,6 +293,120 @@ func (in *GenericRef) DeepCopy() *GenericRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8Slice) DeepCopyInto(out *K8Slice) { + *out = *in + in.Characteristics.DeepCopyInto(&out.Characteristics) + in.Properties.DeepCopyInto(&out.Properties) + in.Policies.DeepCopyInto(&out.Policies) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8Slice. +func (in *K8Slice) DeepCopy() *K8Slice { + if in == nil { + return nil + } + out := new(K8Slice) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8SliceCharacteristics) DeepCopyInto(out *K8SliceCharacteristics) { + *out = *in + out.CPU = in.CPU.DeepCopy() + out.Memory = in.Memory.DeepCopy() + out.Pods = in.Pods.DeepCopy() + if in.Gpu != nil { + in, out := &in.Gpu, &out.Gpu + *out = new(GPU) + (*in).DeepCopyInto(*out) + } + if in.Storage != nil { + in, out := &in.Storage, &out.Storage + x := (*in).DeepCopy() + *out = &x + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8SliceCharacteristics. +func (in *K8SliceCharacteristics) DeepCopy() *K8SliceCharacteristics { + if in == nil { + return nil + } + out := new(K8SliceCharacteristics) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8SliceConfiguration) DeepCopyInto(out *K8SliceConfiguration) { + *out = *in + out.CPU = in.CPU.DeepCopy() + out.Memory = in.Memory.DeepCopy() + out.Pods = in.Pods.DeepCopy() + if in.Gpu != nil { + in, out := &in.Gpu, &out.Gpu + *out = new(GPU) + (*in).DeepCopyInto(*out) + } + if in.Storage != nil { + in, out := &in.Storage, &out.Storage + x := (*in).DeepCopy() + *out = &x + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8SliceConfiguration. +func (in *K8SliceConfiguration) DeepCopy() *K8SliceConfiguration { + if in == nil { + return nil + } + out := new(K8SliceConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *K8SliceSelector) DeepCopyInto(out *K8SliceSelector) { + *out = *in + if in.ArchitectureFilter != nil { + in, out := &in.ArchitectureFilter, &out.ArchitectureFilter + *out = new(StringFilter) + (*in).DeepCopyInto(*out) + } + if in.CPUFilter != nil { + in, out := &in.CPUFilter, &out.CPUFilter + *out = new(ResourceQuantityFilter) + (*in).DeepCopyInto(*out) + } + if in.MemoryFilter != nil { + in, out := &in.MemoryFilter, &out.MemoryFilter + *out = new(ResourceQuantityFilter) + (*in).DeepCopyInto(*out) + } + if in.PodsFilter != nil { + in, out := &in.PodsFilter, &out.PodsFilter + *out = new(ResourceQuantityFilter) + (*in).DeepCopyInto(*out) + } + if in.StorageFilter != nil { + in, out := &in.StorageFilter, &out.StorageFilter + *out = new(ResourceQuantityFilter) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new K8SliceSelector. +func (in *K8SliceSelector) DeepCopy() *K8SliceSelector { + if in == nil { + return nil + } + out := new(K8SliceSelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LiqoCredentials) DeepCopyInto(out *LiqoCredentials) { *out = *in @@ -298,94 +423,100 @@ func (in *LiqoCredentials) DeepCopy() *LiqoCredentials { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MatchSelector) DeepCopyInto(out *MatchSelector) { +func (in *Location) DeepCopyInto(out *Location) { *out = *in - out.CPU = in.CPU.DeepCopy() - out.Memory = in.Memory.DeepCopy() - out.Pods = in.Pods.DeepCopy() - out.Storage = in.Storage.DeepCopy() - out.EphemeralStorage = in.EphemeralStorage.DeepCopy() - out.Gpu = in.Gpu.DeepCopy() } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchSelector. -func (in *MatchSelector) DeepCopy() *MatchSelector { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Location. +func (in *Location) DeepCopy() *Location { if in == nil { return nil } - out := new(MatchSelector) + out := new(Location) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NodeIdentity) DeepCopyInto(out *NodeIdentity) { +func (in *NetworkAuthorizations) DeepCopyInto(out *NetworkAuthorizations) { *out = *in + if in.DeniedCommunications != nil { + in, out := &in.DeniedCommunications, &out.DeniedCommunications + *out = make([]NetworkIntent, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.MandatoryCommunications != nil { + in, out := &in.MandatoryCommunications, &out.MandatoryCommunications + *out = make([]NetworkIntent, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeIdentity. -func (in *NodeIdentity) DeepCopy() *NodeIdentity { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkAuthorizations. +func (in *NetworkAuthorizations) DeepCopy() *NetworkAuthorizations { if in == nil { return nil } - out := new(NodeIdentity) + out := new(NetworkAuthorizations) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OptionalFields) DeepCopyInto(out *OptionalFields) { +func (in *NetworkIntent) DeepCopyInto(out *NetworkIntent) { *out = *in + in.Source.DeepCopyInto(&out.Source) + in.Destination.DeepCopyInto(&out.Destination) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionalFields. -func (in *OptionalFields) DeepCopy() *OptionalFields { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkIntent. +func (in *NetworkIntent) DeepCopy() *NetworkIntent { if in == nil { return nil } - out := new(OptionalFields) + out := new(NetworkIntent) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Partition) DeepCopyInto(out *Partition) { +func (in *NodeIdentity) DeepCopyInto(out *NodeIdentity) { *out = *in - out.CPU = in.CPU.DeepCopy() - out.Memory = in.Memory.DeepCopy() - out.Pods = in.Pods.DeepCopy() - out.Gpu = in.Gpu.DeepCopy() - out.EphemeralStorage = in.EphemeralStorage.DeepCopy() - out.Storage = in.Storage.DeepCopy() } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Partition. -func (in *Partition) DeepCopy() *Partition { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeIdentity. +func (in *NodeIdentity) DeepCopy() *NodeIdentity { if in == nil { return nil } - out := new(Partition) + out := new(NodeIdentity) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Partitionable) DeepCopyInto(out *Partitionable) { +func (in *Partitionability) DeepCopyInto(out *Partitionability) { *out = *in - out.CpuMin = in.CpuMin.DeepCopy() + out.CPUMin = in.CPUMin.DeepCopy() out.MemoryMin = in.MemoryMin.DeepCopy() out.PodsMin = in.PodsMin.DeepCopy() - out.CpuStep = in.CpuStep.DeepCopy() + out.GpuMin = in.GpuMin.DeepCopy() + out.CPUStep = in.CPUStep.DeepCopy() out.MemoryStep = in.MemoryStep.DeepCopy() out.PodsStep = in.PodsStep.DeepCopy() + out.GpuStep = in.GpuStep.DeepCopy() } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Partitionable. -func (in *Partitionable) DeepCopy() *Partitionable { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Partitionability. +func (in *Partitionability) DeepCopy() *Partitionability { if in == nil { return nil } - out := new(Partitionable) + out := new(Partitionability) in.DeepCopyInto(out) return out } @@ -406,26 +537,46 @@ func (in *PhaseStatus) DeepCopy() *PhaseStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Policy) DeepCopyInto(out *Policy) { +func (in *PodNamespaceSelector) DeepCopyInto(out *PodNamespaceSelector) { *out = *in - if in.Partitionable != nil { - in, out := &in.Partitionable, &out.Partitionable - *out = new(Partitionable) - (*in).DeepCopyInto(*out) + if in.Pod != nil { + in, out := &in.Pod, &out.Pod + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } } - if in.Aggregatable != nil { - in, out := &in.Aggregatable, &out.Aggregatable - *out = new(Aggregatable) - **out = **in + if in.Namespace != nil { + in, out := &in.Namespace, &out.Namespace + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodNamespaceSelector. +func (in *PodNamespaceSelector) DeepCopy() *PodNamespaceSelector { + if in == nil { + return nil } + out := new(PodNamespaceSelector) + in.DeepCopyInto(out) + return out } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. -func (in *Policy) DeepCopy() *Policy { +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Policies) DeepCopyInto(out *Policies) { + *out = *in + in.Partitionability.DeepCopyInto(&out.Partitionability) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policies. +func (in *Policies) DeepCopy() *Policies { if in == nil { return nil } - out := new(Policy) + out := new(Policies) in.DeepCopyInto(out) return out } @@ -446,28 +597,124 @@ func (in *Price) DeepCopy() *Price { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RangeSelector) DeepCopyInto(out *RangeSelector) { +func (in *Properties) DeepCopyInto(out *Properties) { + *out = *in + if in.SecurityStandards != nil { + in, out := &in.SecurityStandards, &out.SecurityStandards + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CarbonFootprint != nil { + in, out := &in.CarbonFootprint, &out.CarbonFootprint + *out = new(CarbonFootprint) + (*in).DeepCopyInto(*out) + } + if in.NetworkAuthorizations != nil { + in, out := &in.NetworkAuthorizations, &out.NetworkAuthorizations + *out = new(NetworkAuthorizations) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Properties. +func (in *Properties) DeepCopy() *Properties { + if in == nil { + return nil + } + out := new(Properties) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceMatchSelector) DeepCopyInto(out *ResourceMatchSelector) { + *out = *in + out.Value = in.Value.DeepCopy() +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceMatchSelector. +func (in *ResourceMatchSelector) DeepCopy() *ResourceMatchSelector { + if in == nil { + return nil + } + out := new(ResourceMatchSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceQuantityFilter) DeepCopyInto(out *ResourceQuantityFilter) { + *out = *in + in.Data.DeepCopyInto(&out.Data) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceQuantityFilter. +func (in *ResourceQuantityFilter) DeepCopy() *ResourceQuantityFilter { + if in == nil { + return nil + } + out := new(ResourceQuantityFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceRangeSelector) DeepCopyInto(out *ResourceRangeSelector) { *out = *in - out.MinCpu = in.MinCpu.DeepCopy() - out.MinMemory = in.MinMemory.DeepCopy() - out.MinPods = in.MinPods.DeepCopy() - out.MinEph = in.MinEph.DeepCopy() - out.MinStorage = in.MinStorage.DeepCopy() - out.MinGpu = in.MinGpu.DeepCopy() - out.MaxCpu = in.MaxCpu.DeepCopy() - out.MaxMemory = in.MaxMemory.DeepCopy() - out.MaxPods = in.MaxPods.DeepCopy() - out.MaxEph = in.MaxEph.DeepCopy() - out.MaxStorage = in.MaxStorage.DeepCopy() - out.MaxGpu = in.MaxGpu.DeepCopy() + if in.Min != nil { + in, out := &in.Min, &out.Min + x := (*in).DeepCopy() + *out = &x + } + if in.Max != nil { + in, out := &in.Max, &out.Max + x := (*in).DeepCopy() + *out = &x + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RangeSelector. -func (in *RangeSelector) DeepCopy() *RangeSelector { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceRangeSelector. +func (in *ResourceRangeSelector) DeepCopy() *ResourceRangeSelector { if in == nil { return nil } - out := new(RangeSelector) + out := new(ResourceRangeSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ResourceSelector) DeepCopyInto(out *ResourceSelector) { + *out = *in + in.Selector.DeepCopyInto(&out.Selector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ResourceSelector. +func (in *ResourceSelector) DeepCopy() *ResourceSelector { + if in == nil { + return nil + } + out := new(ResourceSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Selector) DeepCopyInto(out *Selector) { + *out = *in + if in.Filters != nil { + in, out := &in.Filters, &out.Filters + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Selector. +func (in *Selector) DeepCopy() *Selector { + if in == nil { + return nil + } + out := new(Selector) in.DeepCopyInto(out) return out } @@ -536,7 +783,7 @@ func (in *SolverSpec) DeepCopyInto(out *SolverSpec) { *out = *in if in.Selector != nil { in, out := &in.Selector, &out.Selector - *out = new(FlavourSelector) + *out = new(Selector) (*in).DeepCopyInto(*out) } } @@ -569,3 +816,65 @@ func (in *SolverStatus) DeepCopy() *SolverStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SourceDestination) DeepCopyInto(out *SourceDestination) { + *out = *in + in.ResourceSelector.DeepCopyInto(&out.ResourceSelector) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceDestination. +func (in *SourceDestination) DeepCopy() *SourceDestination { + if in == nil { + return nil + } + out := new(SourceDestination) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StringFilter) DeepCopyInto(out *StringFilter) { + *out = *in + in.Data.DeepCopyInto(&out.Data) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringFilter. +func (in *StringFilter) DeepCopy() *StringFilter { + if in == nil { + return nil + } + out := new(StringFilter) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StringMatchSelector) DeepCopyInto(out *StringMatchSelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringMatchSelector. +func (in *StringMatchSelector) DeepCopy() *StringMatchSelector { + if in == nil { + return nil + } + out := new(StringMatchSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StringRangeSelector) DeepCopyInto(out *StringRangeSelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StringRangeSelector. +func (in *StringRangeSelector) DeepCopy() *StringRangeSelector { + if in == nil { + return nil + } + out := new(StringRangeSelector) + in.DeepCopyInto(out) + return out +} diff --git a/apis/reservation/v1alpha1/contract_types.go b/apis/reservation/v1alpha1/contract_types.go index 826ad59..73ea2f6 100644 --- a/apis/reservation/v1alpha1/contract_types.go +++ b/apis/reservation/v1alpha1/contract_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -22,15 +22,15 @@ import ( // ContractSpec defines the desired state of Contract. type ContractSpec struct { - // This is the flavour on which the contract is based. It is used to lifetime maintain the critical characteristics of the contract. - Flavour nodecorev1alpha1.Flavour `json:"flavour"` + // This is the flavor on which the contract is based. It is used to lifetime maintain the critical characteristics of the contract. + Flavor nodecorev1alpha1.Flavor `json:"flavor"` // TransactionID is the ID of the transaction that this contract is part of TransactionID string `json:"transactionID"` - // The partition represents the dimension of the resources sold/bought. + // The configuration represents the dimension of the resources sold/bought. // So it will reflect the dimension of the resources allocated on the remote cluster and reflected on the local virtual node. - Partition *nodecorev1alpha1.Partition `json:"partition,omitempty"` + Configuration *nodecorev1alpha1.Configuration `json:"configuration,omitempty"` // This is the Node identity of the buyer FLUIDOS Node. Buyer nodecorev1alpha1.NodeIdentity `json:"buyer"` @@ -42,13 +42,16 @@ type ContractSpec struct { Seller nodecorev1alpha1.NodeIdentity `json:"seller"` // This credentials will be used by the customer to connect and enstablish a peering with the seller FLUIDOS Node through Liqo. - SellerCredentials nodecorev1alpha1.LiqoCredentials `json:"sellerCredentials"` + PeeringTargetCredentials nodecorev1alpha1.LiqoCredentials `json:"peeringTargetCredentials"` // This is the expiration time of the contract. It can be empty if the contract is not time limited. ExpirationTime string `json:"expirationTime,omitempty"` // This contains additional information about the contract if needed. ExtraInformation map[string]string `json:"extraInformation,omitempty"` + + // NetworkRequests contains the reference to the resource containing the network requests. + NetworkRequests string `json:"networkRequests,omitempty"` } // ContractStatus defines the observed state of Contract. @@ -61,7 +64,8 @@ type ContractStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// +kubebuilder:printcolumn:name="Flavour ID",type=string,JSONPath=`.spec.flavour.metadata.name` +// Contract is the Schema for the contracts API. +// +kubebuilder:printcolumn:name="Flavor ID",type=string,JSONPath=`.spec.flavor.metadata.name` // +kubebuilder:printcolumn:name="Buyer Name",type=string,JSONPath=`.spec.buyer.nodeID` // +kubebuilder:printcolumn:name="Buyer Domain",type=string,priority=1,JSONPath=`.spec.buyer.domain` // +kubebuilder:printcolumn:name="Seller Name",type=string,JSONPath=`.spec.seller.nodeID` @@ -69,7 +73,6 @@ type ContractStatus struct { // +kubebuilder:printcolumn:name="Transaction ID",type=string,priority=1,JSONPath=`.spec.transactionID` // +kubebuilder:printcolumn:name="Buyer Liqo ID",type=string,priority=1,JSONPath=`.spec.buyerClusterID` // +kubebuilder:printcolumn:name="Expiration Time",type=string,priority=1,JSONPath=`.spec.expirationTime` -// Contract is the Schema for the contracts API. type Contract struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -80,7 +83,7 @@ type Contract struct { //+kubebuilder:object:root=true -// ContractList contains a list of Contract +// ContractList contains a list of Contract. type ContractList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/reservation/v1alpha1/contract_webhook.go b/apis/reservation/v1alpha1/contract_webhook.go new file mode 100644 index 0000000..6759ebc --- /dev/null +++ b/apis/reservation/v1alpha1/contract_webhook.go @@ -0,0 +1,110 @@ +// Copyright 2022-2024 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 ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" +) + +// log is for logging in this package. +var contractlog = logf.Log.WithName("contract-resource") + +// SetupWebhookWithManager sets up and registers the webhook with the manager. +func (r *Contract) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/mutate-contract-fluidos-eu-v1alpha1-contract,mutating=true,failurePolicy=fail,sideEffects=None,groups=contract.fluidos.eu,resources=contracts,verbs=create;update,versions=v1alpha1,name=mcontract.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Contract{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *Contract) Default() { + contractlog.Info("CONTRACT DEFAULT WEBHOOK") + contractlog.Info("default", "name", r.Name) +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/validate-contract-fluidos-eu-v1alpha1-contract,mutating=false,failurePolicy=fail,sideEffects=None,groups=contract.fluidos.eu,resources=contracts,verbs=create;update,versions=v1alpha1,name=vcontract.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Contract{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *Contract) ValidateCreate() (admission.Warnings, error) { + contractlog.Info("CONTRACT VALIDATE CREATE WEBHOOK") + contractlog.Info("validate create", "name", r.Name) + + if err := validateConfiguration(r.Spec.Configuration); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *Contract) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + contractlog.Info("CONTRACT VALIDATE UPDATE WEBHOOK") + contractlog.Info("validate update", "name", r.Name) + + contractlog.Info("old", "old", old) + + if err := validateConfiguration(r.Spec.Configuration); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *Contract) ValidateDelete() (admission.Warnings, error) { + contractlog.Info("CONTRACT VALIDATE DELETE WEBHOOK") + contractlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} + +func validateConfiguration(configuration *nodecorev1alpha1.Configuration) error { + if configuration == nil { + return nil + } + // Validate creation of Contract checking ContractType->typeIdentifier matches the struct inside the ContractType->TypeData + typeIdentifier, _, err := nodecorev1alpha1.ParseConfiguration(configuration) + if err != nil { + return err + } + switch typeIdentifier { + case nodecorev1alpha1.TypeK8Slice: + contractlog.Info("Configuration Flavor Type is K8Slice") + case nodecorev1alpha1.TypeVM: + contractlog.Info("Configuration Flavor Type is VM") + case nodecorev1alpha1.TypeService: + contractlog.Info("Configuration Flavor Type is Service") + default: + contractlog.Info("Configuration Flavor Type is not valid") + } + + return nil +} diff --git a/apis/reservation/v1alpha1/groupversion_info.go b/apis/reservation/v1alpha1/groupversion_info.go index 3e0376c..4f4df62 100644 --- a/apis/reservation/v1alpha1/groupversion_info.go +++ b/apis/reservation/v1alpha1/groupversion_info.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,10 +23,10 @@ import ( ) var ( - // GroupVersion is group version used to register these objects + // GroupVersion is group version used to register these objects. GroupVersion = schema.GroupVersion{Group: "reservation.fluidos.eu", Version: "v1alpha1"} - // SchemeBuilder is used to add go types to the GroupVersionKind scheme + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} // AddToScheme adds the types in this group-version to the given scheme. diff --git a/apis/reservation/v1alpha1/reservation_status.go b/apis/reservation/v1alpha1/reservation_status.go index e38fa1c..023e27d 100644 --- a/apis/reservation/v1alpha1/reservation_status.go +++ b/apis/reservation/v1alpha1/reservation_status.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,19 +19,19 @@ import ( "github.com/fluidos-project/node/pkg/utils/tools" ) -// SetPhase sets the phase of the discovery +// SetPhase sets the phase of the discovery. func (r *Reservation) SetPhase(phase nodecorev1alpha1.Phase, msg string) { r.Status.Phase.Phase = phase r.Status.Phase.LastChangeTime = tools.GetTimeNow() r.Status.Phase.Message = msg } -// SetReserveStatus sets the status of the reserve (if it is a reserve) +// SetReserveStatus sets the status of the reserve (if it is a reserve). func (r *Reservation) SetReserveStatus(status nodecorev1alpha1.Phase) { r.Status.ReservePhase = status } -// SetPurchaseStatus sets the status of the purchase (if it is a purchase) +// SetPurchaseStatus sets the status of the purchase (if it is a purchase). func (r *Reservation) SetPurchaseStatus(status nodecorev1alpha1.Phase) { r.Status.PurchasePhase = status } diff --git a/apis/reservation/v1alpha1/reservation_types.go b/apis/reservation/v1alpha1/reservation_types.go index 3cd682a..1a84fd8 100644 --- a/apis/reservation/v1alpha1/reservation_types.go +++ b/apis/reservation/v1alpha1/reservation_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import ( nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" ) -// ReservationSpec defines the desired state of Reservation +// ReservationSpec defines the desired state of Reservation. type ReservationSpec struct { // SolverID is the ID of the solver that asks for the reservation @@ -32,8 +32,8 @@ type ReservationSpec struct { // This is the Node identity of the seller FLUIDOS Node. Seller nodecorev1alpha1.NodeIdentity `json:"seller"` - // Parition is the partition of the flavour that is being reserved - Partition *nodecorev1alpha1.Partition `json:"partition,omitempty"` + // Configuration is the configuration of the flavour that is being reserved + Configuration *nodecorev1alpha1.Configuration `json:"configuration,omitempty"` // Reserve indicates if the reservation is a reserve or not Reserve bool `json:"reserve,omitempty"` @@ -45,7 +45,7 @@ type ReservationSpec struct { PeeringCandidate nodecorev1alpha1.GenericRef `json:"peeringCandidate,omitempty"` } -// ReservationStatus defines the observed state of Reservation +// ReservationStatus defines the observed state of Reservation. type ReservationStatus struct { // This is the current phase of the reservation Phase nodecorev1alpha1.PhaseStatus `json:"phase"` @@ -66,6 +66,7 @@ type ReservationStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status +// Reservation is the Schema for the reservations API. // +kubebuilder:printcolumn:name="Solver ID",type=string,JSONPath=`.spec.solverID` // +kubebuilder:printcolumn:name="Reserve",type=boolean,JSONPath=`.spec.reserve` // +kubebuilder:printcolumn:name="Purchase",type=boolean,JSONPath=`.spec.purchase` @@ -78,7 +79,6 @@ type ReservationStatus struct { // +kubebuilder:printcolumn:name="Contract Name",type=string,JSONPath=`.status.contract.name` // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.phase.phase` // +kubebuilder:printcolumn:name="Message",type=string,priority=1,JSONPath=`.status.phase.message` -// Reservation is the Schema for the reservations API type Reservation struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -89,7 +89,7 @@ type Reservation struct { //+kubebuilder:object:root=true -// ReservationList contains a list of Reservation +// ReservationList contains a list of Reservation. type ReservationList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/reservation/v1alpha1/reservation_webhook.go b/apis/reservation/v1alpha1/reservation_webhook.go new file mode 100644 index 0000000..471d095 --- /dev/null +++ b/apis/reservation/v1alpha1/reservation_webhook.go @@ -0,0 +1,85 @@ +// Copyright 2022-2024 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 ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var reservationlog = logf.Log.WithName("reservation-resource") + +// SetupWebhookWithManager sets up and registers the webhook with the manager. +func (r *Reservation) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/mutate-reservation-fluidos-eu-v1alpha1-reservation,mutating=true,failurePolicy=fail,sideEffects=None,groups=reservation.fluidos.eu,resources=reservations,verbs=create;update,versions=v1alpha1,name=mreservation.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Reservation{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *Reservation) Default() { + reservationlog.Info("RESERVATION DEFAULT WEBHOOK") + reservationlog.Info("default", "name", r.Name) +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/validate-reservation-fluidos-eu-v1alpha1-reservation,mutating=false,failurePolicy=fail,sideEffects=None,groups=reservation.fluidos.eu,resources=reservations,verbs=create;update,versions=v1alpha1,name=vreservation.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Reservation{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *Reservation) ValidateCreate() (admission.Warnings, error) { + reservationlog.Info("RESERVATION VALIDATE CREATE WEBHOOK") + reservationlog.Info("validate create", "name", r.Name) + + if err := validateConfiguration(r.Spec.Configuration); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *Reservation) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + reservationlog.Info("RESERVATION VALIDATE UPDATE WEBHOOK") + reservationlog.Info("validate update", "name", r.Name) + + reservationlog.Info("old", "old", old) + + if err := validateConfiguration(r.Spec.Configuration); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *Reservation) ValidateDelete() (admission.Warnings, error) { + reservationlog.Info("RESERVATION VALIDATE DELETE WEBHOOK") + reservationlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} diff --git a/apis/reservation/v1alpha1/transaction_types.go b/apis/reservation/v1alpha1/transaction_types.go index aa3b2c4..151c404 100644 --- a/apis/reservation/v1alpha1/transaction_types.go +++ b/apis/reservation/v1alpha1/transaction_types.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,25 +20,25 @@ import ( nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" ) -// TransactionSpec defines the desired state of Transaction +// TransactionSpec defines the desired state of Transaction. type TransactionSpec struct { - // FlavourID is the ID of the flavour that is being reserved - FlavourID string `json:"flavourID"` + // FlavorID is the ID of the flavor that is being reserved + FlavorID string `json:"flavorID"` - // Buyer is the buyer Identity of the Fluidos Node that is reserving the Flavour + // Buyer is the buyer Identity of the Fluidos Node that is reserving the Flavor Buyer nodecorev1alpha1.NodeIdentity `json:"buyer"` - // ClusterID is the Liqo ClusterID of the Fluidos Node that is reserving the Flavour + // ClusterID is the Liqo ClusterID of the Fluidos Node that is reserving the Flavor ClusterID string `json:"clusterID"` - // Partition is the partition of the flavour that is being reserved - Partition *nodecorev1alpha1.Partition `json:"partition,omitempty"` + // Configuration is the configuration of the flavor that is being reserved + Configuration *nodecorev1alpha1.Configuration `json:"configuration,omitempty"` - // StartTime is the time at which the reservation should start - StartTime string `json:"startTime,omitempty"` + // ExpirationTime is the time when the reservation will expire + ExpirationTime string `json:"expirationTime,omitempty"` } -// TransactionStatus defines the observed state of Transaction +// TransactionStatus defines the observed state of Transaction. type TransactionStatus struct { // This is the current phase of the reservation Phase nodecorev1alpha1.PhaseStatus `json:"phase"` @@ -47,13 +47,13 @@ type TransactionStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -// +kubebuilder:printcolumn:name="Flavour ID",type="string",JSONPath=".spec.flavourID" +// Transaction is the Schema for the transactions API. +// +kubebuilder:printcolumn:name="Flavor ID",type="string",JSONPath=".spec.flavorID" // +kubebuilder:printcolumn:name="Buyer Name",type="string",JSONPath=".spec.buyer.nodeID" // +kubebuilder:printcolumn:name="Buyer IP",type="string",priority=1,JSONPath=".spec.buyer.ip" // +kubebuilder:printcolumn:name="Buyer Domain",type="string",priority=1,JSONPath=".spec.buyer.domain" // +kubebuilder:printcolumn:name="Cluster ID",type="string",JSONPath=".spec.clusterID" // +kubebuilder:printcolumn:name="Start Time",type="string",JSONPath=".spec.startTime" -// Transaction is the Schema for the transactions API type Transaction struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -64,7 +64,7 @@ type Transaction struct { //+kubebuilder:object:root=true -// TransactionList contains a list of Transaction +// TransactionList contains a list of Transaction. type TransactionList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/apis/reservation/v1alpha1/transaction_webhook.go b/apis/reservation/v1alpha1/transaction_webhook.go new file mode 100644 index 0000000..1d23936 --- /dev/null +++ b/apis/reservation/v1alpha1/transaction_webhook.go @@ -0,0 +1,85 @@ +// Copyright 2022-2024 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 ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var transactionlog = logf.Log.WithName("transaction-resource") + +// SetupWebhookWithManager sets up and registers the webhook with the manager. +func (r *Transaction) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/mutate-reservation-fluidos-eu-v1alpha1-transaction,mutating=true,failurePolicy=fail,sideEffects=None,groups=reservation.fluidos.eu,resources=transactions,verbs=create;update,versions=v1alpha1,name=mtransaction.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Transaction{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *Transaction) Default() { + transactionlog.Info("TRANSACTION DEFAULT WEBHOOK") + transactionlog.Info("default", "name", r.Name) +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//nolint:lll // kubebuilder directives are too long, but they must be on the same line +//+kubebuilder:webhook:path=/validate-reservation-fluidos-eu-v1alpha1-transaction,mutating=false,failurePolicy=fail,sideEffects=None,groups=reservation.fluidos.eu,resources=transactions,verbs=create;update,versions=v1alpha1,name=vtransaction.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Transaction{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *Transaction) ValidateCreate() (admission.Warnings, error) { + transactionlog.Info("TRANSACTION VALIDATE CREATE WEBHOOK") + transactionlog.Info("validate create", "name", r.Name) + + if err := validateConfiguration(r.Spec.Configuration); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *Transaction) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + transactionlog.Info("TRANSACTION VALIDATE UPDATE WEBHOOK") + transactionlog.Info("validate update", "name", r.Name) + + transactionlog.Info("old", "name", old) + + if err := validateConfiguration(r.Spec.Configuration); err != nil { + return nil, err + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *Transaction) ValidateDelete() (admission.Warnings, error) { + transactionlog.Info("TRANSACTION VALIDATE DELETE WEBHOOK") + transactionlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} diff --git a/apis/reservation/v1alpha1/zz_generated.deepcopy.go b/apis/reservation/v1alpha1/zz_generated.deepcopy.go index 341ac43..c47928b 100644 --- a/apis/reservation/v1alpha1/zz_generated.deepcopy.go +++ b/apis/reservation/v1alpha1/zz_generated.deepcopy.go @@ -1,6 +1,6 @@ //go:build !ignore_autogenerated -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ package v1alpha1 import ( nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -85,15 +85,15 @@ func (in *ContractList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContractSpec) DeepCopyInto(out *ContractSpec) { *out = *in - in.Flavour.DeepCopyInto(&out.Flavour) - if in.Partition != nil { - in, out := &in.Partition, &out.Partition - *out = new(nodecorev1alpha1.Partition) + in.Flavor.DeepCopyInto(&out.Flavor) + if in.Configuration != nil { + in, out := &in.Configuration, &out.Configuration + *out = new(nodecorev1alpha1.Configuration) (*in).DeepCopyInto(*out) } out.Buyer = in.Buyer out.Seller = in.Seller - out.SellerCredentials = in.SellerCredentials + out.PeeringTargetCredentials = in.PeeringTargetCredentials if in.ExtraInformation != nil { in, out := &in.ExtraInformation, &out.ExtraInformation *out = make(map[string]string, len(*in)) @@ -193,9 +193,9 @@ func (in *ReservationSpec) DeepCopyInto(out *ReservationSpec) { *out = *in out.Buyer = in.Buyer out.Seller = in.Seller - if in.Partition != nil { - in, out := &in.Partition, &out.Partition - *out = new(nodecorev1alpha1.Partition) + if in.Configuration != nil { + in, out := &in.Configuration, &out.Configuration + *out = new(nodecorev1alpha1.Configuration) (*in).DeepCopyInto(*out) } out.PeeringCandidate = in.PeeringCandidate @@ -291,9 +291,9 @@ func (in *TransactionList) DeepCopyObject() runtime.Object { func (in *TransactionSpec) DeepCopyInto(out *TransactionSpec) { *out = *in out.Buyer = in.Buyer - if in.Partition != nil { - in, out := &in.Partition, &out.Partition - *out = new(nodecorev1alpha1.Partition) + if in.Configuration != nil { + in, out := &in.Configuration, &out.Configuration + *out = new(nodecorev1alpha1.Configuration) (*in).DeepCopyInto(*out) } } diff --git a/cmd/local-resource-manager/doc.go b/cmd/local-resource-manager/doc.go index f433465..fc657db 100644 --- a/cmd/local-resource-manager/doc.go +++ b/cmd/local-resource-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/cmd/local-resource-manager/main.go b/cmd/local-resource-manager/main.go index 558e454..2da8908 100644 --- a/cmd/local-resource-manager/main.go +++ b/cmd/local-resource-manager/main.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,17 +15,19 @@ package main import ( - "context" "flag" - "net/http" "os" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/klog/v2" metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/webhook" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" localresourcemanager "github.com/fluidos-project/node/pkg/local-resource-manager" @@ -45,11 +47,14 @@ func init() { } func main() { + var metricsAddr string + var enableLeaderElection bool var probeAddr string + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.StringVar(&flags.AMOUNT, "amount", "", "Amount of money set for the flavours of this node") - flag.StringVar(&flags.CURRENCY, "currency", "", "Currency of the money set for the flavours of this node") - flag.StringVar(&flags.PERIOD, "period", "", "Period set for the flavours of this node") + flag.StringVar(&flags.AMOUNT, "amount", "", "Amount of money set for the flavors of this node") + flag.StringVar(&flags.CURRENCY, "currency", "", "Currency of the money set for the flavors of this node") + flag.StringVar(&flags.PERIOD, "period", "", "Period set for the flavors of this node") flag.StringVar(&flags.ResourceType, "resources-types", "k8s-fluidos", "Type of the Flavour related to k8s resources") flag.StringVar(&flags.CPUMin, "cpu-min", "0", "Minimum CPU value") flag.StringVar(&flags.MemoryMin, "memory-min", "0", "Minimum memory value") @@ -57,13 +62,25 @@ func main() { flag.StringVar(&flags.CPUStep, "cpu-step", "0", "CPU step value") flag.StringVar(&flags.MemoryStep, "memory-step", "0", "Memory step value") flag.StringVar(&flags.PodsStep, "pods-step", "0", "Pods step value") - flag.Int64Var(&flags.MinCount, "min-count", 0, "Minimum number of flavours") - flag.Int64Var(&flags.MaxCount, "max-count", 0, "Maximum number of flavours") + flag.Int64Var(&flags.MinCount, "min-count", 0, "Minimum number of flavors") + flag.Int64Var(&flags.MaxCount, "max-count", 0, "Maximum number of flavors") flag.StringVar(&flags.ResourceNodeLabel, "node-resource-label", "node-role.fluidos.eu/resources", - "Label used to filter the k8s nodes from which create flavours") - + "Label used to filter the k8s nodes from which create flavors") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + enableWH := flag.Bool("enable-webhooks", true, "Enable webhooks server") + enableAutoDiscovery := flag.Bool("enable-auto-discovery", true, "Enable auto discovery of the node resources") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) flag.Parse() + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + cfg := ctrl.GetConfigOrDie() cl, err := client.New(cfg, client.Options{Scheme: scheme}) if err != nil { @@ -71,30 +88,69 @@ func main() { os.Exit(1) } - err = localresourcemanager.Start(context.Background(), cl) + var webhookServer webhook.Server + + if *enableWH { + webhookServer = webhook.NewServer(webhook.Options{Port: 9443}) + } else { + setupLog.Info("Webhooks are disabled") + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "c7b7b7b7.fluidos.eu", + }) if err != nil { - setupLog.Error(err, "Unable to start LocalResourceManager") + setupLog.Error(err, "unable to start manager") os.Exit(1) } - mux := http.NewServeMux() - mux.HandleFunc("/healthz", healthHandler) // health check endpoint - mux.HandleFunc("/readyz", healthHandler) // readiness check endpoint + // Print something abou the mgr + setupLog.Info("Manager started", "manager", mgr) - //nolint:gosec // We don't need this kind of security check - server := &http.Server{ - Addr: probeAddr, - Handler: mux, + // Register the controller + if err = (&localresourcemanager.NodeReconciler{ + Client: cl, + Scheme: mgr.GetScheme(), + EnableAutoDiscovery: *enableAutoDiscovery, + WebhookServer: webhookServer, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Node") + os.Exit(1) } - setupLog.Info("Starting server", "address", probeAddr) - if err := server.ListenAndServe(); err != http.ErrServerClosed { - setupLog.Error(err, "Server stopped unexpectedly") + if *enableWH { + // Register Flavor webhook + klog.Info("Registering webhooks to the manager") + if err = (&nodecorev1alpha1.Flavor{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Flavor") + os.Exit(1) + } + } else { + setupLog.Info("Webhooks are disabled") + } + + // Register health checks + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + if err := mgr.AddHealthzCheck("webhook", webhookServer.StartedChecker()); err != nil { + setupLog.Error(err, "unable to set up webhook health check") os.Exit(1) } -} -func healthHandler(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("OK")) + setupLog.Info("Starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } } diff --git a/cmd/rear-controller/doc.go b/cmd/rear-controller/doc.go index 37bbf23..92bec81 100644 --- a/cmd/rear-controller/doc.go +++ b/cmd/rear-controller/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/cmd/rear-controller/main.go b/cmd/rear-controller/main.go index d3dbcaa..2a5fdbe 100644 --- a/cmd/rear-controller/main.go +++ b/cmd/rear-controller/main.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook" advertisementv1alpha1 "github.com/fluidos-project/node/apis/advertisement/v1alpha1" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" @@ -65,6 +66,7 @@ func main() { flag.StringVar(&flags.HTTPPort, "http-port", "3004", "Port of the HTTP server") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") + enableWH := flag.Bool("enable-webhooks", true, "Enable webhooks server") opts := zap.Options{ Development: true, } @@ -73,10 +75,18 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + var webhookServer webhook.Server + + if *enableWH { + webhookServer = webhook.NewServer(webhook.Options{Port: 9443}) + } else { + setupLog.Info("Webhooks are disabled") + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, - Port: 9443, + WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "efa8b828.fluidos.eu", @@ -130,6 +140,26 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Reservation") os.Exit(1) } + + if *enableWH { + // Register Reservation webhook + setupLog.Info("Registering webhooks to the manager") + if err = (&reservationv1alpha1.Reservation{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Flavor") + os.Exit(1) + } + if err = (&reservationv1alpha1.Contract{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Contract") + os.Exit(1) + } + if err = (&reservationv1alpha1.Transaction{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Transaction") + os.Exit(1) + } + } else { + setupLog.Info("Webhooks are disabled") + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { @@ -141,6 +171,11 @@ func main() { os.Exit(1) } + if err := mgr.AddHealthzCheck("webhook", webhookServer.StartedChecker()); err != nil { + setupLog.Error(err, "unable to set up webhook health check") + os.Exit(1) + } + // Periodically clear the transaction cache if err := mgr.Add(manager.RunnableFunc(gw.CacheRefresher(flags.RefreshCacheInterval))); err != nil { klog.Errorf("Unable to set up transaction cache refresher: %s", err) @@ -165,10 +200,6 @@ func main() { os.Exit(1) } - //nolint:gocritic // This code is needed to register the webhook - // pcv := discoverymanager.NewPCValidator(mgr.GetClient()) - // mgr.GetWebhookServer().Register("/validate/peeringcandidate", &webhook.Admission{Handler: pcv}) - setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") diff --git a/cmd/rear-manager/doc.go b/cmd/rear-manager/doc.go index d93beee..1a42002 100644 --- a/cmd/rear-manager/doc.go +++ b/cmd/rear-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/cmd/rear-manager/main.go b/cmd/rear-manager/main.go index b5b444e..4ef7e0a 100644 --- a/cmd/rear-manager/main.go +++ b/cmd/rear-manager/main.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/webhook" advertisementv1alpha1 "github.com/fluidos-project/node/apis/advertisement/v1alpha1" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" @@ -60,6 +61,7 @@ func main() { flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + enableWH := flag.Bool("enable-webhooks", true, "Enable webhooks server") opts := zap.Options{ Development: true, } @@ -68,10 +70,18 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + var webhookServer webhook.Server + + if *enableWH { + webhookServer = webhook.NewServer(webhook.Options{Port: 9443}) + } else { + setupLog.Info("Webhooks are disabled") + } + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, - Port: 9443, + WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "586b6b69.fluidos.eu", @@ -86,7 +96,7 @@ func main() { // Index the RemoteClusterID field of the Allocation CRD indexFuncAllocation := func(obj client.Object) []string { allocation := obj.(*nodecorev1alpha1.Allocation) - return []string{allocation.Spec.RemoteClusterID} + return []string{allocation.Name} } if err := cache.IndexField(context.Background(), &nodecorev1alpha1.Allocation{}, "spec.remoteClusterID", indexFuncAllocation); err != nil { @@ -109,20 +119,34 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Allocation") os.Exit(1) } + + if *enableWH { + // Register Solver webhook + setupLog.Info("Registering webhooks to the manager") + if err = (&nodecorev1alpha1.Solver{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Flavor") + os.Exit(1) + } + } else { + setupLog.Info("Webhooks are disabled") + } + //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } - //nolint:gocritic // This code is needed to register the webhook - // av := rearmanager.NewValidator(mgr.GetClient()) - // mgr.GetWebhookServer().Register("/validate/allocation", &webhook.Admission{Handler: av}) + if err := mgr.AddHealthzCheck("webhook", webhookServer.StartedChecker()); err != nil { + setupLog.Error(err, "unable to set up webhook health check") + os.Exit(1) + } setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { diff --git a/deployments/node/README.md b/deployments/node/README.md index 6401f3c..154e65d 100644 --- a/deployments/node/README.md +++ b/deployments/node/README.md @@ -12,12 +12,13 @@ A Helm chart for Fluidos Node | common.extraArgs | list | `[]` | Extra arguments for all fluidos-node pods | | common.nodeSelector | object | `{}` | NodeSelector for all fluidos-node pods | | common.tolerations | list | `[]` | Tolerations for all fluidos-node pods | -| localResourceManager.config.flavour.cpuMin | string | `"0"` | The minimum number of CPUs that can be requested to purchase a flavour. | -| localResourceManager.config.flavour.cpuStep | string | `"1000m"` | The CPU step that must be respected when requesting a flavour through a Flavour Selector. | -| localResourceManager.config.flavour.memoryMin | string | `"0"` | The minimum amount of memory that can be requested to purchase a flavour. | -| localResourceManager.config.flavour.memoryStep | string | `"100Mi"` | The memory step that must be respected when requesting a flavour through a Flavour Selector. | +| localResourceManager.config.enableAutoDiscovery | bool | `true` | Enable the auto-discovery of the resources. | +| localResourceManager.config.flavor.cpuMin | string | `"0"` | The minimum number of CPUs that can be requested to purchase a flavor. | +| localResourceManager.config.flavor.cpuStep | string | `"1000m"` | The CPU step that must be respected when requesting a flavor through a Flavor Selector. | +| localResourceManager.config.flavor.memoryMin | string | `"0"` | The minimum amount of memory that can be requested to purchase a flavor. | +| localResourceManager.config.flavor.memoryStep | string | `"100Mi"` | The memory step that must be respected when requesting a flavor through a Flavor Selector. | | localResourceManager.config.nodeResourceLabel | string | `"node-role.fluidos.eu/resources"` | Label used to identify the nodes from which resources are collected. | -| localResourceManager.config.resourceType | string | `"k8s-fluidos"` | This flag defines the resource type of the generated flavours. | +| localResourceManager.config.resourceType | string | `"k8s-fluidos"` | This flag defines the resource type of the generated flavors. | | localResourceManager.imageName | string | `"ghcr.io/fluidos-project/local-resource-manager"` | | | localResourceManager.pod.annotations | object | `{}` | Annotations for the local-resource-manager pod. | | localResourceManager.pod.extraArgs | list | `[]` | Extra arguments for the local-resource-manager pod. | @@ -68,6 +69,10 @@ A Helm chart for Fluidos Node | rearManager.pod.resources | object | `{"limits":{},"requests":{}}` | Resource requests and limits (https://kubernetes.io/docs/user-guide/compute-resources/) for the rear-manager pod. | | rearManager.replicas | int | `1` | The number of REAR Manager, which can be increased for active/passive high availability. | | tag | string | `""` | Images' tag to select a development version of fluidos-node instead of a release | +| webhook.deployment | object | `{"certsMount":"/tmp/k8s-webhook-server/serving-certs/"}` | Configuration for the webhook server. | +| webhook.deployment.certsMount | string | `"/tmp/k8s-webhook-server/serving-certs/"` | The mount path for the webhook certificates. | +| webhook.enabled | bool | `true` | Enable the webhook server for the local-resource-manager. | +| webhook.issuer | string | `"self-signed"` | Configuration for the webhook server. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml b/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml index e46f43d..8f22cd2 100644 --- a/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml +++ b/deployments/node/crds/advertisement.fluidos.eu_discoveries.yaml @@ -36,7 +36,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Discovery is the Schema for the discoveries API + description: Discovery is the Schema for the discoveries API. properties: apiVersion: description: |- @@ -56,138 +56,24 @@ spec: metadata: type: object spec: - description: DiscoverySpec defines the desired state of Discovery + description: DiscoverySpec defines the desired state of Discovery. properties: selector: description: |- This is the FlavourSelector that describes the characteristics of the intent that the solver is looking to satisfy This pattern corresponds to what has been defined in the REAR Protocol to do a discovery with a selector properties: - architecture: - type: string - matchSelector: - description: MatchSelector represents the criteria for selecting - Flavours through a strict match. - properties: - cpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeralStorage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + filters: + description: Filters contains the filters that the solver is using + to refining the research. type: object - rangeSelector: - description: RangeSelector represents the criteria for selecting - Flavours through a range. - properties: - MaxCpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxEph: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxGpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxMemory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxPods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxStorage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minCpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minEph: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minGpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minMemory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minPods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minStorage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - type: + x-kubernetes-preserve-unknown-fields: true + flavorType: + description: FlavorType is the type of the Flavor that the solver + is looking for. type: string required: - - architecture - - type + - flavorType type: object solverID: description: |- @@ -197,7 +83,7 @@ spec: subscribe: description: |- This flag indicates that needs to be established a subscription to the provider in case a match is found. - In order to have periodic updates of the status of the matching Flavour + In order to have periodic updates of the status of the matching Flavor type: boolean required: - selector @@ -205,7 +91,7 @@ spec: - subscribe type: object status: - description: DiscoveryStatus defines the observed state of Discovery + description: DiscoveryStatus defines the observed state of Discovery. properties: peeringCandidateList: description: This is a list of the PeeringCandidates that have been @@ -221,7 +107,7 @@ spec: items: items: description: PeeringCandidate is the Schema for the peeringcandidates - API + API. properties: apiVersion: description: |- @@ -259,13 +145,12 @@ spec: type: object spec: description: PeeringCandidateSpec defines the desired state - of PeeringCandidate + of PeeringCandidate. properties: available: type: boolean - flavour: - description: Flavour is the Schema for the flavours - API. + flavor: + description: Flavor is the Schema for the flavors API. properties: apiVersion: description: |- @@ -302,97 +187,68 @@ spec: type: string type: object spec: - description: FlavourSpec defines the desired state - of Flavour + description: FlavorSpec defines the desired state + of Flavor. properties: - characteristics: - description: |- - Characteristics contains the characteristics of the Flavour. - They are based on the type of the Flavour and can change depending on it. In this case, the type is K8S so the characteristics are CPU, Memory, GPU and EphemeralStorage. + availability: + description: Availability is the availability + flag of the Flavor. + type: boolean + flavorType: + description: FlavorType is the type of the Flavor. properties: - architecture: - description: Architecture is the architecture - of the Flavour. + typeData: + description: Raw is the raw value of the + Flavor. + type: object + x-kubernetes-preserve-unknown-fields: true + typeIdentifier: + description: Type of the Flavor. type: string - cpu: - anyOf: - - type: integer - - type: string - description: CPU is the number of CPU cores - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - description: EphemeralStorage is the amount - of ephemeral storage of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - description: GPU is the number of GPU cores - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - description: Memory is the amount of RAM - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - persistent-storage: - anyOf: - - type: integer - - type: string - description: PersistentStorage is the amount - of persistent storage of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - description: Pods is the maximum number - of pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - architecture - - cpu - - memory - - pods + - typeData + - typeIdentifier type: object - optionalFields: - description: |- - This field is used to specify the optional fields that can be retrieved from the Flavour. - In the future it will be expanded to include more optional fields defined in the REAR Protocol or custom ones. + location: + description: Location is the location of the + Flavor. properties: - availability: - description: |- - Availability is the availability flag of the Flavour. - It is a field inherited from the REAR Protocol specifications. - type: boolean - workerID: - description: WorkerID is the ID of the worker - that provides the Flavour. + additionalNotes: + description: AdditionalNotes are additional + notes of the location. + type: string + city: + description: City is the city of the location. + type: string + country: + description: Country is the country of the + location. + type: string + latitude: + description: Latitude is the latitude of + the location. + type: string + longitude: + description: Longitude is the longitude + of the location. type: string type: object + networkPropertyType: + description: NetworkPropertyType is the network + property type of the Flavor. + type: string owner: description: Owner contains the identity info - of the owner of the Flavour. It can be unknown - if the Flavour is provided by a reseller or + of the owner of the Flavor. It can be unknown + if the Flavor is provided by a reseller or a third party. properties: domain: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -400,93 +256,9 @@ spec: - ip - nodeID type: object - policy: - description: Policy contains the policy of the - Flavour. The policy describes the partitioning - and aggregation properties of the Flavour. - properties: - aggregatable: - description: Aggregatable contains the aggregation - properties of the Flavour. - properties: - maxCount: - description: MaxCount is the maximum - requirable number of instances of - the Flavour. - type: integer - minCount: - description: MinCount is the minimum - requirable number of instances of - the Flavour. - type: integer - required: - - maxCount - - minCount - type: object - partitionable: - description: Partitionable contains the - partitioning properties of the Flavour. - properties: - cpuMin: - anyOf: - - type: integer - - type: string - description: CpuMin is the minimum requirable - number of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - cpuStep: - anyOf: - - type: integer - - type: string - description: CpuStep is the incremental - value of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryMin: - anyOf: - - type: integer - - type: string - description: MemoryMin is the minimum - requirable amount of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryStep: - anyOf: - - type: integer - - type: string - description: MemoryStep is the incremental - value of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsMin: - anyOf: - - type: integer - - type: string - description: PodsMin is the minimum - requirable number of pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsStep: - anyOf: - - type: integer - - type: string - description: PodsStep is the incremental - value of pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - cpuMin - - cpuStep - - memoryMin - - memoryStep - - podsMin - - podsStep - type: object - type: object price: description: Price contains the price model - of the Flavour. + of the Flavor. properties: amount: description: Amount is the amount of the @@ -507,43 +279,32 @@ spec: type: object providerID: description: |- - ProviderID is the ID of the FLUIDOS Node ID that provides this Flavour. + ProviderID is the ID of the FLUIDOS Node ID that provides this Flavor. It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain type: string - quantityAvailable: - description: This field is used to represent - the available quantity of transactions of - the Flavour. - type: integer - type: - description: Type is the type of the Flavour. - Currently, only K8S is supported. - type: string required: - - characteristics - - optionalFields + - availability + - flavorType - owner - - policy - price - providerID - - type type: object status: - description: FlavourStatus defines the observed - state of Flavour. + description: FlavorStatus defines the observed state + of Flavor. properties: creationTime: description: This field represents the creation - time of the Flavour. + time of the Flavor. type: string expirationTime: description: This field represents the expiration - time of the Flavour. It is used to determine - when the Flavour is no longer valid. + time of the Flavor. It is used to determine + when the Flavor is no longer valid. type: string lastUpdateTime: description: This field represents the last - update time of the Flavour. + update time of the Flavor. type: string required: - creationTime @@ -555,12 +316,12 @@ spec: type: string required: - available - - flavour + - flavor - solverID type: object status: description: PeeringCandidateStatus defines the observed - state of PeeringCandidate + state of PeeringCandidate. properties: creationTime: description: This field represents the creation time @@ -639,6 +400,7 @@ spec: message: type: string phase: + description: Phase represents the phase of the solver. type: string startTime: type: string diff --git a/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml b/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml index 14343eb..2fc765a 100644 --- a/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml +++ b/deployments/node/crds/advertisement.fluidos.eu_peeringcandidates.yaml @@ -17,7 +17,7 @@ spec: - name: v1alpha1 schema: openAPIV3Schema: - description: PeeringCandidate is the Schema for the peeringcandidates API + description: PeeringCandidate is the Schema for the peeringcandidates API. properties: apiVersion: description: |- @@ -37,12 +37,12 @@ spec: metadata: type: object spec: - description: PeeringCandidateSpec defines the desired state of PeeringCandidate + description: PeeringCandidateSpec defines the desired state of PeeringCandidate. properties: available: type: boolean - flavour: - description: Flavour is the Schema for the flavours API. + flavor: + description: Flavor is the Schema for the flavors API. properties: apiVersion: description: |- @@ -79,91 +79,61 @@ spec: type: string type: object spec: - description: FlavourSpec defines the desired state of Flavour + description: FlavorSpec defines the desired state of Flavor. properties: - characteristics: - description: |- - Characteristics contains the characteristics of the Flavour. - They are based on the type of the Flavour and can change depending on it. In this case, the type is K8S so the characteristics are CPU, Memory, GPU and EphemeralStorage. + availability: + description: Availability is the availability flag of the + Flavor. + type: boolean + flavorType: + description: FlavorType is the type of the Flavor. properties: - architecture: - description: Architecture is the architecture of the Flavour. + typeData: + description: Raw is the raw value of the Flavor. + type: object + x-kubernetes-preserve-unknown-fields: true + typeIdentifier: + description: Type of the Flavor. type: string - cpu: - anyOf: - - type: integer - - type: string - description: CPU is the number of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - description: EphemeralStorage is the amount of ephemeral - storage of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - description: GPU is the number of GPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - description: Memory is the amount of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - persistent-storage: - anyOf: - - type: integer - - type: string - description: PersistentStorage is the amount of persistent - storage of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - description: Pods is the maximum number of pods of the - Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - architecture - - cpu - - memory - - pods + - typeData + - typeIdentifier type: object - optionalFields: - description: |- - This field is used to specify the optional fields that can be retrieved from the Flavour. - In the future it will be expanded to include more optional fields defined in the REAR Protocol or custom ones. + location: + description: Location is the location of the Flavor. properties: - availability: - description: |- - Availability is the availability flag of the Flavour. - It is a field inherited from the REAR Protocol specifications. - type: boolean - workerID: - description: WorkerID is the ID of the worker that provides - the Flavour. + additionalNotes: + description: AdditionalNotes are additional notes of the + location. + type: string + city: + description: City is the city of the location. + type: string + country: + description: Country is the country of the location. + type: string + latitude: + description: Latitude is the latitude of the location. + type: string + longitude: + description: Longitude is the longitude of the location. type: string type: object + networkPropertyType: + description: NetworkPropertyType is the network property type + of the Flavor. + type: string owner: description: Owner contains the identity info of the owner - of the Flavour. It can be unknown if the Flavour is provided + of the Flavor. It can be unknown if the Flavor is provided by a reseller or a third party. properties: domain: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -171,90 +141,8 @@ spec: - ip - nodeID type: object - policy: - description: Policy contains the policy of the Flavour. The - policy describes the partitioning and aggregation properties - of the Flavour. - properties: - aggregatable: - description: Aggregatable contains the aggregation properties - of the Flavour. - properties: - maxCount: - description: MaxCount is the maximum requirable number - of instances of the Flavour. - type: integer - minCount: - description: MinCount is the minimum requirable number - of instances of the Flavour. - type: integer - required: - - maxCount - - minCount - type: object - partitionable: - description: Partitionable contains the partitioning properties - of the Flavour. - properties: - cpuMin: - anyOf: - - type: integer - - type: string - description: CpuMin is the minimum requirable number - of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - cpuStep: - anyOf: - - type: integer - - type: string - description: CpuStep is the incremental value of CPU - cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryMin: - anyOf: - - type: integer - - type: string - description: MemoryMin is the minimum requirable amount - of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryStep: - anyOf: - - type: integer - - type: string - description: MemoryStep is the incremental value of - RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsMin: - anyOf: - - type: integer - - type: string - description: PodsMin is the minimum requirable number - of pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsStep: - anyOf: - - type: integer - - type: string - description: PodsStep is the incremental value of - pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - cpuMin - - cpuStep - - memoryMin - - memoryStep - - podsMin - - podsStep - type: object - type: object price: - description: Price contains the price model of the Flavour. + description: Price contains the price model of the Flavor. properties: amount: description: Amount is the amount of the price. @@ -272,41 +160,31 @@ spec: type: object providerID: description: |- - ProviderID is the ID of the FLUIDOS Node ID that provides this Flavour. + ProviderID is the ID of the FLUIDOS Node ID that provides this Flavor. It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain type: string - quantityAvailable: - description: This field is used to represent the available - quantity of transactions of the Flavour. - type: integer - type: - description: Type is the type of the Flavour. Currently, only - K8S is supported. - type: string required: - - characteristics - - optionalFields + - availability + - flavorType - owner - - policy - price - providerID - - type type: object status: - description: FlavourStatus defines the observed state of Flavour. + description: FlavorStatus defines the observed state of Flavor. properties: creationTime: description: This field represents the creation time of the - Flavour. + Flavor. type: string expirationTime: description: This field represents the expiration time of - the Flavour. It is used to determine when the Flavour is - no longer valid. + the Flavor. It is used to determine when the Flavor is no + longer valid. type: string lastUpdateTime: description: This field represents the last update time of - the Flavour. + the Flavor. type: string required: - creationTime @@ -318,11 +196,11 @@ spec: type: string required: - available - - flavour + - flavor - solverID type: object status: - description: PeeringCandidateStatus defines the observed state of PeeringCandidate + description: PeeringCandidateStatus defines the observed state of PeeringCandidate. properties: creationTime: description: This field represents the creation time of the PeeringCandidate. diff --git a/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml b/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml index 278ec22..79dad9f 100644 --- a/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml +++ b/deployments/node/crds/nodecore.fluidos.eu_allocations.yaml @@ -37,7 +37,7 @@ spec: metadata: type: object spec: - description: AllocationSpec defines the desired state of Allocation + description: AllocationSpec defines the desired state of Allocation. properties: contract: description: This is the reference to the contract related to the @@ -52,11 +52,6 @@ spec: empty in case of cluster-wide resources. type: string type: object - 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 forwarding: description: |- This flag indicates if the allocation is a forwarding allocation @@ -67,22 +62,8 @@ spec: 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 - nodeName: - description: This is the corresponding Node or VirtualNode local name - type: string - remoteClusterID: - description: This is the ID of the cluster that owns the allocation. - type: string - 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 - intentID - - nodeName - - type type: object status: description: AllocationStatus defines the observed state of Allocation. diff --git a/deployments/node/crds/nodecore.fluidos.eu_flavors.yaml b/deployments/node/crds/nodecore.fluidos.eu_flavors.yaml new file mode 100644 index 0000000..45f8c0d --- /dev/null +++ b/deployments/node/crds/nodecore.fluidos.eu_flavors.yaml @@ -0,0 +1,173 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.14.0 + name: flavors.nodecore.fluidos.eu +spec: + group: nodecore.fluidos.eu + names: + kind: Flavor + listKind: FlavorList + plural: flavors + shortNames: + - fl + singular: flavor + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.providerID + name: Provider ID + type: string + - jsonPath: .spec.flavorType.typeIdentifier + name: Type + type: string + - jsonPath: .spec.owner.nodeID + name: Owner Name + priority: 1 + type: string + - jsonPath: .spec.availability + name: Available + type: boolean + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + - jsonPath: .metadata.ownerReferences[0].name + name: Kubernetes Node Owner + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: Flavor is the Schema for the flavors API. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: FlavorSpec defines the desired state of Flavor. + properties: + availability: + description: Availability is the availability flag of the Flavor. + type: boolean + flavorType: + description: FlavorType is the type of the Flavor. + properties: + typeData: + description: Raw is the raw value of the Flavor. + type: object + x-kubernetes-preserve-unknown-fields: true + typeIdentifier: + description: Type of the Flavor. + type: string + required: + - typeData + - typeIdentifier + type: object + location: + description: Location is the location of the Flavor. + properties: + additionalNotes: + description: AdditionalNotes are additional notes of the location. + type: string + city: + description: City is the city of the location. + type: string + country: + description: Country is the country of the location. + type: string + latitude: + description: Latitude is the latitude of the location. + type: string + longitude: + description: Longitude is the longitude of the location. + type: string + type: object + networkPropertyType: + description: NetworkPropertyType is the network property type of the + Flavor. + type: string + owner: + description: Owner contains the identity info of the owner of the + Flavor. It can be unknown if the Flavor is provided by a reseller + or a third party. + properties: + domain: + type: string + ip: + type: string + liqoID: + type: string + nodeID: + type: string + required: + - domain + - ip + - nodeID + type: object + price: + description: Price contains the price model of the Flavor. + properties: + amount: + description: Amount is the amount of the price. + type: string + currency: + description: Currency is the currency of the price. + type: string + period: + description: Period is the period of the price. + type: string + required: + - amount + - currency + - period + type: object + providerID: + description: |- + ProviderID is the ID of the FLUIDOS Node ID that provides this Flavor. + It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain + type: string + required: + - availability + - flavorType + - owner + - price + - providerID + type: object + status: + description: FlavorStatus defines the observed state of Flavor. + properties: + creationTime: + description: This field represents the creation time of the Flavor. + type: string + expirationTime: + description: This field represents the expiration time of the Flavor. + It is used to determine when the Flavor is no longer valid. + type: string + lastUpdateTime: + description: This field represents the last update time of the Flavor. + type: string + required: + - creationTime + - expirationTime + - lastUpdateTime + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deployments/node/crds/nodecore.fluidos.eu_flavours.yaml b/deployments/node/crds/nodecore.fluidos.eu_flavours.yaml deleted file mode 100644 index dceef18..0000000 --- a/deployments/node/crds/nodecore.fluidos.eu_flavours.yaml +++ /dev/null @@ -1,302 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.14.0 - name: flavours.nodecore.fluidos.eu -spec: - group: nodecore.fluidos.eu - names: - kind: Flavour - listKind: FlavourList - plural: flavours - singular: flavour - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.providerID - name: Provider ID - type: string - - jsonPath: .spec.type - name: Type - type: string - - jsonPath: .spec.characteristics.cpu - name: CPU - priority: 1 - type: string - - jsonPath: .spec.characteristics.memory - name: Memory - priority: 1 - type: string - - jsonPath: .spec.owner.nodeID - name: Owner Name - priority: 1 - type: string - - jsonPath: .spec.owner.domain - name: Owner Domain - priority: 1 - type: string - - jsonPath: .spec.optionalFields.availability - name: Available - type: boolean - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: Flavour is the Schema for the flavours API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: FlavourSpec defines the desired state of Flavour - properties: - characteristics: - description: |- - Characteristics contains the characteristics of the Flavour. - They are based on the type of the Flavour and can change depending on it. In this case, the type is K8S so the characteristics are CPU, Memory, GPU and EphemeralStorage. - properties: - architecture: - description: Architecture is the architecture of the Flavour. - type: string - cpu: - anyOf: - - type: integer - - type: string - description: CPU is the number of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - description: EphemeralStorage is the amount of ephemeral storage - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - description: GPU is the number of GPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - description: Memory is the amount of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - persistent-storage: - anyOf: - - type: integer - - type: string - description: PersistentStorage is the amount of persistent storage - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - description: Pods is the maximum number of pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - architecture - - cpu - - memory - - pods - type: object - optionalFields: - description: |- - This field is used to specify the optional fields that can be retrieved from the Flavour. - In the future it will be expanded to include more optional fields defined in the REAR Protocol or custom ones. - properties: - availability: - description: |- - Availability is the availability flag of the Flavour. - It is a field inherited from the REAR Protocol specifications. - type: boolean - workerID: - description: WorkerID is the ID of the worker that provides the - Flavour. - type: string - type: object - owner: - description: Owner contains the identity info of the owner of the - Flavour. It can be unknown if the Flavour is provided by a reseller - or a third party. - properties: - domain: - type: string - ip: - type: string - nodeID: - type: string - required: - - domain - - ip - - nodeID - type: object - policy: - description: Policy contains the policy of the Flavour. The policy - describes the partitioning and aggregation properties of the Flavour. - properties: - aggregatable: - description: Aggregatable contains the aggregation properties - of the Flavour. - properties: - maxCount: - description: MaxCount is the maximum requirable number of - instances of the Flavour. - type: integer - minCount: - description: MinCount is the minimum requirable number of - instances of the Flavour. - type: integer - required: - - maxCount - - minCount - type: object - partitionable: - description: Partitionable contains the partitioning properties - of the Flavour. - properties: - cpuMin: - anyOf: - - type: integer - - type: string - description: CpuMin is the minimum requirable number of CPU - cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - cpuStep: - anyOf: - - type: integer - - type: string - description: CpuStep is the incremental value of CPU cores - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryMin: - anyOf: - - type: integer - - type: string - description: MemoryMin is the minimum requirable amount of - RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryStep: - anyOf: - - type: integer - - type: string - description: MemoryStep is the incremental value of RAM of - the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsMin: - anyOf: - - type: integer - - type: string - description: PodsMin is the minimum requirable number of pods - of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsStep: - anyOf: - - type: integer - - type: string - description: PodsStep is the incremental value of pods of - the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - cpuMin - - cpuStep - - memoryMin - - memoryStep - - podsMin - - podsStep - type: object - type: object - price: - description: Price contains the price model of the Flavour. - properties: - amount: - description: Amount is the amount of the price. - type: string - currency: - description: Currency is the currency of the price. - type: string - period: - description: Period is the period of the price. - type: string - required: - - amount - - currency - - period - type: object - providerID: - description: |- - ProviderID is the ID of the FLUIDOS Node ID that provides this Flavour. - It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain - type: string - quantityAvailable: - description: This field is used to represent the available quantity - of transactions of the Flavour. - type: integer - type: - description: Type is the type of the Flavour. Currently, only K8S - is supported. - type: string - required: - - characteristics - - optionalFields - - owner - - policy - - price - - providerID - - type - type: object - status: - description: FlavourStatus defines the observed state of Flavour. - properties: - creationTime: - description: This field represents the creation time of the Flavour. - type: string - expirationTime: - description: This field represents the expiration time of the Flavour. - It is used to determine when the Flavour is no longer valid. - type: string - lastUpdateTime: - description: This field represents the last update time of the Flavour. - type: string - required: - - creationTime - - expirationTime - - lastUpdateTime - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml b/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml index 7975704..c910725 100644 --- a/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml +++ b/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml @@ -24,7 +24,7 @@ spec: - jsonPath: .spec.reserveAndBuy name: Reserve and Buy type: boolean - - jsonPath: .spec.enstablishPeering + - jsonPath: .spec.establishPeering name: Peering type: boolean - jsonPath: .status.findCandidate @@ -53,7 +53,7 @@ spec: openAPIV3Schema: description: |- Solver is the Schema for the solvers API - Solver is the Schema for the solvers API + Solver is the Schema for the solvers API. properties: apiVersion: description: |- @@ -73,10 +73,10 @@ spec: metadata: type: object spec: - description: SolverSpec defines the desired state of Solver + description: SolverSpec defines the desired state of Solver. properties: - enstablishPeering: - description: EnstablishPeering is a flag that indicates if the solver + establishPeering: + description: EstablishPeering is a flag that indicates if the solver should enstablish a peering with the candidate. type: boolean findCandidate: @@ -93,145 +93,31 @@ spec: should reserve and buy the resources on the candidate. type: boolean selector: - description: Selector contains the flavour requirements for the solver. + description: Selector contains the flavor requirements for the solver. properties: - architecture: - type: string - matchSelector: - description: MatchSelector represents the criteria for selecting - Flavours through a strict match. - properties: - cpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeralStorage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - type: object - rangeSelector: - description: RangeSelector represents the criteria for selecting - Flavours through a range. - properties: - MaxCpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxEph: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxGpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxMemory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxPods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - MaxStorage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minCpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minEph: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minGpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minMemory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minPods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - minStorage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + filters: + description: Filters contains the filters that the solver is using + to refining the research. type: object - type: + x-kubernetes-preserve-unknown-fields: true + flavorType: + description: FlavorType is the type of the Flavor that the solver + is looking for. type: string required: - - architecture - - type + - flavorType type: object required: - intentID type: object status: - description: SolverStatus defines the observed state of Solver + 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 fullfill the intent. + The Node Orchestrator will use this allocation to fulfill the intent. properties: name: description: The name of the resource to be referenced. @@ -281,17 +167,17 @@ spec: discoveryPhase: description: |- DiscoveryPhase describes the status of the Discovery where the Discovery Manager - is looking for matching flavours outside the FLUIDOS Node + is looking for matching flavors outside the FLUIDOS Node type: string findCandidate: description: |- FindCandidate describes the status of research of the candidate. - Rear Manager is looking for the best candidate Flavour to solve the Node Orchestrator request. + Rear Manager is looking for the best candidate Flavor to solve the Node Orchestrator request. type: string peering: description: |- Peering describes the status of the peering with the candidate. - Rear Manager is trying to enstablish a peering with the candidate FLUIDOS Node. + Rear Manager is trying to establish a peering with the candidate FLUIDOS Node. type: string reservationPhase: description: |- @@ -300,13 +186,13 @@ spec: type: string reserveAndBuy: description: |- - ReserveAndBuy describes the status of the reservation and purchase of selected Flavour. + ReserveAndBuy describes the status of the reservation and purchase of selected Flavor. Rear Manager is trying to reserve and purchase the resources on the candidate FLUIDOS Node. type: string solverPhase: description: |- SolverPhase describes the status of the Solver generated by the Node Orchestrator. - It is usefull to understand if the solver is still running or if it has finished or failed. + It is useful to understand if the solver is still running or if it has finished or failed. properties: endTime: type: string @@ -315,6 +201,7 @@ spec: message: type: string phase: + description: Phase represents the phase of the solver. type: string startTime: type: string diff --git a/deployments/node/crds/reservation.fluidos.eu_contracts.yaml b/deployments/node/crds/reservation.fluidos.eu_contracts.yaml index 747ad84..c5a05cf 100644 --- a/deployments/node/crds/reservation.fluidos.eu_contracts.yaml +++ b/deployments/node/crds/reservation.fluidos.eu_contracts.yaml @@ -15,8 +15,8 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .spec.flavour.metadata.name - name: Flavour ID + - jsonPath: .spec.flavor.metadata.name + name: Flavor ID type: string - jsonPath: .spec.buyer.nodeID name: Buyer Name @@ -76,6 +76,8 @@ spec: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -88,6 +90,22 @@ spec: to search a contract and the related resources during the peering phase. type: string + configuration: + description: |- + The configuration represents the dimension of the resources sold/bought. + So it will reflect the dimension of the resources allocated on the remote cluster and reflected on the local virtual node. + properties: + data: + description: ConfigurationData is the data of the configuration. + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Identifier is the identifier of the configuration. + type: string + required: + - data + - type + type: object expirationTime: description: This is the expiration time of the contract. It can be empty if the contract is not time limited. @@ -98,8 +116,8 @@ spec: description: This contains additional information about the contract if needed. type: object - flavour: - description: This is the flavour on which the contract is based. It + flavor: + description: This is the flavor on which the contract is based. It is used to lifetime maintain the critical characteristics of the contract. properties: @@ -138,91 +156,61 @@ spec: type: string type: object spec: - description: FlavourSpec defines the desired state of Flavour + description: FlavorSpec defines the desired state of Flavor. properties: - characteristics: - description: |- - Characteristics contains the characteristics of the Flavour. - They are based on the type of the Flavour and can change depending on it. In this case, the type is K8S so the characteristics are CPU, Memory, GPU and EphemeralStorage. + availability: + description: Availability is the availability flag of the + Flavor. + type: boolean + flavorType: + description: FlavorType is the type of the Flavor. properties: - architecture: - description: Architecture is the architecture of the Flavour. + typeData: + description: Raw is the raw value of the Flavor. + type: object + x-kubernetes-preserve-unknown-fields: true + typeIdentifier: + description: Type of the Flavor. type: string - cpu: - anyOf: - - type: integer - - type: string - description: CPU is the number of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - description: EphemeralStorage is the amount of ephemeral - storage of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - description: GPU is the number of GPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - description: Memory is the amount of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - persistent-storage: - anyOf: - - type: integer - - type: string - description: PersistentStorage is the amount of persistent - storage of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - description: Pods is the maximum number of pods of the - Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - architecture - - cpu - - memory - - pods + - typeData + - typeIdentifier type: object - optionalFields: - description: |- - This field is used to specify the optional fields that can be retrieved from the Flavour. - In the future it will be expanded to include more optional fields defined in the REAR Protocol or custom ones. + location: + description: Location is the location of the Flavor. properties: - availability: - description: |- - Availability is the availability flag of the Flavour. - It is a field inherited from the REAR Protocol specifications. - type: boolean - workerID: - description: WorkerID is the ID of the worker that provides - the Flavour. + additionalNotes: + description: AdditionalNotes are additional notes of the + location. + type: string + city: + description: City is the city of the location. + type: string + country: + description: Country is the country of the location. + type: string + latitude: + description: Latitude is the latitude of the location. + type: string + longitude: + description: Longitude is the longitude of the location. type: string type: object + networkPropertyType: + description: NetworkPropertyType is the network property type + of the Flavor. + type: string owner: description: Owner contains the identity info of the owner - of the Flavour. It can be unknown if the Flavour is provided + of the Flavor. It can be unknown if the Flavor is provided by a reseller or a third party. properties: domain: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -230,90 +218,8 @@ spec: - ip - nodeID type: object - policy: - description: Policy contains the policy of the Flavour. The - policy describes the partitioning and aggregation properties - of the Flavour. - properties: - aggregatable: - description: Aggregatable contains the aggregation properties - of the Flavour. - properties: - maxCount: - description: MaxCount is the maximum requirable number - of instances of the Flavour. - type: integer - minCount: - description: MinCount is the minimum requirable number - of instances of the Flavour. - type: integer - required: - - maxCount - - minCount - type: object - partitionable: - description: Partitionable contains the partitioning properties - of the Flavour. - properties: - cpuMin: - anyOf: - - type: integer - - type: string - description: CpuMin is the minimum requirable number - of CPU cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - cpuStep: - anyOf: - - type: integer - - type: string - description: CpuStep is the incremental value of CPU - cores of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryMin: - anyOf: - - type: integer - - type: string - description: MemoryMin is the minimum requirable amount - of RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memoryStep: - anyOf: - - type: integer - - type: string - description: MemoryStep is the incremental value of - RAM of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsMin: - anyOf: - - type: integer - - type: string - description: PodsMin is the minimum requirable number - of pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - podsStep: - anyOf: - - type: integer - - type: string - description: PodsStep is the incremental value of - pods of the Flavour. - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - required: - - cpuMin - - cpuStep - - memoryMin - - memoryStep - - podsMin - - podsStep - type: object - type: object price: - description: Price contains the price model of the Flavour. + description: Price contains the price model of the Flavor. properties: amount: description: Amount is the amount of the price. @@ -331,41 +237,31 @@ spec: type: object providerID: description: |- - ProviderID is the ID of the FLUIDOS Node ID that provides this Flavour. + ProviderID is the ID of the FLUIDOS Node ID that provides this Flavor. It can correspond to ID of the owner FLUIDOS Node or to the ID of a FLUIDOS SuperNode that represents the entry point to a FLUIDOS Domain type: string - quantityAvailable: - description: This field is used to represent the available - quantity of transactions of the Flavour. - type: integer - type: - description: Type is the type of the Flavour. Currently, only - K8S is supported. - type: string required: - - characteristics - - optionalFields + - availability + - flavorType - owner - - policy - price - providerID - - type type: object status: - description: FlavourStatus defines the observed state of Flavour. + description: FlavorStatus defines the observed state of Flavor. properties: creationTime: description: This field represents the creation time of the - Flavour. + Flavor. type: string expirationTime: description: This field represents the expiration time of - the Flavour. It is used to determine when the Flavour is - no longer valid. + the Flavor. It is used to determine when the Flavor is no + longer valid. type: string lastUpdateTime: description: This field represents the last update time of - the Flavour. + the Flavor. type: string required: - creationTime @@ -373,54 +269,27 @@ spec: - lastUpdateTime type: object type: object - partition: - description: |- - The partition represents the dimension of the resources sold/bought. - So it will reflect the dimension of the resources allocated on the remote cluster and reflected on the local virtual node. + networkRequests: + description: NetworkRequests contains the reference to the resource + containing the network requests. + type: string + peeringTargetCredentials: + description: This credentials will be used by the customer to connect + and enstablish a peering with the seller FLUIDOS Node through Liqo. properties: - architecture: + clusterID: + type: string + clusterName: + type: string + endpoint: + type: string + token: type: string - cpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - architecture - - cpu - - memory - - pods + - clusterID + - clusterName + - endpoint + - token type: object seller: description: This is the Node identity of the seller FLUIDOS Node. @@ -429,6 +298,8 @@ spec: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -436,24 +307,6 @@ spec: - ip - nodeID type: object - sellerCredentials: - description: This credentials will be used by the customer to connect - and enstablish a peering with the seller FLUIDOS Node through Liqo. - properties: - clusterID: - type: string - clusterName: - type: string - endpoint: - type: string - token: - type: string - required: - - clusterID - - clusterName - - endpoint - - token - type: object transactionID: description: TransactionID is the ID of the transaction that this contract is part of @@ -461,9 +314,9 @@ spec: required: - buyer - buyerClusterID - - flavour + - flavor + - peeringTargetCredentials - seller - - sellerCredentials - transactionID type: object status: @@ -479,6 +332,7 @@ spec: message: type: string phase: + description: Phase represents the phase of the solver. type: string startTime: type: string diff --git a/deployments/node/crds/reservation.fluidos.eu_reservations.yaml b/deployments/node/crds/reservation.fluidos.eu_reservations.yaml index f88da7e..216bbce 100644 --- a/deployments/node/crds/reservation.fluidos.eu_reservations.yaml +++ b/deployments/node/crds/reservation.fluidos.eu_reservations.yaml @@ -59,7 +59,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Reservation is the Schema for the reservations API + description: Reservation is the Schema for the reservations API. properties: apiVersion: description: |- @@ -79,7 +79,7 @@ spec: metadata: type: object spec: - description: ReservationSpec defines the desired state of Reservation + description: ReservationSpec defines the desired state of Reservation. properties: buyer: description: This is the Node identity of the buyer FLUIDOS Node. @@ -88,6 +88,8 @@ spec: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -95,53 +97,20 @@ spec: - ip - nodeID type: object - partition: - description: Parition is the partition of the flavour that is being - reserved + configuration: + description: Configuration is the configuration of the flavour that + is being reserved properties: - architecture: - type: string - cpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true + data: + description: ConfigurationData is the data of the configuration. + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Identifier is the identifier of the configuration. + type: string required: - - architecture - - cpu - - memory - - pods + - data + - type type: object peeringCandidate: description: PeeringCandidate is the reference to the PeeringCandidate @@ -171,6 +140,8 @@ spec: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -187,7 +158,7 @@ spec: - solverID type: object status: - description: ReservationStatus defines the observed state of Reservation + description: ReservationStatus defines the observed state of Reservation. properties: contract: description: Contract is the reference to the Contract of the Reservation @@ -211,6 +182,7 @@ spec: message: type: string phase: + description: Phase represents the phase of the solver. type: string startTime: type: string diff --git a/deployments/node/crds/reservation.fluidos.eu_transactions.yaml b/deployments/node/crds/reservation.fluidos.eu_transactions.yaml index 7d5758d..1789e3d 100644 --- a/deployments/node/crds/reservation.fluidos.eu_transactions.yaml +++ b/deployments/node/crds/reservation.fluidos.eu_transactions.yaml @@ -15,8 +15,8 @@ spec: scope: Namespaced versions: - additionalPrinterColumns: - - jsonPath: .spec.flavourID - name: Flavour ID + - jsonPath: .spec.flavorID + name: Flavor ID type: string - jsonPath: .spec.buyer.nodeID name: Buyer Name @@ -38,7 +38,7 @@ spec: name: v1alpha1 schema: openAPIV3Schema: - description: Transaction is the Schema for the transactions API + description: Transaction is the Schema for the transactions API. properties: apiVersion: description: |- @@ -58,16 +58,18 @@ spec: metadata: type: object spec: - description: TransactionSpec defines the desired state of Transaction + description: TransactionSpec defines the desired state of Transaction. properties: buyer: description: Buyer is the buyer Identity of the Fluidos Node that - is reserving the Flavour + is reserving the Flavor properties: domain: type: string ip: type: string + liqoID: + type: string nodeID: type: string required: @@ -77,70 +79,37 @@ spec: type: object clusterID: description: ClusterID is the Liqo ClusterID of the Fluidos Node that - is reserving the Flavour - type: string - flavourID: - description: FlavourID is the ID of the flavour that is being reserved + is reserving the Flavor type: string - partition: - description: Partition is the partition of the flavour that is being - reserved + configuration: + description: Configuration is the configuration of the flavor that + is being reserved properties: - architecture: + data: + description: ConfigurationData is the data of the configuration. + type: object + x-kubernetes-preserve-unknown-fields: true + type: + description: Identifier is the identifier of the configuration. type: string - cpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - ephemeral-storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - gpu: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - memory: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - pods: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - storage: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true required: - - architecture - - cpu - - memory - - pods + - data + - type type: object - startTime: - description: StartTime is the time at which the reservation should - start + expirationTime: + description: ExpirationTime is the time when the reservation will + expire + type: string + flavorID: + description: FlavorID is the ID of the flavor that is being reserved type: string required: - buyer - clusterID - - flavourID + - flavorID type: object status: - description: TransactionStatus defines the observed state of Transaction + description: TransactionStatus defines the observed state of Transaction. properties: phase: description: This is the current phase of the reservation @@ -152,6 +121,7 @@ spec: message: type: string phase: + description: Phase represents the phase of the solver. type: string startTime: type: string diff --git a/deployments/node/files/node-local-resource-manager-ClusterRole.yaml b/deployments/node/files/node-local-resource-manager-ClusterRole.yaml index 7179a1d..2bdc3cb 100644 --- a/deployments/node/files/node-local-resource-manager-ClusterRole.yaml +++ b/deployments/node/files/node-local-resource-manager-ClusterRole.yaml @@ -34,7 +34,7 @@ rules: - apiGroups: - nodecore.fluidos.eu resources: - - flavours + - flavors verbs: - create - delete diff --git a/deployments/node/files/node-rear-controller-ClusterRole.yaml b/deployments/node/files/node-rear-controller-ClusterRole.yaml index c774c97..1dad445 100644 --- a/deployments/node/files/node-rear-controller-ClusterRole.yaml +++ b/deployments/node/files/node-rear-controller-ClusterRole.yaml @@ -96,7 +96,7 @@ rules: - apiGroups: - nodecore.fluidos.eu resources: - - flavours + - flavors verbs: - create - delete @@ -108,13 +108,13 @@ rules: - apiGroups: - nodecore.fluidos.eu resources: - - flavours/finalizers + - flavors/finalizers verbs: - update - apiGroups: - nodecore.fluidos.eu resources: - - flavours/status + - flavors/status verbs: - get - patch diff --git a/deployments/node/files/node-rear-manager-ClusterRole.yaml b/deployments/node/files/node-rear-manager-ClusterRole.yaml index ab3e65f..7b8a0c5 100644 --- a/deployments/node/files/node-rear-manager-ClusterRole.yaml +++ b/deployments/node/files/node-rear-manager-ClusterRole.yaml @@ -124,7 +124,7 @@ rules: - apiGroups: - nodecore.fluidos.eu resources: - - flavours + - flavors verbs: - create - delete @@ -136,13 +136,13 @@ rules: - apiGroups: - nodecore.fluidos.eu resources: - - flavours/finalizers + - flavors/finalizers verbs: - update - apiGroups: - nodecore.fluidos.eu resources: - - flavours/status + - flavors/status verbs: - get - patch diff --git a/deployments/node/samples/allocation.yaml b/deployments/node/samples/allocation.yaml index 0e217e1..2976e8f 100644 --- a/deployments/node/samples/allocation.yaml +++ b/deployments/node/samples/allocation.yaml @@ -4,17 +4,9 @@ metadata: name: allocation-sample namespace: fluidos spec: - # From the reservation get the contract and from the contract get the Spec.SellerCredentials.ClusterID - remoteClusterID: 6b89ba16-af18-4600-9b97-0ec0ee8be41a # Get it from the solver intentID: intent-sample - # Set a name for the VirtualNode on the consumer cluster. Pattern suggested: "liqo-clusterName", where clusterName s the one you get from the contract.Spec.SellerCredentials.ClusterName - nodeName: liqo-fluidos-provider-1 - # On the consumer set it as VirtualNode, since the allocation will be bound to a VirtualNode to be created - type: VirtualNode - # On the consumer set it as Local, since the allocation of resources will be consumed locally - destination: Local # Retrieve information from the reservation and the contract bou d to it contract: - name: contract-fluidos.eu-k8s-fluidos-0edea17b-f1d5 + name: contract-fluidos.eu-k8slice-07eadba8bf32d8d6f142b6726592956a-43a4 namespace: fluidos \ No newline at end of file diff --git a/deployments/node/samples/nginx-deployment.yaml b/deployments/node/samples/nginx-deployment.yaml index c64a180..045b2e1 100644 --- a/deployments/node/samples/nginx-deployment.yaml +++ b/deployments/node/samples/nginx-deployment.yaml @@ -19,8 +19,8 @@ spec: - containerPort: 80 resources: limits: - cpu: "500m" + cpu: "200m" memory: "512Mi" requests: - cpu: "100m" + cpu: "50m" memory: "128Mi" diff --git a/deployments/node/samples/reservation.yaml b/deployments/node/samples/reservation.yaml index abff590..249d6ca 100644 --- a/deployments/node/samples/reservation.yaml +++ b/deployments/node/samples/reservation.yaml @@ -4,28 +4,32 @@ metadata: name: reservation-sample namespace: fluidos spec: - solverID: solver-sample + solverID: solver-sample-1 + # Set it as you want, following needs and requests in the solver. + # Optional + configuration: + # Be sure to use the same type of the peeringCandidate + type: K8Slice + # Be sure to use values that are in the range of the peeringCandidate + data: + cpu: 1000m + memory: 1Gi + pods: "110" + # Retrieve from PeeringCandidate chosen to reserve + peeringCandidate: + name: peeringcandidate-fluidos.eu-k8slice-24f77877ba11ce29a25f95ec33a244c5 + namespace: fluidos + # Set it to reserve + reserve: true + # Set it to purchase after reservation is completed and you have a transaction + purchase: true + # Retrieve from PeeringCandidate Flavor Owner field + seller: + domain: fluidos.eu + ip: 172.18.0.7:30001 + nodeID: ac7jspin96 # Retrieve from configmap buyer: domain: fluidos.eu - nodeID: mhmigs553h - ip: 172.18.0.6:30000 - # Retrieve from PeeringCandidate Flavor Owner field - seller: - domain: fluidos.eu - nodeID: 7m0htg6wpb - ip: 172.18.0.5:30001 - # Set it as you want, following needs and requests in the solver. Actually if not used can lead to a bug - partition: - architecture: amd64 - cpu: "1000m" - memory: "1Gi" - pods: "50" - # Set it to reserve - reserve: true - # Set it to purchase after reservation is completed and you have a transaction - purchase: false - # Retrieve from PeeringCandidate chosen to reserve - peeringCandidate: - name: peeringcandidate-fluidos.eu-k8s-fluidos-0edea17b - namespace: fluidos \ No newline at end of file + ip: 172.18.0.5:30000 + nodeID: 4sqa7o2wsh \ No newline at end of file diff --git a/deployments/node/samples/solver-custom.yaml b/deployments/node/samples/solver-custom.yaml index ee5804a..f326f7e 100644 --- a/deployments/node/samples/solver-custom.yaml +++ b/deployments/node/samples/solver-custom.yaml @@ -4,21 +4,43 @@ metadata: name: solver-sample namespace: fluidos spec: - # This is the Selector used to find a Flavour (FLUIDOS node) that matches the requirements + # This is the Selector used to find a Flavor (FLUIDOS node) that matches the requirements selector: - # ONLY k8s-fluidos is supported at the moment - type: k8s-fluidos - # REMEMBER: the architecture is the one of the node, not the one of the container. Change it accordingly - architecture: amd64 - # ONLY rangeSelector is supported at the moment - rangeSelector: - minCpu: "1000m" - minMemory: "1Gi" - minPods: "50" + # The flavorType is the type of the Flavor (FLUIDOS node) that the solver should find + flavorType: K8Slice + # The filters are used to filter the Flavors (FLUIDOS nodes) that the solver should consider + filters: + # The architectureFilter is used to filter the Flavors (FLUIDOS nodes) based on the Architecture + architectureFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have the x86_64 architecture + # In demo environments, be careful with the architecture, it may be different + name: Match + data: + value: "amd64" + # The cpuFilter is used to filter the Flavors (FLUIDOS nodes) based on the CPU + cpuFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have at least 100m of CPU + name: Range + data: + min: "1000m" + # The memoryFilter is used to filter the Flavors (FLUIDOS nodes) based on the Memory + memoryFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have at least 1Gi of Memory, but no more than 100Gi + name: Range + data: + min: "1Gi" + max: "100Gi" + # The podsFilter is used to filter the Flavors (FLUIDOS nodes) based on the Pods + podsFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have exactly 110 Pods + name: Match + data: + value: 110 + # The intentID is the ID of the intent that the solver should satisfy intentID: "intent-sample" # 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) reserveAndBuy: false # This flag is used to indicate that the solver should establish peering with the candidate (FLUIDOS node) - enstablishPeering: false + establishPeering: false diff --git a/deployments/node/samples/solver.yaml b/deployments/node/samples/solver.yaml index b800133..15a4a91 100644 --- a/deployments/node/samples/solver.yaml +++ b/deployments/node/samples/solver.yaml @@ -4,21 +4,43 @@ metadata: name: solver-sample namespace: fluidos spec: - # This is the Selector used to find a Flavour (FLUIDOS node) that matches the requirements + # This is the Selector used to find a Flavor (FLUIDOS node) that matches the requirements selector: - # ONLY k8s-fluidos is supported at the moment - type: k8s-fluidos - # REMEMBER: the architecture is the one of the node, not the one of the container. Change it accordingly - architecture: amd64 - # ONLY rangeSelector is supported at the moment - rangeSelector: - minCpu: "1000m" - minMemory: "1Gi" - minPods: "50" + # The flavorType is the type of the Flavor (FLUIDOS node) that the solver should find + flavorType: K8Slice + # The filters are used to filter the Flavors (FLUIDOS nodes) that the solver should consider + filters: + # The architectureFilter is used to filter the Flavors (FLUIDOS nodes) based on the Architecture + architectureFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have the x86_64 architecture + # In demo environments, be careful with the architecture, it may be different + name: Match + data: + value: "amd64" + # The cpuFilter is used to filter the Flavors (FLUIDOS nodes) based on the CPU + cpuFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have at least 100m of CPU + name: Range + data: + min: "1000m" + # The memoryFilter is used to filter the Flavors (FLUIDOS nodes) based on the Memory + memoryFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have at least 1Gi of Memory, but no more than 100Gi + name: Range + data: + min: "1Gi" + max: "100Gi" + # The podsFilter is used to filter the Flavors (FLUIDOS nodes) based on the Pods + podsFilter: + # This filter specifies that the Flavors (FLUIDOS nodes) should have exactly 110 Pods + name: Match + data: + value: 110 + # The intentID is the ID of the intent that the solver should satisfy intentID: "intent-sample" # 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) reserveAndBuy: true # This flag is used to indicate that the solver should establish peering with the candidate (FLUIDOS node) - enstablishPeering: true + establishPeering: true diff --git a/deployments/node/templates/fluidos-cert-issuer.yaml b/deployments/node/templates/fluidos-cert-issuer.yaml new file mode 100644 index 0000000..dbae6dd --- /dev/null +++ b/deployments/node/templates/fluidos-cert-issuer.yaml @@ -0,0 +1,14 @@ +{{- $resManagerConfig := (merge (dict "name" "local-resource-manager" "module" "local-resource-manager") .) -}} + +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + {{- include "fluidos.labels" $resManagerConfig | nindent 4 }} + name: {{ .Values.webhook.clusterIssuer | default "self-signed" }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-2" +spec: + selfSigned: {} \ No newline at end of file diff --git a/deployments/node/templates/fluidos-local-resource-manager-deployment.yaml b/deployments/node/templates/fluidos-local-resource-manager-deployment.yaml index 0ec7af8..23ac472 100644 --- a/deployments/node/templates/fluidos-local-resource-manager-deployment.yaml +++ b/deployments/node/templates/fluidos-local-resource-manager-deployment.yaml @@ -48,19 +48,31 @@ spec: args: - --node-resource-label={{ .Values.localResourceManager.config.nodeResourceLabel }} - --resources-types={{ .Values.localResourceManager.config.resourceType }} - - --cpu-min={{ .Values.localResourceManager.config.flavour.cpuMin }} - - --memory-min={{ .Values.localResourceManager.config.flavour.memoryMin }} - - --cpu-step={{ .Values.localResourceManager.config.flavour.cpuStep }} - - --memory-step={{ .Values.localResourceManager.config.flavour.memoryStep }} + - --cpu-min={{ .Values.localResourceManager.config.flavor.cpuMin }} + - --memory-min={{ .Values.localResourceManager.config.flavor.memoryMin }} + - --cpu-step={{ .Values.localResourceManager.config.flavor.cpuStep }} + - --memory-step={{ .Values.localResourceManager.config.flavor.memoryStep }} + - --enable-webhooks={{ .Values.webhook.enabled | default "true" }} + - --enable-auto-discovery={{ .Values.localResourceManager.config.enableAutoDiscovery | default "true" }} resources: {{- toYaml .Values.localResourceManager.pod.resources | nindent 10 }} ports: - name: healthz containerPort: 8081 protocol: TCP + - name: webhook + containerPort: 9443 + protocol: TCP readinessProbe: httpGet: path: /readyz port: healthz + volumeMounts: + - name: webhook-certs + mountPath: {{ .Values.webhook.deployment.certsMount | default "/tmp/k8s-webhook-server/serving-certs/" }} + volumes: + - name: webhook-certs + secret: + secretName: {{ include "fluidos.prefixedName" $resManagerConfig }} {{- if ((.Values.common).nodeSelector) }} nodeSelector: {{- toYaml .Values.common.nodeSelector | nindent 8 }} diff --git a/deployments/node/templates/fluidos-pre-install-hook-authz.yaml b/deployments/node/templates/fluidos-pre-install-hook-authz.yaml index 10825e4..83dd1a8 100644 --- a/deployments/node/templates/fluidos-pre-install-hook-authz.yaml +++ b/deployments/node/templates/fluidos-pre-install-hook-authz.yaml @@ -5,7 +5,7 @@ metadata: namespace: fluidos annotations: "helm.sh/hook": pre-install - "helm.sh/hook-weight": "-4" + "helm.sh/hook-weight": "-5" --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -13,7 +13,7 @@ metadata: name: admin-cluster-role annotations: "helm.sh/hook": pre-install - "helm.sh/hook-weight": "-4" + "helm.sh/hook-weight": "-5" rules: - apiGroups: ["*"] resources: ["*"] @@ -25,7 +25,7 @@ metadata: name: admin-cluster-rolebinding annotations: "helm.sh/hook": pre-install - "helm.sh/hook-weight": "-4" + "helm.sh/hook-weight": "-5" roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole diff --git a/deployments/node/templates/fluidos-pre-install-hook-cm.yaml b/deployments/node/templates/fluidos-pre-install-hook-cm.yaml index 6a91897..64dcd9e 100644 --- a/deployments/node/templates/fluidos-pre-install-hook-cm.yaml +++ b/deployments/node/templates/fluidos-pre-install-hook-cm.yaml @@ -5,7 +5,7 @@ metadata: namespace: {{ .Release.Namespace }} annotations: "helm.sh/hook": pre-install - "helm.sh/hook-weight": "-3" + "helm.sh/hook-weight": "-4" data: pre-install.sh: | #!/bin/bash diff --git a/deployments/node/templates/fluidos-pre-install-hook-job.yaml b/deployments/node/templates/fluidos-pre-install-hook-job.yaml index 087c1a0..58f05f2 100644 --- a/deployments/node/templates/fluidos-pre-install-hook-job.yaml +++ b/deployments/node/templates/fluidos-pre-install-hook-job.yaml @@ -5,7 +5,7 @@ metadata: namespace: {{ .Release.Namespace }} annotations: "helm.sh/hook": pre-install - "helm.sh/hook-weight": "-2" + "helm.sh/hook-weight": "-3" "helm.sh/hook-delete-policy": hook-succeeded spec: template: diff --git a/deployments/node/templates/fluidos-rear-controller-deployment.yaml b/deployments/node/templates/fluidos-rear-controller-deployment.yaml index ce5e931..c8c497a 100644 --- a/deployments/node/templates/fluidos-rear-controller-deployment.yaml +++ b/deployments/node/templates/fluidos-rear-controller-deployment.yaml @@ -59,10 +59,20 @@ spec: - name: {{ .Values.rearController.service.grpc.name }} containerPort: {{ .Values.rearController.service.grpc.port }} protocol: TCP + - name: webhook + containerPort: 9443 + protocol: TCP readinessProbe: httpGet: path: /readyz port: healthz + volumeMounts: + - name: webhook-certs + mountPath: {{ .Values.webhook.deployment.certsMount | default "/tmp/k8s-webhook-server/serving-certs/" }} + volumes: + - name: webhook-certs + secret: + secretName: {{ include "fluidos.prefixedName" $rearControllerConfig }} {{- if (.Values.common).nodeSelector }} nodeSelector: {{- toYaml .Values.common.nodeSelector | nindent 8 }} diff --git a/deployments/node/templates/fluidos-rear-manager-deployment.yaml b/deployments/node/templates/fluidos-rear-manager-deployment.yaml index 6e33edf..2be0429 100644 --- a/deployments/node/templates/fluidos-rear-manager-deployment.yaml +++ b/deployments/node/templates/fluidos-rear-manager-deployment.yaml @@ -46,15 +46,26 @@ spec: name: {{ $rearManagerConfig.name }} command: ["/usr/bin/rear-manager"] args: + - --enable-webhooks={{ .Values.webhook.enabled | default "true" }} resources: {{- toYaml .Values.rearManager.pod.resources | nindent 10 }} ports: - name: healthz containerPort: 8081 protocol: TCP + - name: webhook + containerPort: 9443 + protocol: TCP readinessProbe: httpGet: path: /readyz port: healthz + volumeMounts: + - name: webhook-certs + mountPath: {{ .Values.webhook.deployment.certsMount | default "/tmp/k8s-webhook-server/serving-certs/" }} + volumes: + - name: webhook-certs + secret: + secretName: {{ include "fluidos.prefixedName" $rearManagerConfig }} {{- if ((.Values.common).nodeSelector) }} nodeSelector: {{- toYaml .Values.common.nodeSelector | nindent 8 }} diff --git a/deployments/node/templates/webhook/fluidos-local-resource-manager-certificate.yaml b/deployments/node/templates/webhook/fluidos-local-resource-manager-certificate.yaml new file mode 100644 index 0000000..c4a25dd --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-local-resource-manager-certificate.yaml @@ -0,0 +1,18 @@ +{{- $resManagerConfig := (merge (dict "name" "local-resource-manager" "module" "local-resource-manager") .) -}} + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "fluidos.prefixedName" $resManagerConfig }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-1" +spec: + dnsNames: + - {{ include "fluidos.prefixedName" $resManagerConfig }}.{{ .Release.Namespace }}.svc + - {{ include "fluidos.prefixedName" $resManagerConfig }}.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + kind: Issuer + name: {{ .Values.webhook.Issuer | default "self-signed" }} + secretName: {{ include "fluidos.prefixedName" $resManagerConfig }} \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-local-resource-manager-mutating-webhook.yaml b/deployments/node/templates/webhook/fluidos-local-resource-manager-mutating-webhook.yaml new file mode 100644 index 0000000..da2d44d --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-local-resource-manager-mutating-webhook.yaml @@ -0,0 +1,29 @@ +{{- $resManagerConfig := (merge (dict "name" "local-resource-manager" "module" "local-resource-manager") .) -}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "fluidos.prefixedName" $resManagerConfig }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "fluidos.prefixedName" $resManagerConfig }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $resManagerConfig }} + namespace: {{ .Release.Namespace }} + path: /mutate-nodecore-fluidos-eu-v1alpha1-flavor + failurePolicy: Fail + name: mutate.flavor.nodecore.fluidos.eu + rules: + - apiGroups: + - nodecore.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - flavors + sideEffects: None \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-local-resource-manager-validating-webhook.yaml b/deployments/node/templates/webhook/fluidos-local-resource-manager-validating-webhook.yaml new file mode 100644 index 0000000..d965608 --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-local-resource-manager-validating-webhook.yaml @@ -0,0 +1,51 @@ +{{- $resManagerConfig := (merge (dict "name" "local-resource-manager" "module" "local-resource-manager") .) -}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ include "fluidos.prefixedName" $resManagerConfig }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "fluidos.prefixedName" $resManagerConfig }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $resManagerConfig }} + namespace: {{ .Release.Namespace }} + path: /validate-nodecore-fluidos-eu-v1alpha1-flavor + failurePolicy: Fail + name: validate.flavor.nodecore.fluidos.eu + rules: + - apiGroups: + - nodecore.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - flavors + sideEffects: None +# - admissionReviewVersions: +# - v1 +# - v1beta1 +# clientConfig: +# service: +# name: {{ include "fluidos.prefixedName" $resManagerConfig }} +# namespace: {{ .Release.Namespace }} +# path: /validate/peeringcandidate +# failurePolicy: Ignore +# name: pc.validate.fluidos.eu +# rules: +# - apiGroups: +# - advertisement.node.fluidos.io +# apiVersions: +# - v1alpha1 +# operations: +# - CREATE +# - UPDATE +# - DELETE +# resources: +# - peeringcandidates +# sideEffects: None \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-local-resource-manager-webhook-service.yaml b/deployments/node/templates/webhook/fluidos-local-resource-manager-webhook-service.yaml new file mode 100644 index 0000000..83d48af --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-local-resource-manager-webhook-service.yaml @@ -0,0 +1,15 @@ +{{- $resManagerConfig := (merge (dict "name" "local-resource-manager" "module" "local-resource-manager") .) -}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "fluidos.prefixedName" $resManagerConfig }} + namespace: {{ .Release.Namespace }} +spec: + ports: + - port: 443 + protocol: TCP + name: https + targetPort: 9443 #9443 + selector: + {{- include "fluidos.labels" $resManagerConfig | nindent 6 }} diff --git a/deployments/node/templates/webhook/fluidos-rear-controller-certificate.yaml b/deployments/node/templates/webhook/fluidos-rear-controller-certificate.yaml new file mode 100644 index 0000000..da02e6a --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-controller-certificate.yaml @@ -0,0 +1,17 @@ +{{- $rearControllerConfig := (merge (dict "name" "rear-controller" "module" "rear-controller") .) -}} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-1" +spec: + dnsNames: + - {{ include "fluidos.prefixedName" $rearControllerConfig }}.{{ .Release.Namespace }}.svc + - {{ include "fluidos.prefixedName" $rearControllerConfig }}.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + kind: Issuer + name: {{ .Values.webhook.Issuer | default "self-signed" }} + secretName: {{ include "fluidos.prefixedName" $rearControllerConfig }} \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-rear-controller-mutating-webhook.yaml b/deployments/node/templates/webhook/fluidos-rear-controller-mutating-webhook.yaml new file mode 100644 index 0000000..ddb9adc --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-controller-mutating-webhook.yaml @@ -0,0 +1,68 @@ +{{- $rearControllerConfig := (merge (dict "name" "rear-controller" "module" "rear-controller") .) -}} +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "fluidos.prefixedName" $rearControllerConfig }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + path: /mutate-reservation-fluidos-eu-v1alpha1-reservation + failurePolicy: Fail + name: mutate.reservation.reservation.fluidos.eu + rules: + - apiGroups: + - reservation.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - reservations + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + path: /mutate-reservation-fluidos-eu-v1alpha1-transaction + failurePolicy: Fail + name: mutate.transaction.reservation.fluidos.eu + rules: + - apiGroups: + - reservation.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - transactions + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + path: /mutate-reservation-fluidos-eu-v1alpha1-contract + failurePolicy: Fail + name: mutate.contract.reservation.fluidos.eu + rules: + - apiGroups: + - contract.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - contracts + sideEffects: None \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-rear-controller-validating-webhook.yaml b/deployments/node/templates/webhook/fluidos-rear-controller-validating-webhook.yaml new file mode 100644 index 0000000..70cad3f --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-controller-validating-webhook.yaml @@ -0,0 +1,69 @@ +{{- $rearControllerConfig := (merge (dict "name" "rear-controller" "module" "rear-controller") .) -}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "fluidos.prefixedName" $rearControllerConfig }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + path: /validate-reservation-fluidos-eu-v1alpha1-reservation + failurePolicy: Fail + name: validate.reservation.reservation.fluidos.eu + rules: + - apiGroups: + - reservation.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - reservations + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + path: /validate-reservation-fluidos-eu-v1alpha1-transaction + failurePolicy: Fail + name: validate.transaction.reservation.fluidos.eu + rules: + - apiGroups: + - reservation.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - transactions + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} + path: /validate-reservation-fluidos-eu-v1alpha1-contract + failurePolicy: Fail + name: validate.contract.reservation.fluidos.eu + rules: + - apiGroups: + - contract.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - contracts + sideEffects: None \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-rear-controller-webhook-service.yaml b/deployments/node/templates/webhook/fluidos-rear-controller-webhook-service.yaml new file mode 100644 index 0000000..3ed1664 --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-controller-webhook-service.yaml @@ -0,0 +1,15 @@ +{{- $rearControllerConfig := (merge (dict "name" "rear-controller" "module" "rear-controller") .) -}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "fluidos.prefixedName" $rearControllerConfig }} + namespace: {{ .Release.Namespace }} +spec: + ports: + - port: 443 + protocol: TCP + name: https + targetPort: 9443 #9443 + selector: + {{- include "fluidos.labels" $rearControllerConfig | nindent 6 }} diff --git a/deployments/node/templates/fluidos-pre-install-issuer-and-cert.yaml b/deployments/node/templates/webhook/fluidos-rear-manager-certificate.yaml similarity index 59% rename from deployments/node/templates/fluidos-pre-install-issuer-and-cert.yaml rename to deployments/node/templates/webhook/fluidos-rear-manager-certificate.yaml index ae41cde..54bf1a3 100644 --- a/deployments/node/templates/fluidos-pre-install-issuer-and-cert.yaml +++ b/deployments/node/templates/webhook/fluidos-rear-manager-certificate.yaml @@ -1,33 +1,18 @@ -{{- $resManagerConfig := (merge (dict "name" "local-resource-manager" "module" "local-resource-manager") .) -}} - -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - annotations: - "helm.sh/hook": pre-install - "helm.sh/hook-weight": "-1" - labels: - {{- include "fluidos.labels" $resManagerConfig | nindent 4 }} - name: fluidos-self-signed - namespace: {{ .Release.Namespace }} -spec: - selfSigned: {} ---- {{- $rearManagerConfig := (merge (dict "name" "rear-manager" "module" "rear-manager") .) -}} apiVersion: cert-manager.io/v1 kind: Certificate metadata: + name: {{ include "fluidos.prefixedName" $rearManagerConfig }} + namespace: {{ .Release.Namespace }} annotations: "helm.sh/hook": pre-install "helm.sh/hook-weight": "-1" - name: {{ include "fluidos.prefixedName" $rearManagerConfig }} - namespace: {{ .Release.Namespace }} spec: dnsNames: - {{ include "fluidos.prefixedName" $rearManagerConfig }}.{{ .Release.Namespace }}.svc - {{ include "fluidos.prefixedName" $rearManagerConfig }}.{{ .Release.Namespace }}.svc.cluster.local issuerRef: kind: Issuer - name: fluidos-self-signed + name: {{ .Values.webhook.Issuer | default "self-signed" }} secretName: {{ include "fluidos.prefixedName" $rearManagerConfig }} \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-rear-manager-mutating-webhook.yaml b/deployments/node/templates/webhook/fluidos-rear-manager-mutating-webhook.yaml new file mode 100644 index 0000000..80acda7 --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-manager-mutating-webhook.yaml @@ -0,0 +1,29 @@ +{{- $rearManagerConfig := (merge (dict "name" "rear-manager" "module" "rear-manager") .) -}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: {{ include "fluidos.prefixedName" $rearManagerConfig }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "fluidos.prefixedName" $rearManagerConfig }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearManagerConfig }} + namespace: {{ .Release.Namespace }} + path: /mutate-nodecore-fluidos-eu-v1alpha1-solver + failurePolicy: Fail + name: mutate.solver.nodecore.fluidos.eu + rules: + - apiGroups: + - nodecore.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - solvers + sideEffects: None \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-rear-manager-validating-webhook.yaml b/deployments/node/templates/webhook/fluidos-rear-manager-validating-webhook.yaml new file mode 100644 index 0000000..dcda3bc --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-manager-validating-webhook.yaml @@ -0,0 +1,51 @@ +{{- $rearManagerConfig := (merge (dict "name" "rear-manager" "module" "rear-manager") .) -}} + +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: {{ include "fluidos.prefixedName" $rearManagerConfig }} + annotations: + cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "fluidos.prefixedName" $rearManagerConfig }} +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: {{ include "fluidos.prefixedName" $rearManagerConfig }} + namespace: {{ .Release.Namespace }} + path: /validate-nodecore-fluidos-eu-v1alpha1-solver + failurePolicy: Fail + name: validate.solver.nodecore.fluidos.eu + rules: + - apiGroups: + - nodecore.fluidos.eu + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - solvers + sideEffects: None +# - admissionReviewVersions: +# - v1 +# - v1beta1 +# clientConfig: +# service: +# name: {{ include "fluidos.prefixedName" $rearManagerConfig }} +# namespace: {{ .Release.Namespace }} +# path: /validate/peeringcandidate +# failurePolicy: Ignore +# name: pc.validate.fluidos.eu +# rules: +# - apiGroups: +# - advertisement.node.fluidos.io +# apiVersions: +# - v1alpha1 +# operations: +# - CREATE +# - UPDATE +# - DELETE +# resources: +# - peeringcandidates +# sideEffects: None \ No newline at end of file diff --git a/deployments/node/templates/webhook/fluidos-rear-manager-webhook-service.yaml b/deployments/node/templates/webhook/fluidos-rear-manager-webhook-service.yaml new file mode 100644 index 0000000..2e0b595 --- /dev/null +++ b/deployments/node/templates/webhook/fluidos-rear-manager-webhook-service.yaml @@ -0,0 +1,15 @@ +{{- $rearManagerConfig := (merge (dict "name" "rear-manager" "module" "rear-manager") .) -}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "fluidos.prefixedName" $rearManagerConfig }} + namespace: {{ .Release.Namespace }} +spec: + ports: + - port: 443 + protocol: TCP + name: https + targetPort: 9443 #9443 + selector: + {{- include "fluidos.labels" $rearManagerConfig | nindent 6 }} diff --git a/deployments/node/values.yaml b/deployments/node/values.yaml index 3e94b57..a82f109 100644 --- a/deployments/node/values.yaml +++ b/deployments/node/values.yaml @@ -35,16 +35,18 @@ localResourceManager: config: # -- Label used to identify the nodes from which resources are collected. nodeResourceLabel: "node-role.fluidos.eu/resources" - # -- This flag defines the resource type of the generated flavours. + # -- This flag defines the resource type of the generated flavors. resourceType: "k8s-fluidos" - flavour: - # -- The minimum number of CPUs that can be requested to purchase a flavour. + # -- Enable the auto-discovery of the resources. + enableAutoDiscovery: true + flavor: + # -- The minimum number of CPUs that can be requested to purchase a flavor. cpuMin: "0" - # -- The minimum amount of memory that can be requested to purchase a flavour. + # -- The minimum amount of memory that can be requested to purchase a flavor. memoryMin: "0" - # -- The CPU step that must be respected when requesting a flavour through a Flavour Selector. + # -- The CPU step that must be respected when requesting a flavor through a Flavor Selector. cpuStep: "1000m" - # -- The memory step that must be respected when requesting a flavour through a Flavour Selector. + # -- The memory step that must be respected when requesting a flavor through a Flavor Selector. memoryStep: "100Mi" rearManager: @@ -148,4 +150,15 @@ networkManager: # -- The NodeID is a UUID that identifies the FLUIDOS Node. It is used to generate the FQDN of the owned FLUIDOS Nodes and it is unique in the FLUIDOS closed domain nodeID: -# provider: "your-provider" +provider: "your-provider" + +webhook: + # -- Enable the webhook server for the local-resource-manager. + enabled: true + # -- Configuration for the webhook server. + issuer: "self-signed" + # -- Configuration for the webhook server. + deployment: + # -- The mount path for the webhook certificates. + certsMount: "/tmp/k8s-webhook-server/serving-certs/" + diff --git a/docs/usage/usage.md b/docs/usage/usage.md index 843dd9c..6ac8bc0 100644 --- a/docs/usage/usage.md +++ b/docs/usage/usage.md @@ -4,13 +4,13 @@ In this section we will instruct you on how you can interact with the FLUIDOS No ## Solver Creation -The first step is to create a `Solver` CR. This CR has different fields in its specification that if set correctly can lead to a custom behaviour of the FLUIDOS Node. +The first step is to create a `Solver` CR. This CR has different fields in its specification that if set correctly can lead to a custom behavior of the FLUIDOS Node. Therefore, set the specification of the `Solver` CR as follows: ```yaml reserveAndBuy: false - enstablishPeering: false + establishPeering: false ``` You can find an example here: [solver.yaml](../../deployments/node/samples/solver-custom.yaml) @@ -19,6 +19,91 @@ Doing so, the FLUIDOS Node, after the creation of the `Solver` CR, will only dis Retrieving the `Discovery` CR which contains the SolverID field as the name of the Solver CR, you can see the Peering Candidates discovered by the FLUIDOS Node. +### Selector + +In the `Solver` CR you can also set a `Selector` field. This field specifies the requirements that the Peering Candidates must satisfy. + +In particular, you have two fields: + +- `flavorType`: that specifies the flavor type of the Peering Candidate. The possible values are: + - `K8Slice`: for a Peering Candidate that has a Kubernetes Slice flavor. + - `VM`: for a Peering Candidate that has a VM flavor. + - `Service`: for a Peering Candidate that has a Service flavor. + - `Sensor`: for a Peering Candidate that has a Sensor flavor. +- `filters`: that specifies the filters that the Peering Candidates must satisfy. These filters depends on the flavor type of the Peering Candidate that you want to discover. + +### Filter types + +Each filter inside a `Selector` field can be of different types. The possible types are: + +#### ResourceQuantityFilter + +The `ResourceQuantityFilter` is a specific type of filter used by some specific filters in specific selectors. It can be of two types and it is specified by the `name` field. The possible values are: + +- `Match`: that specifies the exact value that the Peering Candidate must have. +- `Range`: that specifies a range of values that the Peering Candidate must have. + +The other field is `data` that specifies the effective value of the filter and its structure depends on the type of the filter. + +- `Match` filter: Its structure is as follows: + +```yaml + name: Match + data: + value: "1000m" +``` + +- `Range` filter: Its structure is as follows: + +```yaml + name: Range + data: + min: "1Gi" + max: "10Gi" +``` + +The **Range** filter can have only the `min` field, the `max` field or both. + +### Selector types + +The `Selector` field can be of different types. The possible types are: + +#### K8Slice Filters + +K8Slice filters are the filters that you can set for a Peering Candidate that has a Kubernetes Slice flavor. They are specifically fields: + +- `CpuFilter`: that specifies the CPU that the Peering Candidate must have. It is a `ResourceQuantityFilter` and can be both a `Match` or a `Range` filter type. (Optional) +- `MemoryFilter`: that specifies the Memory that the Peering Candidate must have. It is a `ResourceQuantityFilter` and can be both a `Match` or a `Range` filter type. (Optional) +- `PodsFilter`: that specifies the Architecture that the Peering Candidate must have. It is a `ResourceQuantityFilter` and can be both a `Match` or a `Range` filter type. (Optional) +- `StorageFilter`: that specifies the Storage that the Peering Candidate must have. It is a `ResourceQuantityFilter` and can be both a `Match` or a `Range` filter type. (Optional) + +A structure example of a `K8Slice` filter is as follows: + +```yaml + cpuFilter: + name: Match + value: "1000m" + memoryFilter: + name: Range + min: "1Gi" + max: "10Gi" + podsFilter: + name: Range + max: 10 +``` + +#### VM Filters + +*Not yet implemented.* + +#### Service Filters + +*Not yet implemented.* + +#### Sensor Filters + +*Not yet implemented.* + ## Reservation For each Peering Candidate that you want to reserve (temporary action), you need to create a `Reservation` CR. @@ -34,6 +119,43 @@ If you want to postpone the purchase phase, you need to set the `purchase` field Doing so, the FLUIDOS Node will not proceed with the purchase of the Peering Candidate, but you will have a **temporary** reserved Peering Candidate both in the consumer and provider side. +## Configuration + +In the `Reservation` CR you can define an optional field called `configuration`. This field is used to specify the configuration of the Peering Candidate that you want to reserve. + +The structure of the `configuration` field depends on the flavor type of the Peering Candidate that you want to reserve. In fact, there are two fields that you have to set: + +- `type`: that specifies the flavor type of the Configuration. The possible values are: + - `K8Slice`: for a Peering Candidate that has a Kubernetes Slice flavor. + - `VM`: for a Peering Candidate that has a VM flavor. + - `Service`: for a Peering Candidate that has a Service flavor. + - `Sensor`: for a Peering Candidate that has a Sensor flavor. +- `data`: that specifies the specific configuration parameters of the Peering Candidate, related to the type of the Configuration. + +### Configuration types + +The `configuration` field can be of different types. The possible types are: + +#### K8Slice Configuration + +K8Slice configuration is the configuration that you can set for a Peering Candidate that has a Kubernetes Slice flavor. The configuration of the K8Slice leads to the creation of a partition of the Peering Candidate. The fields that you have set are: + +- `cpu`: that specifies the CPU of the Partition +- `memory`: that specifies the Memory of the Partition +- `pods`: that specifies the Pods of the Partition +- `gpu`: that specifies the GPU of the Partition (Optional, not yet implemented) +- `storage`: hat specifies the Storage of the Partition (Optional) + +A structure example of a `K8Slice` configuration is as follows: + +```yaml + type: K8Slice + data: + cpu: "1000m" + memory: "1Gi" + pods: "10" +``` + ## Purchase When you want to permanently buy a Peering Candidate, you need to edit its related `Reservation` CR and set the `purchase` field to `true`. diff --git a/go.mod b/go.mod index dea1d12..ac75f32 100644 --- a/go.mod +++ b/go.mod @@ -50,18 +50,20 @@ require ( github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/virtual-kubelet/virtual-kubelet v1.10.0 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.13.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20221114191408-850992195362 // indirect - golang.org/x/net v0.15.0 // indirect + golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.19.0 // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 32844e6..c3362cb 100644 --- a/go.sum +++ b/go.sum @@ -119,8 +119,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/virtual-kubelet/virtual-kubelet v1.10.0 h1:eV/mFFqThOJLz7Gjn1Ev8LchanGKGA2qZlsW6wipb4g= github.com/virtual-kubelet/virtual-kubelet v1.10.0/go.mod h1:7Pvdei1p82C9uWS1VzLrnXbHTwQcGBoqShahChpacgI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -142,8 +142,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20221114191408-850992195362 h1:NoHlPRbyl1VFI6FjwHtPQCN7wAMXI6cKcqrmXhOOfBQ= golang.org/x/exp v0.0.0-20221114191408-850992195362/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -151,8 +151,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -163,8 +163,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -184,20 +184,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -207,8 +207,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 015241e..971da72 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/local-resource-manager/controller_manager.go b/pkg/local-resource-manager/controller_manager.go deleted file mode 100644 index 721cd3f..0000000 --- a/pkg/local-resource-manager/controller_manager.go +++ /dev/null @@ -1,195 +0,0 @@ -// 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 localresourcemanager - -import ( - "context" - "fmt" - "log" - "reflect" - - "k8s.io/klog/v2" - "sigs.k8s.io/controller-runtime/pkg/client" - - nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" - "github.com/fluidos-project/node/pkg/utils/getters" - "github.com/fluidos-project/node/pkg/utils/resourceforge" -) - -// clusterRole -//+kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch -//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch -//+kubebuilder:rbac:groups=metrics.k8s.io,resources=pods,verbs=get;list;watch -//+kubebuilder:rbac:groups=metrics.k8s.io,resources=nodes,verbs=get;list;watch - -// TODO: If the local resource manager restarts, -// ensure to check and subtract the already allocated resources from the node -// resources calculation. - -// Start starts the controller. - -func canMergeFlavours(flavour1, flavour2 *nodecorev1alpha1.Flavour) bool { - if flavour1.Spec.ProviderID != flavour2.Spec.ProviderID { - return false - } - - if flavour1.TypeMeta != flavour2.TypeMeta { - return false - } - - if flavour1.Namespace != flavour2.Namespace { - return false - } - - if flavour1.Spec.OptionalFields.Availability != flavour2.Spec.OptionalFields.Availability { - return false - } - - if flavour1.Spec.Owner != flavour2.Spec.Owner { - return false - } - - if flavour1.Spec.Price != flavour2.Spec.Price { - return false - } - - if flavour1.Spec.Type != flavour2.Spec.Type { - return false - } - - if !reflect.DeepEqual(flavour1.Spec.Policy, flavour2.Spec.Policy) { - return false - } - - if flavour1.Spec.Characteristics.Architecture != flavour2.Spec.Characteristics.Architecture { - return false - } - - if flavour1.Spec.Characteristics.Gpu != flavour2.Spec.Characteristics.Gpu { - return false - } - - if flavour1.Spec.Characteristics.Pods != flavour2.Spec.Characteristics.Pods { - return false - } - - if flavour1.Spec.Characteristics.Cpu.Value() != flavour2.Spec.Characteristics.Cpu.Value() { - return false - } - - if flavour1.Spec.Characteristics.Memory.Value()/ - flavour1.Spec.Policy.Partitionable.MemoryStep.Value() != - flavour2.Spec.Characteristics.Memory.Value()/ - flavour2.Spec.Policy.Partitionable.MemoryStep.Value() { - return false - } - - if flavour1.Spec.Characteristics.EphemeralStorage.Value() != flavour2.Spec.Characteristics.EphemeralStorage.Value() { - return false - } - - if flavour1.Spec.Characteristics.PersistentStorage.Value() != flavour2.Spec.Characteristics.PersistentStorage.Value() { - return false - } - - return true -} - -func mergeFlavours(flavours []nodecorev1alpha1.Flavour) []nodecorev1alpha1.Flavour { - mergedFlavours := []nodecorev1alpha1.Flavour{} - - for len(flavours) > 0 { - mergedFlavour := flavours[0] - mergedFlavour.Spec.QuantityAvailable = 1 - - mergedFlavour.Spec.Characteristics.Memory.Set( - (mergedFlavour.Spec.Characteristics.Memory.Value() / - mergedFlavour.Spec.Policy.Partitionable.MemoryStep.Value()) * - mergedFlavour.Spec.Policy.Partitionable.MemoryStep.Value(), - ) - - mergedFlavour.Spec.Characteristics.Cpu.Set( - (mergedFlavour.Spec.Characteristics.Cpu.Value() / - mergedFlavour.Spec.Policy.Partitionable.CpuStep.Value()) * - mergedFlavour.Spec.Policy.Partitionable.CpuStep.Value(), - ) - - flavours = flavours[1:] - - for i := len(flavours) - 1; i >= 0; i-- { - if canMergeFlavours(&mergedFlavour, &flavours[i]) { - mergedFlavour.Spec.QuantityAvailable += flavours[i].Spec.QuantityAvailable - flavours = append(flavours[:i], flavours[i+1:]...) - } - } - - mergedFlavours = append(mergedFlavours, mergedFlavour) - } - - return mergedFlavours -} - -func Start(ctx context.Context, cl client.Client) error { - klog.Info("Getting FLUIDOS Node identity...") - - nodeIdentity := getters.GetNodeIdentity(ctx, cl) - if nodeIdentity == nil { - klog.Info("Error getting FLUIDOS Node identity") - return fmt.Errorf("error getting FLUIDOS Node identity") - } - - // Check current flavours - flavours := &nodecorev1alpha1.FlavourList{} - err := cl.List(ctx, flavours) - if err != nil { - log.Printf("Error getting flavours: %v", err) - return err - } - - if len(flavours.Items) == 0 { - // Verify with current node status - klog.Info("Getting nodes resources...") - nodes, err := GetNodesResources(ctx, cl) - if err != nil { - log.Printf("Error getting nodes resources: %v", err) - return err - } - - klog.Infof("Creating Flavours: found %d nodes", len(nodes)) - - forgedFlavours := []nodecorev1alpha1.Flavour{} - - // For each node create a Flavour - // This creates a Flavour - for i := range nodes { - flavour := resourceforge.ForgeFlavourFromMetrics(&nodes[i], *nodeIdentity) - forgedFlavours = append(forgedFlavours, *flavour) - } - - // Perform Flavour merge - mergedFlavours := mergeFlavours(forgedFlavours) - - for i := range mergedFlavours { - if err := cl.Create(ctx, &mergedFlavours[i]); err != nil { - log.Printf("Error creating Flavour: %v", err) - return err - } - klog.Infof("Flavour created: %s", mergedFlavours[i].Name) - } - } - - return nil -} diff --git a/pkg/local-resource-manager/doc.go b/pkg/local-resource-manager/doc.go index 53e24db..aec290d 100644 --- a/pkg/local-resource-manager/doc.go +++ b/pkg/local-resource-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/local-resource-manager/node_controller.go b/pkg/local-resource-manager/node_controller.go new file mode 100644 index 0000000..f283521 --- /dev/null +++ b/pkg/local-resource-manager/node_controller.go @@ -0,0 +1,183 @@ +// Copyright 2022-2024 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 localresourcemanager + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" + metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" + "github.com/fluidos-project/node/pkg/utils/flags" + "github.com/fluidos-project/node/pkg/utils/getters" + models "github.com/fluidos-project/node/pkg/utils/models" + "github.com/fluidos-project/node/pkg/utils/resourceforge" +) + +// ClusterRole +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch +// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch +// +kubebuilder:rbac:groups=metrics.k8s.io,resources=pods,verbs=get;list;watch +// +kubebuilder:rbac:groups=metrics.k8s.io,resources=nodes,verbs=get;list;watch + +// NodeReconciler reconciles a Node object and creates Flavor objects. +type NodeReconciler struct { + client.Client + Scheme *runtime.Scheme + EnableAutoDiscovery bool + WebhookServer webhook.Server +} + +// Reconcile reconciles a Node object to create Flavor objects. +func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := ctrl.LoggerFrom(ctx, "node", req.NamespacedName) + ctx = ctrl.LoggerInto(ctx, log) + + // Check if AutoDiscovery is enabled + if !r.EnableAutoDiscovery { + klog.Info("AutoDiscovery is disabled") + return ctrl.Result{}, nil + } + + // Check if the webhook server is running + if err := r.WebhookServer.StartedChecker()(nil); err != nil { + klog.Info("Webhook server not started yet, requeuing the request") + return ctrl.Result{Requeue: true}, nil + } + + // Set for labels over the node + labelSelector := labels.Set{flags.ResourceNodeLabel: "true"}.AsSelector() + + // Fetch the Node instance + var node corev1.Node + if err := r.Get(ctx, req.NamespacedName, &node); err != nil { + if client.IgnoreNotFound(err) != nil { + klog.Info("Node not found") + return ctrl.Result{}, nil + } + } + + // Check if the node has the label + if !labelSelector.Matches(labels.Set(node.GetLabels())) { + klog.Infof("Node %s does not have the label %s", node.Name, flags.ResourceNodeLabel) + return ctrl.Result{}, nil + } + + nodeMetrics := &metricsv1beta1.NodeMetrics{} + + // Get the node metrics referred to the node + if err := r.Client.Get(ctx, client.ObjectKey{Name: node.Name}, nodeMetrics); err != nil { + klog.Errorf("Error getting NodeMetrics: %v", err) + return ctrl.Result{}, err + } + + // Get the NodeInfo struct for the node and its metrics + nodeInfo, err := GetNodeInfos(&node, nodeMetrics) + if err != nil { + klog.Errorf("Error getting NodeInfo: %v", err) + return ctrl.Result{}, err + } + klog.Infof("NodeInfo created: %s", nodeInfo.Name) + + // Get NodeIdentity + nodeIdentity := getters.GetNodeIdentity(ctx, r.Client) + if nodeIdentity == nil { + klog.Error("Error getting FLUIDOS Node identity") + return ctrl.Result{}, nil + } + + // Create ownerReferences with only the current node under examination + ownerReferences := []metav1.OwnerReference{ + { + APIVersion: nodecorev1alpha1.GroupVersion.String(), + Kind: "Node", + Name: node.Name, + UID: node.UID, + }, + } + + // Get all the Flavors owned by this node as kubernetes ownership + flavorsList := &nodecorev1alpha1.FlavorList{} + err = r.List(ctx, flavorsList) + if err != nil { + klog.Errorf("Error listing Flavors: %v", err) + return ctrl.Result{}, nil + } + + var matchFlavors []nodecorev1alpha1.Flavor + + // Filter the Flavors by the owner reference + for i := range flavorsList.Items { + flavor := &flavorsList.Items[i] + // Check if the node is one of the owners + for _, owner := range flavor.OwnerReferences { + if owner.Name == node.Name { + // Add the Flavor to the list + matchFlavors = append(matchFlavors, *flavor) + } + } + } + + // Check if you have found any Flavor + if len(matchFlavors) > 0 { + klog.Infof("Found %d flavors for node %s", len(matchFlavors), node.Name) + // TODO: Check if the Flavors are consistent with the NodeInfo + // TODO: Update the Flavors if necessary + return ctrl.Result{}, nil + } + + // No Flavor found, create a new one + flavor, err := r.createFlavor(ctx, nodeInfo, *nodeIdentity, ownerReferences) + if err != nil { + klog.Errorf("Error creating Flavor: %v", err) + return ctrl.Result{Requeue: true}, nil + } + klog.Infof("Flavor created: %s", flavor.Name) + + return ctrl.Result{}, nil +} + +func (r *NodeReconciler) createFlavor(ctx context.Context, nodeInfo *models.NodeInfo, + nodeIdentity nodecorev1alpha1.NodeIdentity, ownerReferences []metav1.OwnerReference) (flavor *nodecorev1alpha1.Flavor, err error) { + // Forge the Flavor from the NodeInfo and NodeIdentity + flavorResult := resourceforge.ForgeFlavorFromMetrics(nodeInfo, nodeIdentity, ownerReferences) + + // Create the Flavor + err = r.Create(ctx, flavorResult) + if err != nil { + return nil, err + } + klog.Infof("Flavor created: %s", flavorResult.Name) + + return flavorResult, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&corev1.Node{}). + Watches(&nodecorev1alpha1.Flavor{}, &handler.EnqueueRequestForObject{}). + Complete(r) +} diff --git a/pkg/local-resource-manager/node_services.go b/pkg/local-resource-manager/node_services.go index 84e0aa8..bffa5ad 100644 --- a/pkg/local-resource-manager/node_services.go +++ b/pkg/local-resource-manager/node_services.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,89 +15,56 @@ package localresourcemanager import ( - "context" + "fmt" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/klog/v2" metricsv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" - "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/fluidos-project/node/pkg/utils/flags" "github.com/fluidos-project/node/pkg/utils/models" ) -// GetNodesResources retrieves the metrics from all the worker nodes in the cluster. -func GetNodesResources(ctx context.Context, cl client.Client) ([]models.NodeInfo, error) { - // Set a label selector to filter worker nodes - labelSelector := labels.Set{flags.ResourceNodeLabel: "true"}.AsSelector() - - // Get a list of nodes - nodes := &corev1.NodeList{} - err := cl.List(ctx, nodes, &client.ListOptions{ - LabelSelector: labelSelector, - }) - if err != nil { - klog.Errorf("Error when listing nodes: %s", err) - return nil, err - } - - // Get a list of nodes metrics - nodesMetrics := &metricsv1beta1.NodeMetricsList{} - err = cl.List(ctx, nodesMetrics, &client.ListOptions{ - LabelSelector: labelSelector, - }) - if err != nil { - klog.Errorf("Error when listing nodes metrics: %s", err) - return nil, err +// GetNodeInfos returns the NodeInfo struct for a given node and its metrics. +func GetNodeInfos(node *corev1.Node, nodeMetrics *metricsv1beta1.NodeMetrics) (*models.NodeInfo, error) { + // Check if the node and the node metrics match + if node.Name != nodeMetrics.Name { + klog.Info("Node and NodeMetrics do not match") + return nil, fmt.Errorf("node and node metrics do not match") } - var nodesInfo []models.NodeInfo - // Print the name of each node - for n := range nodes.Items { - for m := range nodesMetrics.Items { - node := nodes.Items[n] - metrics := nodesMetrics.Items[m] - if nodes.Items[n].Name != nodesMetrics.Items[m].Name { - // So that we can select just the nodes that we want - continue - } - metricsStruct := forgeResourceMetrics(&metrics, &node) - nodeInfo := forgeNodeInfo(&node, metricsStruct) - nodesInfo = append(nodesInfo, *nodeInfo) - } - } + metricsStruct := forgeResourceMetrics(nodeMetrics, node) + nodeInfo := forgeNodeInfo(node, metricsStruct) - return nodesInfo, nil + return nodeInfo, nil } // forgeResourceMetrics creates from params a new ResourceMetrics Struct. func forgeResourceMetrics(nodeMetrics *metricsv1beta1.NodeMetrics, node *corev1.Node) *models.ResourceMetrics { // Get the total and used resources - cpuTotal := node.Status.Allocatable.Cpu() - cpuUsed := nodeMetrics.Usage.Cpu() - memoryTotal := node.Status.Allocatable.Memory() - memoryUsed := nodeMetrics.Usage.Memory() - podsTotal := node.Status.Allocatable.Pods() - podsUsed := nodeMetrics.Usage.Pods() - ephemeralStorage := nodeMetrics.Usage.StorageEphemeral() + cpuTotal := node.Status.Allocatable.Cpu().DeepCopy() + cpuUsed := nodeMetrics.Usage.Cpu().DeepCopy() + memoryTotal := node.Status.Allocatable.Memory().DeepCopy() + memoryUsed := nodeMetrics.Usage.Memory().DeepCopy() + podsTotal := node.Status.Allocatable.Pods().DeepCopy() + podsUsed := nodeMetrics.Usage.Pods().DeepCopy() + ephemeralStorage := nodeMetrics.Usage.StorageEphemeral().DeepCopy() // Compute the available resources cpuAvail := cpuTotal.DeepCopy() memAvail := memoryTotal.DeepCopy() podsAvail := podsTotal.DeepCopy() - cpuAvail.Sub(*cpuUsed) - memAvail.Sub(*memoryUsed) - podsAvail.Sub(*podsUsed) + cpuAvail.Sub(cpuUsed) + memAvail.Sub(memoryUsed) + podsAvail.Sub(podsUsed) return &models.ResourceMetrics{ - CPUTotal: *cpuTotal, + CPUTotal: cpuTotal, CPUAvailable: cpuAvail, - MemoryTotal: *memoryTotal, + MemoryTotal: memoryTotal, MemoryAvailable: memAvail, - PodsTotal: *podsTotal, + PodsTotal: podsTotal, PodsAvailable: podsAvail, - EphemeralStorage: *ephemeralStorage, + EphemeralStorage: ephemeralStorage, } } diff --git a/pkg/rear-controller/contract-manager/doc.go b/pkg/rear-controller/contract-manager/doc.go index 69b1c6d..77efbe7 100644 --- a/pkg/rear-controller/contract-manager/doc.go +++ b/pkg/rear-controller/contract-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-controller/contract-manager/models.go b/pkg/rear-controller/contract-manager/models.go index 5733222..1f845d8 100644 --- a/pkg/rear-controller/contract-manager/models.go +++ b/pkg/rear-controller/contract-manager/models.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,23 +24,16 @@ type Selector struct { EphemeralStorage int `json:"ephemeral-storage,omitempty"` } -// Transaction contains information regarding the transaction for a flavour. +// Transaction contains information regarding the transaction for a flavor. type Transaction struct { - TransactionID string `json:"transactionID"` - FlavourID string `json:"flavourID"` - StartTime time.Time `json:"startTime,omitempty"` + TransactionID string `json:"transactionID"` + FlavorID string `json:"flavorID"` + ExpirationTime time.Time `json:"startTime,omitempty"` } -// Purchase contains information regarding the purchase for a flavour. +// Purchase contains information regarding the purchase for a flavor. type Purchase struct { TransactionID string `json:"transactionID"` - FlavourID string `json:"flavourID"` + FlavorID string `json:"flavorID"` BuyerID string `json:"buyerID"` } - -// ResponsePurchase contain information after purchase a Flavour. -type ResponsePurchase struct { - FlavourID string `json:"flavourID"` - BuyerID string `json:"buyerID"` - Status string `json:"status"` -} diff --git a/pkg/rear-controller/contract-manager/reservation_controller.go b/pkg/rear-controller/contract-manager/reservation_controller.go index a62fe57..f629690 100644 --- a/pkg/rear-controller/contract-manager/reservation_controller.go +++ b/pkg/rear-controller/contract-manager/reservation_controller.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import ( nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" reservationv1alpha1 "github.com/fluidos-project/node/apis/reservation/v1alpha1" "github.com/fluidos-project/node/pkg/rear-controller/gateway" + "github.com/fluidos-project/node/pkg/utils/models" "github.com/fluidos-project/node/pkg/utils/namings" "github.com/fluidos-project/node/pkg/utils/resourceforge" "github.com/fluidos-project/node/pkg/utils/tools" @@ -175,14 +176,14 @@ func (r *ReservationReconciler) handleReserve(ctx context.Context, return ctrl.Result{}, err } - flavourID := namings.RetrieveFlavourNameFromPC(reservation.Spec.PeeringCandidate.Name) - res, err := r.Gateway.ReserveFlavour(ctx, reservation, flavourID) + flavorID := namings.RetrieveFlavorNameFromPC(reservation.Spec.PeeringCandidate.Name) + res, err := r.Gateway.ReserveFlavor(ctx, reservation, flavorID) if err != nil { if res != nil { klog.Infof("Transaction is non correctly set, Retrying...") return ctrl.Result{Requeue: true}, nil } - klog.Errorf("Error when reserving flavour for Reservation %s: %s", req.NamespacedName, err) + klog.Errorf("Error when reserving flavor for Reservation %s: %s", req.NamespacedName, err) // Set the peering candidate as available again peeringCandidate.Spec.Available = true @@ -200,7 +201,7 @@ func (r *ReservationReconciler) handleReserve(ctx context.Context, // Set the reservation as failed reservation.SetReserveStatus(nodecorev1alpha1.PhaseFailed) - reservation.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation failed: error when reserving flavour") + reservation.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation failed: error when reserving flavor") if err := r.updateReservationStatus(ctx, reservation); err != nil { klog.Errorf("Error when updating Reservation %s status: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -259,6 +260,34 @@ func (r *ReservationReconciler) handleReserve(ctx context.Context, func (r *ReservationReconciler) handlePurchase(ctx context.Context, req ctrl.Request, reservation *reservationv1alpha1.Reservation) (ctrl.Result, error) { purchasePhase := reservation.Status.PurchasePhase + + // Get the flavor from the peering candidate of the reservation + var peeringCandidate advertisementv1alpha1.PeeringCandidate + if err := r.Get(ctx, client.ObjectKey{ + Name: reservation.Spec.PeeringCandidate.Name, + Namespace: reservation.Spec.PeeringCandidate.Namespace, + }, &peeringCandidate); err != nil { + klog.Errorf("Error when getting PeeringCandidate %s before reconcile: %s", req.NamespacedName, err) + reservation.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation failed: error when getting PeeringCandidate") + if err := r.updateReservationStatus(ctx, reservation); err != nil { + klog.Errorf("Error when updating Reservation %s status before reconcile: %s", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + // Get the flavor type identifier from the flavor + flavorTypeIdentifier, _, err := nodecorev1alpha1.ParseFlavorType(&peeringCandidate.Spec.Flavor) + if err != nil { + klog.Errorf("Error when parsing Flavor %s type: %s", req.NamespacedName, err) + reservation.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation failed: error when parsing Flavor type") + if err := r.updateReservationStatus(ctx, reservation); err != nil { + klog.Errorf("Error when updating Reservation %s status before reconcile: %s", req.NamespacedName, err) + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + switch purchasePhase { case nodecorev1alpha1.PhaseIdle: klog.Infof("Purchase phase for the reservation %s idle, starting...", reservation.Name) @@ -281,9 +310,20 @@ func (r *ReservationReconciler) handlePurchase(ctx context.Context, } transactionID := reservation.Status.TransactionID - resPurchase, err := r.Gateway.PurchaseFlavour(ctx, transactionID, reservation.Spec.Seller) + var contract *models.Contract + var err error + + // Based on the flavorTypeIdentifier, purchase flavor action may need buyer credentials + switch flavorTypeIdentifier { + case nodecorev1alpha1.TypeK8Slice: + contract, err = r.Gateway.PurchaseFlavor(ctx, transactionID, reservation.Spec.Seller, nil) + // TODO: Implement other flavor types if purchase request needs buyer credentials + default: + klog.Errorf("Flavor type %s not supported", flavorTypeIdentifier) + } + if err != nil { - klog.Errorf("Error when purchasing flavour for Reservation %s: %s", req.NamespacedName, err) + klog.Errorf("Error when purchasing flavor for Reservation %s: %s", req.NamespacedName, err) // Set the PeerCandidate as available again var peeringCandidate advertisementv1alpha1.PeeringCandidate @@ -315,7 +355,7 @@ func (r *ReservationReconciler) handlePurchase(ctx context.Context, // Set the reservation as failed reservation.SetPurchaseStatus(nodecorev1alpha1.PhaseFailed) - reservation.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation failed: error when purchasing flavour") + reservation.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation failed: error when purchasing flavor") if err := r.updateReservationStatus(ctx, reservation); err != nil { klog.Errorf("Error when updating Reservation %s status: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -323,22 +363,26 @@ func (r *ReservationReconciler) handlePurchase(ctx context.Context, return ctrl.Result{}, err } - klog.Infof("Purchase completed with status %s", resPurchase.Status) + klog.Infof("Purchase completed, got contract ID %s", contract.ContractID) // Create a contract CR now that the reservation is solved - contract := resourceforge.ForgeContractFromObj(&resPurchase.Contract) - err = r.Create(ctx, contract) + contractCR, err := resourceforge.ForgeContractFromObj(contract) + if err != nil { + klog.Errorf("Error when forging Contract %s: %s", contractCR.Name, err) + return ctrl.Result{}, err + } + err = r.Create(ctx, contractCR) if errors.IsAlreadyExists(err) { - klog.Errorf("Error when creating Contract %s: %s", contract.Name, err) + klog.Errorf("Error when creating Contract %s: %s", contractCR.Name, err) } else if err != nil { - klog.Errorf("Error when creating Contract %s: %s", contract.Name, err) + klog.Errorf("Error when creating Contract %s: %s", contractCR.Name, err) return ctrl.Result{}, err } - klog.Infof("Contract %s created", contract.Name) + klog.Infof("Contract %s created", contractCR.Name) reservation.Status.Contract = nodecorev1alpha1.GenericRef{ - Name: contract.Name, - Namespace: contract.Namespace, + Name: contractCR.Name, + Namespace: contractCR.Namespace, } reservation.SetPurchaseStatus(nodecorev1alpha1.PhaseSolved) reservation.SetPhase(nodecorev1alpha1.PhaseSolved, "Reservation solved") diff --git a/pkg/rear-controller/discovery-manager/const.go b/pkg/rear-controller/discovery-manager/const.go deleted file mode 100644 index 5473567..0000000 --- a/pkg/rear-controller/discovery-manager/const.go +++ /dev/null @@ -1,27 +0,0 @@ -// 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 discoverymanager - -const ( - SERVER_ADDR = "http://localhost:14144/api" - K8S_TYPE = "k8s-fluidos" - DEFAULT_NAMESPACE = "default" - CLIENT_ID = "topix.fluidos.eu" -) - -// We define different server addresses, much more dynamic and less hardcoded -var ( - SERVER_ADDRESSES = []string{"http://localhost:14144/api"} -) diff --git a/pkg/rear-controller/discovery-manager/discovery_controller.go b/pkg/rear-controller/discovery-manager/discovery_controller.go index deb879e..d8df938 100644 --- a/pkg/rear-controller/discovery-manager/discovery_controller.go +++ b/pkg/rear-controller/discovery-manager/discovery_controller.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -86,10 +86,11 @@ func (r *DiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( //nolint:exhaustive // We don't need to handle all the cases switch discovery.Status.Phase.Phase { case nodecorev1alpha1.PhaseRunning: - flavours, err := r.Gateway.DiscoverFlavours(ctx, discovery.Spec.Selector) + klog.Infof("Discovery %s running", discovery.Name) + flavors, err := r.Gateway.DiscoverFlavors(ctx, discovery.Spec.Selector) if err != nil { - klog.Errorf("Error when getting Flavour: %s", err) - discovery.SetPhase(nodecorev1alpha1.PhaseFailed, "Error when getting Flavour") + klog.Errorf("Error when getting Flavor: %s", err) + discovery.SetPhase(nodecorev1alpha1.PhaseFailed, "Error when getting Flavor") if err := r.updateDiscoveryStatus(ctx, &discovery); err != nil { klog.Errorf("Error when updating Discovery %s status: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -97,9 +98,9 @@ func (r *DiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, nil } - if len(flavours) == 0 { - klog.Infof("No Flavours found") - discovery.SetPhase(nodecorev1alpha1.PhaseFailed, "No Flavours found") + if len(flavors) == 0 { + klog.Infof("No Flavors found") + discovery.SetPhase(nodecorev1alpha1.PhaseFailed, "No Flavors found") if err := r.updateDiscoveryStatus(ctx, &discovery); err != nil { klog.Errorf("Error when updating Discovery %s status: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -107,10 +108,10 @@ func (r *DiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( return ctrl.Result{}, nil } - klog.Infof("Flavours found: %d", len(flavours)) + klog.Infof("Flavors found: %d", len(flavors)) - for _, flavour := range flavours { - peeringCandidate = resourceforge.ForgePeeringCandidate(flavour, discovery.Spec.SolverID, true) + for _, flavor := range flavors { + peeringCandidate = resourceforge.ForgePeeringCandidate(flavor, discovery.Spec.SolverID, true) err = r.Create(context.Background(), peeringCandidate) if err != nil { klog.Infof("Discovery %s failed: error while creating Peering Candidate", discovery.Name) @@ -121,7 +122,7 @@ func (r *DiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( klog.Errorf("Error when updating PeeringCandidate %s status before reconcile: %s", req.NamespacedName, err) return ctrl.Result{}, err } - // Appemd the PeeringCandidate to the list of PeeringCandidates found by the Discovery + // Append the PeeringCandidate to the list of PeeringCandidates found by the Discovery discovery.Status.PeeringCandidateList.Items = append(discovery.Status.PeeringCandidateList.Items, *peeringCandidate) } diff --git a/pkg/rear-controller/discovery-manager/doc.go b/pkg/rear-controller/discovery-manager/doc.go index b19a2a4..8b3736a 100644 --- a/pkg/rear-controller/discovery-manager/doc.go +++ b/pkg/rear-controller/discovery-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go b/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go index 73b193f..4e8c0fb 100644 --- a/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go +++ b/pkg/rear-controller/discovery-manager/peeringcandidate_wh.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-controller/doc.go b/pkg/rear-controller/doc.go index 7f4e1c4..296aefa 100644 --- a/pkg/rear-controller/doc.go +++ b/pkg/rear-controller/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,5 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -// package rearcontroller implements the utility functions for the rear controller: contract manager, discovery manager and rear gateway +// Package rearcontroller implements the utility functions for the rear controller: contract manager, discovery manager and rear gateway package rearcontroller diff --git a/pkg/rear-controller/gateway/client.go b/pkg/rear-controller/gateway/client.go index 169578b..8b07697 100644 --- a/pkg/rear-controller/gateway/client.go +++ b/pkg/rear-controller/gateway/client.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "net/http" + "strings" "k8s.io/klog/v2" @@ -30,10 +31,8 @@ import ( "github.com/fluidos-project/node/pkg/utils/parseutil" ) -// TODO: move this function into the REAR Gateway package - -// ReserveFlavour reserves a flavour with the given flavourID. -func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1alpha1.Reservation, flavourID string) (*models.Transaction, error) { +// ReserveFlavor reserves a flavor with the given flavorID. +func (g *Gateway) ReserveFlavor(ctx context.Context, reservation *reservationv1alpha1.Reservation, flavorID string) (*models.Transaction, error) { err := checkLiqoReadiness(g.LiqoReady) if err != nil { return nil, err @@ -48,35 +47,34 @@ func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1 var transaction models.Transaction body := models.ReserveRequest{ - FlavourID: flavourID, + FlavorID: flavorID, Buyer: models.NodeIdentity{ NodeID: g.ID.NodeID, IP: g.ID.IP, Domain: g.ID.Domain, + AdditionalInformation: &models.NodeIdentityAdditionalInfo{ + LiqoID: liqoCredentials.ClusterID, + }, }, - ClusterID: liqoCredentials.ClusterID, - Partition: func() *models.Partition { - if reservation.Spec.Partition != nil { - return parseutil.ParsePartition(reservation.Spec.Partition) + Configuration: func() *models.Configuration { + if reservation.Spec.Configuration != nil { + configuration := parseutil.ParseConfiguration(reservation.Spec.Configuration) + return configuration } return nil }(), } - klog.Infof("Reservation %s for flavour %s", reservation.Name, flavourID) - - if reservation.Spec.Partition != nil { - body.Partition = parseutil.ParsePartition(reservation.Spec.Partition) - } + klog.Infof("Reservation %s for flavor %s", reservation.Name, flavorID) selectorBytes, err := json.Marshal(body) if err != nil { return nil, err } - // TODO: this url should be taken from the nodeIdentity of the flavour + // TODO: this url should be taken from the nodeIdentity of the flavor bodyBytes := bytes.NewBuffer(selectorBytes) - url := fmt.Sprintf("http://%s%s%s", reservation.Spec.Seller.IP, ReserveFlavourPath, flavourID) + url := fmt.Sprintf("http://%s%s", reservation.Spec.Seller.IP, Routes.Reserve) klog.Infof("Sending request to %s", url) @@ -87,7 +85,13 @@ func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1 defer resp.Body.Close() // Check if the response status code is 200 (OK) - if resp.StatusCode != http.StatusOK { + switch resp.StatusCode { + case http.StatusOK: + klog.Infof("Received OK response status code: %v", resp) + case http.StatusNotFound: + klog.Errorf("Received NOT FOUND response status code: %v", resp) + return nil, fmt.Errorf("received NOT FOUND response status code: %d", resp.StatusCode) + default: klog.Errorf("Received non-OK response status code: %v", resp) return nil, fmt.Errorf("received non-OK response status code: %d", resp.StatusCode) } @@ -104,21 +108,22 @@ func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1 return &transaction, fmt.Errorf("transactionID is empty") } - klog.Infof("Flavour %s reserved: transaction ID %s", flavourID, transaction.TransactionID) + klog.Infof("Flavor %s reserved: transaction ID %s", flavorID, transaction.TransactionID) g.addNewTransacion(&transaction) return &transaction, nil } -// PurchaseFlavour purchases a flavour with the given flavourID. -func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, seller nodecorev1alpha1.NodeIdentity) (*models.ResponsePurchase, error) { +// PurchaseFlavor purchases a flavor with the given flavorID. +func (g *Gateway) PurchaseFlavor(ctx context.Context, transactionID string, + seller nodecorev1alpha1.NodeIdentity, buyerLiqoCredentials *models.LiqoCredentials) (*models.Contract, error) { err := checkLiqoReadiness(g.LiqoReady) if err != nil { return nil, err } - var purchase models.ResponsePurchase + var contract models.Contract // Check if the transaction exists transaction, err := g.GetTransaction(transactionID) @@ -126,8 +131,10 @@ func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, sel return nil, err } + klog.Infof("Transaction %s for flavor %s", transactionID, transaction.FlavorID) + body := models.PurchaseRequest{ - TransactionID: transaction.TransactionID, + LiqoCredentials: buyerLiqoCredentials, } selectorBytes, err := json.Marshal(body) @@ -136,8 +143,8 @@ func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, sel } bodyBytes := bytes.NewBuffer(selectorBytes) - // TODO: this url should be taken from the nodeIdentity of the flavour - url := fmt.Sprintf("http://%s%s%s", seller.IP, PurchaseFlavourPath, transactionID) + apiPath := strings.Replace(Routes.Purchase, "{transactionID}", transactionID, 1) + url := fmt.Sprintf("http://%s%s", seller.IP, apiPath) resp, err := makeRequest(ctx, "POST", url, bodyBytes) if err != nil { @@ -150,56 +157,66 @@ func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, sel return nil, fmt.Errorf("received non-OK response status code: %d", resp.StatusCode) } - if err := json.NewDecoder(resp.Body).Decode(&purchase); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&contract); err != nil { return nil, err } - return &purchase, nil + return &contract, nil } -// DiscoverFlavours is a function that returns an array of Flavour that fit the Selector by performing a get request to an http server. -func (g *Gateway) DiscoverFlavours(ctx context.Context, selector *nodecorev1alpha1.FlavourSelector) ([]*nodecorev1alpha1.Flavour, error) { +// DiscoverFlavors is a function that returns an array of Flavor that fit the Selector by performing a get request to an http server. +func (g *Gateway) DiscoverFlavors(ctx context.Context, selector *nodecorev1alpha1.Selector) ([]*nodecorev1alpha1.Flavor, error) { + klog.Info("Discovering flavors") + + // Check if Liqo is ready err := checkLiqoReadiness(g.LiqoReady) if err != nil { return nil, err } - var s *models.Selector - var flavoursCR []*nodecorev1alpha1.Flavour + var s models.Selector + var flavorsCR []*nodecorev1alpha1.Flavor + klog.Info("Checking selector") if selector != nil { - s = parseutil.ParseFlavourSelector(selector) + s, err = parseutil.ParseFlavorSelector(selector) + klog.Infof("Selector parsed: %v", s) + if err != nil { + return nil, err + } } + klog.Info("Getting local providers") providers := getters.GetLocalProviders(context.Background(), g.client) - // Send the POST request to all the servers in the list + // Send the GET request to all the servers in the list for _, provider := range providers { - flavour, err := discover(ctx, s, provider) + klog.Infof("Provider: %s", provider) + flavors, err := discover(ctx, s, provider) if err != nil { - klog.Errorf("Error when searching Flavour: %s", err) + klog.Errorf("Error when searching Flavor: %s", err) return nil, err } - // Check if the flavour is nil - if flavour == nil { - klog.Infof("No Flavours found for provider %s", provider) + // Check if the flavor is nil + if len(flavors) == 0 { + klog.Infof("No Flavors found for provider %s", provider) } else { - klog.Infof("Flavour found for provider %s", provider) - flavoursCR = append(flavoursCR, flavour) + klog.Infof("Flavors found for provider %s: %d", provider, len(flavors)) + flavorsCR = append(flavorsCR, flavors...) } } - klog.Infof("Found %d flavours", len(flavoursCR)) - return flavoursCR, nil + klog.Infof("Found %d flavors", len(flavorsCR)) + return flavorsCR, nil } -func discover(ctx context.Context, s *models.Selector, provider string) (*nodecorev1alpha1.Flavour, error) { +func discover(ctx context.Context, s models.Selector, provider string) ([]*nodecorev1alpha1.Flavor, error) { if s != nil { - klog.Infof("Searching Flavour with selector %v", s) - return searchFlavourWithSelector(ctx, s, provider) + klog.Infof("Searching Flavor with selector %v", s) + return searchFlavorWithSelector(ctx, s, provider) } - klog.Infof("Searching Flavour with no selector") - return searchFlavour(ctx, provider) + klog.Infof("Searching Flavor with no selector") + return searchFlavor(ctx, provider) } func checkLiqoReadiness(b bool) error { diff --git a/pkg/rear-controller/gateway/doc.go b/pkg/rear-controller/gateway/doc.go index fd5dce0..43dc13a 100644 --- a/pkg/rear-controller/gateway/doc.go +++ b/pkg/rear-controller/gateway/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-controller/gateway/gateway.go b/pkg/rear-controller/gateway/gateway.go index bb826a9..9c1d3f2 100644 --- a/pkg/rear-controller/gateway/gateway.go +++ b/pkg/rear-controller/gateway/gateway.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -37,25 +37,25 @@ import ( // clusterRole // +kubebuilder:rbac:groups=reservation.fluidos.eu,resources=contracts,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours/finalizers,verbs=update +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors/finalizers,verbs=update // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations/status,verbs=get;update;patch // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations/finalizers,verbs=update // +kubebuilder:rbac:groups=core,resources=*,verbs=get;list;watch const ( - // ListFlavoursPath is the path to get the list of flavours. - ListFlavoursPath = "/api/listflavours" - // ListFlavourByIDPath is the path to get a flavour by ID. - ListFlavourByIDPath = "/api/listflavours/" - // ReserveFlavourPath is the path to reserve a flavour. - ReserveFlavourPath = "/api/reserveflavour/" - // PurchaseFlavourPath is the path to purchase a flavour. - PurchaseFlavourPath = "/api/purchaseflavour/" - // ListFlavoursBySelectorPath is the path to get the list of flavours by selector. - ListFlavoursBySelectorPath = "/api/listflavours/selector" + // ListFlavorsPath is the path to get the list of flavors. + ListFlavorsPath = "/api/listflavors" + // ListFlavorByIDPath is the path to get a flavor by ID. + ListFlavorByIDPath = "/api/listflavors/" + // ReserveFlavorPath is the path to reserve a flavor. + ReserveFlavorPath = "/api/reserveflavor/" + // PurchaseFlavorPath is the path to purchase a flavor. + PurchaseFlavorPath = "/api/purchaseflavor/" + // ListFlavorsBySelectorPath is the path to get the list of flavors by selector. + ListFlavorsBySelectorPath = "/api/listflavors/selector" ) // Gateway is the object that contains all the logical data stractures of the REAR Gateway. @@ -64,7 +64,7 @@ type Gateway struct { ID *nodecorev1alpha1.NodeIdentity // Transactions is a map of Transaction - Transactions map[string]models.Transaction + Transactions map[string]*models.Transaction // client is the Kubernetes client client client.Client @@ -80,7 +80,7 @@ type Gateway struct { func NewGateway(c client.Client) *Gateway { return &Gateway{ client: c, - Transactions: make(map[string]models.Transaction), + Transactions: make(map[string]*models.Transaction), LiqoReady: false, ClusterID: "", } @@ -107,12 +107,11 @@ func (g *Gateway) Start(ctx context.Context) error { router.Use(g.readinessMiddleware) // Gateway endpoints - router.HandleFunc(ListFlavoursPath, g.getFlavours).Methods("GET") - //nolint:gocritic // For the moment we are not using this endpoint - // router.HandleFunc(LIST_FLAVOUR_BY_ID_PATH+"{flavourID}", g.getFlavourByID).Methods("GET") - router.HandleFunc(ListFlavoursBySelectorPath, g.getFlavoursBySelector).Methods("POST") - router.HandleFunc(ReserveFlavourPath+"{flavourID}", g.reserveFlavour).Methods("POST") - router.HandleFunc(PurchaseFlavourPath+"{transactionID}", g.purchaseFlavour).Methods("POST") + router.HandleFunc(Routes.Flavors, g.getFlavors).Methods("GET") + router.HandleFunc(Routes.K8SliceFlavors, g.getK8SliceFlavorsBySelector).Methods("GET") + // TODO: Implement the other selector types + router.HandleFunc(Routes.Reserve, g.reserveFlavor).Methods("POST") + router.HandleFunc(Routes.Purchase, g.purchaseFlavor).Methods("POST") // Configure the HTTP server //nolint:gosec // we are not using a TLS certificate @@ -155,7 +154,7 @@ func (g *Gateway) CacheRefresher(interval time.Duration) func(ctx context.Contex func (g *Gateway) refreshCache(ctx context.Context) (bool, error) { klog.Infof("Refreshing cache") for transactionID, transaction := range g.Transactions { - if tools.CheckExpiration(transaction.StartTime, flags.ExpirationTransaction) { + if tools.CheckExpiration(transaction.ExpirationTime) { klog.Infof("Transaction %s expired, removing it from cache...", transactionID) g.removeTransaction(transactionID) return false, nil diff --git a/pkg/rear-controller/gateway/provider.go b/pkg/rear-controller/gateway/provider.go index 4c9ec81..320cb34 100644 --- a/pkg/rear-controller/gateway/provider.go +++ b/pkg/rear-controller/gateway/provider.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,18 +17,15 @@ package gateway import ( "context" "encoding/json" - "io" "net/http" "github.com/gorilla/mux" - "k8s.io/apimachinery/pkg/api/resource" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" reservationv1alpha1 "github.com/fluidos-project/node/apis/reservation/v1alpha1" "github.com/fluidos-project/node/pkg/utils/common" - "github.com/fluidos-project/node/pkg/utils/flags" "github.com/fluidos-project/node/pkg/utils/getters" "github.com/fluidos-project/node/pkg/utils/models" "github.com/fluidos-project/node/pkg/utils/namings" @@ -38,104 +35,96 @@ import ( "github.com/fluidos-project/node/pkg/utils/tools" ) -// getFlavours gets all the flavours CRs from the cluster. -func (g *Gateway) getFlavours(w http.ResponseWriter, _ *http.Request) { +// getFlavors gets all the flavors CRs from the cluster. +func (g *Gateway) getFlavors(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") - klog.Infof("Processing request for getting all Flavours...") + klog.Infof("Processing request for getting all Flavors...") - flavours, err := services.GetAllFlavours(g.client) + flavors, err := services.GetAllFlavors(g.client) if err != nil { - klog.Errorf("Error getting all the Flavour CRs: %s", err) - http.Error(w, "Error getting all the Flavour CRs", http.StatusInternalServerError) + klog.Errorf("Error getting all the Flavor CRs: %s", err) + http.Error(w, "Error getting all the Flavor CRs", http.StatusInternalServerError) return } - klog.Infof("Found %d Flavours in the cluster", len(flavours)) + klog.Infof("Found %d Flavors in the cluster", len(flavors)) - availableFlavours := make([]nodecorev1alpha1.Flavour, 0) + availableFlavors := make([]nodecorev1alpha1.Flavor, 0) - // Filtering only the available flavours - for i := range flavours { - if !flavours[i].Spec.OptionalFields.Availability { - availableFlavours = append(availableFlavours, flavours[i]) + // Filtering only the available flavors + for i := range flavors { + if !flavors[i].Spec.Availability { + availableFlavors = append(availableFlavors, flavors[i]) } } - klog.Infof("Available Flavours: %d", len(availableFlavours)) - if len(availableFlavours) == 0 { - klog.Infof("No available Flavours found") + klog.Infof("Available Flavors: %d", len(availableFlavors)) + if len(availableFlavors) == 0 { + klog.Infof("No available Flavors found") // Return content for empty list - emptyList := make([]*nodecorev1alpha1.Flavour, 0) + emptyList := make([]*nodecorev1alpha1.Flavor, 0) encodeResponseStatusCode(w, emptyList, http.StatusNoContent) return } - // Select the flavour with the max CPU - max := resource.MustParse("0") - index := 0 - for i := range availableFlavours { - if availableFlavours[i].Spec.Characteristics.Cpu.Cmp(max) == 1 { - max = availableFlavours[i].Spec.Characteristics.Cpu - index = i + // Parse the flavors CR to the models.Flavor struct + flavorsParsed := make([]models.Flavor, 0) + for i := range availableFlavors { + parsedFlavor := parseutil.ParseFlavor(&availableFlavors[i]) + if parsedFlavor == nil { + klog.Errorf("Error parsing the Flavor: %s", err) + continue } + flavorsParsed = append(flavorsParsed, *parsedFlavor) } - selected := *flavours[index].DeepCopy() - - klog.Infof("Flavour %s selected - Parsing...", selected.Name) - parsed := parseutil.ParseFlavour(&selected) - - klog.Infof("Flavour parsed: %v", parsed) - - // Encode the Flavour as JSON and write it to the response writer - encodeResponse(w, parsed) + // Encode the Flavor as JSON and write it to the response writer + encodeResponse(w, flavorsParsed) } -// getFlavourBySelectorHandler gets the flavour CRs from the cluster that match the selector. -func (g *Gateway) getFlavoursBySelector(w http.ResponseWriter, r *http.Request) { +// getFlavorBySelectorHandler gets the flavor CRs from the cluster that match the selector. +func (g *Gateway) getK8SliceFlavorsBySelector(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - klog.Infof("Processing request for getting Flavours by selector...") + klog.Infof("Processing request for getting K8Slice Flavors by selector...") - // Read the request body - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + klog.Infof("URL: %s", r.URL.String()) - // build the selector from the request body - selector, err := buildSelector(body) + // build the selector from the url query parameters + selector, err := queryParamToSelector(r.URL.Query(), models.K8SliceNameDefault) if err != nil { - klog.Errorf("Error building the selector: %s", err) - http.Error(w, err.Error(), http.StatusBadRequest) + klog.Errorf("Error building the selector from the URL query parameters: %s", err) + http.Error(w, "Error building the selector from the URL query parameters", http.StatusBadRequest) return } - flavours, err := services.GetAllFlavours(g.client) + // Print the selector information parsing it: + klog.Infof("Selector type: %s", selector.GetSelectorType()) + + flavors, err := services.GetAllFlavors(g.client) if err != nil { - klog.Errorf("Error getting all the Flavour CRs: %s", err) - http.Error(w, "Error getting all the Flavour CRs", http.StatusInternalServerError) + klog.Errorf("Error getting all the Flavor CRs: %s", err) + http.Error(w, "Error getting all the Flavor CRs", http.StatusInternalServerError) return } - klog.Infof("Found %d Flavours in the cluster", len(flavours)) + klog.Infof("Found %d Flavors in the cluster", len(flavors)) - availableFlavours := make([]nodecorev1alpha1.Flavour, 0) + availableFlavors := make([]nodecorev1alpha1.Flavor, 0) - // Filtering only the available flavours - for i := range flavours { - if flavours[i].Spec.OptionalFields.Availability { - availableFlavours = append(availableFlavours, flavours[i]) + // Filtering only the available flavors + for i := range flavors { + if flavors[i].Spec.Availability { + availableFlavors = append(availableFlavors, flavors[i]) } } - klog.Infof("Available Flavours: %d", len(availableFlavours)) - if len(availableFlavours) == 0 { - klog.Infof("No available Flavours found") + klog.Infof("Available Flavors: %d", len(availableFlavors)) + if len(availableFlavors) == 0 { + klog.Infof("No available Flavors found") // Return content for empty list - emptyList := make([]*nodecorev1alpha1.Flavour, 0) + emptyList := make([]models.Flavor, 0) encodeResponseStatusCode(w, emptyList, http.StatusNoContent) return } @@ -147,50 +136,41 @@ func (g *Gateway) getFlavoursBySelector(w http.ResponseWriter, r *http.Request) return } - klog.Infof("Filtering Flavours by selector...") - flavoursSelected, err := common.FilterFlavoursBySelector(availableFlavours, selector) + klog.Infof("Filtering Flavors by selector...") + flavorsSelected, err := common.FilterFlavorsBySelector(availableFlavors, selector) if err != nil { - http.Error(w, "Error getting the Flavours by selector", http.StatusInternalServerError) + http.Error(w, "Error getting the Flavors by selector", http.StatusInternalServerError) return } - klog.Infof("Flavours found that match the selector are: %d", len(flavoursSelected)) + klog.Infof("Flavors found that match the selector are: %d", len(flavorsSelected)) - if len(flavoursSelected) == 0 { - klog.Infof("No matching Flavours found") + if len(flavorsSelected) == 0 { + klog.Infof("No matching Flavors found") // Return content for empty list - emptyList := make([]*nodecorev1alpha1.Flavour, 0) - encodeResponseStatusCode(w, emptyList, http.StatusNoContent) + emptyList := make([]models.Flavor, 0) + encodeResponse(w, emptyList) return } - // Select the flavour with the max CPU - max := resource.MustParse("0") - index := 0 - - for i := range flavoursSelected { - if flavoursSelected[i].Spec.Characteristics.Cpu.Cmp(max) == 1 { - max = flavoursSelected[i].Spec.Characteristics.Cpu - index = i + // Parse the flavors CR to the models.Flavor struct + flavorsParsed := make([]models.Flavor, 0) + for i := range flavorsSelected { + parsedFlavor := parseutil.ParseFlavor(&availableFlavors[i]) + if parsedFlavor == nil { + klog.Errorf("Error parsing the Flavor: %s", err) + continue } + flavorsParsed = append(flavorsParsed, *parsedFlavor) } - selected := *flavoursSelected[index].DeepCopy() - - klog.Infof("Flavour %s selected - Parsing...", selected.Name) - parsed := parseutil.ParseFlavour(&selected) - - klog.Infof("Flavour parsed: %v", parsed) - - // Encode the Flavour as JSON and write it to the response writer - encodeResponse(w, parsed) + // Encode the Flavor as JSON and write it to the response writer + encodeResponse(w, flavorsParsed) } -// reserveFlavour reserves a Flavour by its flavourID. -func (g *Gateway) reserveFlavour(w http.ResponseWriter, r *http.Request) { - // Get the flavourID value from the URL parameters - params := mux.Vars(r) - flavourID := params["flavourID"] +// reserveFlavor reserves a Flavor by its flavorID. +func (g *Gateway) reserveFlavor(w http.ResponseWriter, r *http.Request) { + // Get the flavorID value from the URL parameters var transaction *models.Transaction var request models.ReserveRequest @@ -200,30 +180,51 @@ func (g *Gateway) reserveFlavour(w http.ResponseWriter, r *http.Request) { return } - klog.Infof("Partition: %v", *request.Partition) + flavorID := request.FlavorID - if flavourID != request.FlavourID { - klog.Infof("Mismatch body & param: %s != %s", flavourID, request.FlavourID) - http.Error(w, "Mismatch body & param", http.StatusConflict) + // Get the flavor by ID + flavor, err := services.GetFlavorByID(flavorID, g.client) + if err != nil { + klog.Errorf("Error getting the Flavor by ID: %s", err) + http.Error(w, "Error getting the Flavor by ID", http.StatusInternalServerError) + return + } + if flavor == nil { + klog.Errorf("Flavor %s not found", flavorID) + http.Error(w, "Flavor not found", http.StatusNotFound) + return + } + // Get the flavor type + flavorTypeIdentifier, _, err := nodecorev1alpha1.ParseFlavorType(flavor) + if err != nil { + klog.Errorf("Error parsing the Flavor type: %s", err) + http.Error(w, "Error parsing the Flavor type", http.StatusInternalServerError) + return + } + // Check if configuration is valid, based on the Flavor the client wants to reserve + switch flavorTypeIdentifier { + case nodecorev1alpha1.TypeK8Slice: + // The configuration is not necessary for the K8Slice flavor + if request.Configuration == nil { + klog.Info("No configuration provided for K8Slice flavor") + } + // TODO: Implement the other flavor types + default: + klog.Errorf("Flavor type %s not supported", flavorTypeIdentifier) + http.Error(w, "Flavor type not supported", http.StatusBadRequest) return } // Check if the Transaction already exists - t, found := g.SearchTransaction(request.Buyer.NodeID, flavourID) + t, found := g.SearchTransaction(request.Buyer.NodeID, flavorID) if found { - t.StartTime = tools.GetTimeNow() + t.ExpirationTime = tools.GetExpirationTime() transaction = t g.addNewTransacion(t) } if !found { - klog.Infof("Reserving flavour %s started", flavourID) - - flavour, _ := services.GetFlavourByID(flavourID, g.client) - if flavour == nil { - http.Error(w, "Flavour not found", http.StatusNotFound) - return - } + klog.Infof("Reserving flavor %s started", flavorID) // Create a new transaction ID transactionID, err := namings.ForgeTransactionID() @@ -232,6 +233,12 @@ func (g *Gateway) reserveFlavour(w http.ResponseWriter, r *http.Request) { return } + // Check the consumer communicated the LiqoID in the optional AdditionalInformation field + if request.Buyer.AdditionalInformation == nil || request.Buyer.AdditionalInformation.LiqoID == "" { + http.Error(w, "Error: LiqoID not provided", http.StatusBadRequest) + return + } + // Create a new transaction transaction = resourceforge.ForgeTransactionObj(transactionID, &request) @@ -244,9 +251,9 @@ func (g *Gateway) reserveFlavour(w http.ResponseWriter, r *http.Request) { encodeResponse(w, transaction) } -// purchaseFlavour is an handler for purchasing a Flavour. -func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { - // Get the flavourID value from the URL parameters +// purchaseFlavor is an handler for purchasing a Flavor. +func (g *Gateway) purchaseFlavor(w http.ResponseWriter, r *http.Request) { + // Get the flavorID value from the URL parameters params := mux.Vars(r) transactionID := params["transactionID"] var purchase models.PurchaseRequest @@ -256,25 +263,19 @@ func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { return } - if transactionID != purchase.TransactionID { - klog.Infof("Mismatch body & param") - http.Error(w, "Mismatch body & param", http.StatusConflict) - return - } - - klog.Infof("Purchasing request for transaction %s", purchase.TransactionID) + klog.Infof("Purchasing request for transaction %s", transactionID) // Retrieve the transaction from the transactions map - transaction, err := g.GetTransaction(purchase.TransactionID) + transaction, err := g.GetTransaction(transactionID) if err != nil { klog.Errorf("Error getting the Transaction: %s", err) http.Error(w, "Error getting the Transaction", http.StatusInternalServerError) return } - klog.Infof("Flavour requested: %s", transaction.FlavourID) + klog.Infof("Flavor requested: %s", transaction.FlavorID) - if tools.CheckExpiration(transaction.StartTime, flags.ExpirationTransaction) { + if tools.CheckExpiration(transaction.ExpirationTime) { klog.Infof("Transaction %s expired", transaction.TransactionID) http.Error(w, "Error: transaction Timeout", http.StatusRequestTimeout) g.removeTransaction(transaction.TransactionID) @@ -285,7 +286,7 @@ func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { var contract reservationv1alpha1.Contract // Check if the Contract with the same TransactionID already exists - if err := g.client.List(context.Background(), &contractList, client.MatchingFields{"spec.transactionID": purchase.TransactionID}); err != nil { + if err := g.client.List(context.Background(), &contractList, client.MatchingFields{"spec.transactionID": transactionID}); err != nil { if client.IgnoreNotFound(err) != nil { klog.Errorf("Error when listing Contracts: %s", err) http.Error(w, "Error when listing Contracts", http.StatusInternalServerError) @@ -294,29 +295,27 @@ func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { } if len(contractList.Items) > 0 { - klog.Infof("Contract already exists for transaction %s", purchase.TransactionID) + klog.Infof("Contract already exists for transaction %s", transactionID) contract = contractList.Items[0] // Create a contract object to be returned with the response contractObject := parseutil.ParseContract(&contract) - // create a response purchase - responsePurchase := resourceforge.ForgeResponsePurchaseObj(contractObject) // Respond with the response purchase as JSON - encodeResponse(w, responsePurchase) + encodeResponse(w, contractObject) return } - klog.Infof("Performing purchase of flavour %s...", transaction.FlavourID) + klog.Infof("Performing purchase of flavor %s...", transaction.FlavorID) // Remove the transaction from the transactions map g.removeTransaction(transaction.TransactionID) - klog.Infof("Flavour %s successfully purchased!", transaction.FlavourID) + klog.Infof("Flavor %s successfully purchased!", transaction.FlavorID) - // Get the flavour sold for creating the contract - flavourSold, err := services.GetFlavourByID(transaction.FlavourID, g.client) + // Get the flavor sold for creating the contract + flavorSold, err := services.GetFlavorByID(transaction.FlavorID, g.client) if err != nil { - klog.Errorf("Error getting the Flavour by ID: %s", err) - http.Error(w, "Error getting the Flavour by ID", http.StatusInternalServerError) + klog.Errorf("Error getting the Flavor by ID: %s", err) + http.Error(w, "Error getting the Flavor by ID", http.StatusInternalServerError) return } @@ -329,7 +328,7 @@ func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { // Create a new contract klog.Infof("Creating a new contract...") - contract = *resourceforge.ForgeContract(flavourSold, &transaction, liqoCredentials) + contract = *resourceforge.ForgeContract(flavorSold, &transaction, liqoCredentials) err = g.client.Create(context.Background(), &contract) if err != nil { klog.Errorf("Error creating the Contract: %s", err) @@ -341,15 +340,16 @@ func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { // Create a contract object to be returned with the response contractObject := parseutil.ParseContract(&contract) - // create a response purchase - responsePurchase := resourceforge.ForgeResponsePurchaseObj(contractObject) - klog.Infof("Contract %v", *contractObject.Partition) + if contractObject.Configuration != nil { + klog.Infof("Contract %v", *contractObject.Configuration) + } else { + klog.Infof("No configuration found in the contract") + } // Create allocation klog.Infof("Creating allocation...") - workerName := contract.Spec.Flavour.Spec.OptionalFields.WorkerID - allocation := *resourceforge.ForgeAllocation(&contract, "", workerName, nodecorev1alpha1.Remote, nodecorev1alpha1.Node) + allocation := *resourceforge.ForgeAllocation(&contract, "") err = g.client.Create(context.Background(), &allocation) if err != nil { klog.Errorf("Error creating the Allocation: %s", err) @@ -357,8 +357,8 @@ func (g *Gateway) purchaseFlavour(w http.ResponseWriter, r *http.Request) { return } - klog.Infof("Response purchase %v", *responsePurchase.Contract.Partition) + klog.Infof("Contract %s successfully created and now sending to the client!", contract.Name) // Respond with the response purchase as JSON - encodeResponse(w, responsePurchase) + encodeResponse(w, contractObject) } diff --git a/pkg/rear-controller/gateway/routes.go b/pkg/rear-controller/gateway/routes.go new file mode 100644 index 0000000..fa9d8ae --- /dev/null +++ b/pkg/rear-controller/gateway/routes.go @@ -0,0 +1,41 @@ +// Copyright 2022-2024 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 gateway + +// Routes defines the routes for the rear controller. +var Routes = struct { + // Flavors is the route to get all the flavors. + Flavors string + // K8SliceFlavors is the route to get all the K8Slice flavors. + K8SliceFlavors string + // VMFlavors is the route to get all the VM flavors. + VMFlavors string + // ServiceFlavors is the route to get all the service flavors. + ServiceFlavors string + // SensorsFlavors is the route to get all the sensors flavors. + SensorsFlavors string + // Reserve is the route to reserve a flavor. + Reserve string + // Purchase is the route to purchase a flavor. + Purchase string +}{ + Flavors: "/api/v2/flavors", + K8SliceFlavors: "/api/v2/flavors/k8slice", + VMFlavors: "/api/v2/flavors/vm", + ServiceFlavors: "/api/v2/flavors/service", + SensorsFlavors: "/api/v2/flavors/sensors", + Reserve: "/api/v2/reservations", + Purchase: "/api/v2/transactions/{transactionID}/purchase", +} diff --git a/pkg/rear-controller/gateway/services.go b/pkg/rear-controller/gateway/services.go index e739063..238b9fe 100644 --- a/pkg/rear-controller/gateway/services.go +++ b/pkg/rear-controller/gateway/services.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -28,19 +28,33 @@ import ( "github.com/fluidos-project/node/pkg/utils/resourceforge" ) -func searchFlavourWithSelector(ctx context.Context, selector *models.Selector, addr string) (*nodecorev1alpha1.Flavour, error) { - var flavour models.Flavour +func searchFlavorWithSelector(ctx context.Context, selector models.Selector, addr string) ([]*nodecorev1alpha1.Flavor, error) { + var flavors []models.Flavor - // Marshal the selector into JSON bytes - selectorBytes, err := json.Marshal(selector) + var url string + + v, err := selectorToQueryParams(selector) if err != nil { return nil, err } - body := bytes.NewBuffer(selectorBytes) - url := fmt.Sprintf("http://%s%s", addr, ListFlavoursBySelectorPath) + // Differentiate the URL request based on the selector type + switch selector.GetSelectorType() { + case models.K8SliceNameDefault: + url = fmt.Sprintf("http://%s%s", addr, Routes.K8SliceFlavors) + // Convert the selector to query parameters + // TODO: Implement the other selector types + + default: + return nil, fmt.Errorf("unsupported selector type") + } + + // Append the query parameters to the URL + url += "?" + v + klog.Infof("URL: %s", url) - resp, err := makeRequest(ctx, "POST", url, body) + // Make the request + resp, err := makeRequest(ctx, "GET", url, nil) if err != nil { return nil, err } @@ -57,20 +71,32 @@ func searchFlavourWithSelector(ctx context.Context, selector *models.Selector, a return nil, fmt.Errorf("received non-OK response status code: %d", resp.StatusCode) } - if err := json.NewDecoder(resp.Body).Decode(&flavour); err != nil { + // Print the response body + klog.Infof("Response body: %s", resp.Body) + + if err := json.NewDecoder(resp.Body).Decode(&flavors); err != nil { klog.Errorf("Error decoding the response body: %s", err) return nil, err } - flavourCR := resourceforge.ForgeFlavourFromObj(&flavour) + var flavorCRs []*nodecorev1alpha1.Flavor + + for i := range flavors { + flavor := &flavors[i] + flavorCR, err := resourceforge.ForgeFlavorFromObj(flavor) + if err != nil { + return nil, err + } + flavorCRs = append(flavorCRs, flavorCR) + } - return flavourCR, nil + return flavorCRs, nil } -func searchFlavour(ctx context.Context, addr string) (*nodecorev1alpha1.Flavour, error) { - var flavour models.Flavour +func searchFlavor(ctx context.Context, addr string) ([]*nodecorev1alpha1.Flavor, error) { + var flavors []models.Flavor - url := fmt.Sprintf("http://%s%s", addr, ListFlavoursPath) + url := fmt.Sprintf("http://%s%s", addr, ListFlavorsPath) resp, err := makeRequest(ctx, "GET", url, nil) if err != nil { @@ -88,14 +114,23 @@ func searchFlavour(ctx context.Context, addr string) (*nodecorev1alpha1.Flavour, return nil, fmt.Errorf("received non-OK response status code: %d", resp.StatusCode) } - if err := json.NewDecoder(resp.Body).Decode(&flavour); err != nil { + if err := json.NewDecoder(resp.Body).Decode(&flavors); err != nil { klog.Errorf("Error decoding the response body: %s", err) return nil, err } - flavourCR := resourceforge.ForgeFlavourFromObj(&flavour) + var flavorCRs []*nodecorev1alpha1.Flavor + + for i := range flavors { + flavor := &flavors[i] + flavorCR, err := resourceforge.ForgeFlavorFromObj(flavor) + if err != nil { + return nil, err + } + flavorCRs = append(flavorCRs, flavorCR) + } - return flavourCR, nil + return flavorCRs, nil } func makeRequest(ctx context.Context, method, url string, body *bytes.Buffer) (*http.Response, error) { diff --git a/pkg/rear-controller/gateway/utils.go b/pkg/rear-controller/gateway/utils.go index e71689f..594466d 100644 --- a/pkg/rear-controller/gateway/utils.go +++ b/pkg/rear-controller/gateway/utils.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,19 +18,48 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "reflect" + "regexp" + "strings" + + resourceLib "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/klog/v2" "github.com/fluidos-project/node/pkg/utils/models" ) -// buildSelector builds a selector from a request body. -func buildSelector(body []byte) (*models.Selector, error) { - // Parse the request body into the APIRequest struct - var selector models.Selector - err := json.Unmarshal(body, &selector) - if err != nil { - return &models.Selector{}, err +// selectorToQueryParams converts a selector to a query string. +func selectorToQueryParams(selector models.Selector) (string, error) { + switch selector.GetSelectorType() { + case models.K8SliceNameDefault: + k8sliceSelector := selector.(models.K8SliceSelector) + return encodeK8SliceSelector(k8sliceSelector) + // TODO: Implement the other selector types + default: + return "", fmt.Errorf("unsupported selector type") + } +} + +// queryParamToSelector converts a query string to a selector. +func queryParamToSelector(queryValues url.Values, selectorType models.FlavorTypeName) (models.Selector, error) { + klog.Infof("queryValues: %v", queryValues) + + // Print each query parameter + for key, value := range queryValues { + klog.Infof("key: %s, value: %s", key, value) + } + + switch selectorType { + case models.K8SliceNameDefault: + k8sliceSelector, err := decodeK8SliceSelector(queryValues) + if err != nil { + return nil, err + } + return *k8sliceSelector, nil + default: + return nil, fmt.Errorf("unsupported selector type") } - return &selector, nil } // GetTransaction returns a transaction from the transactions map. @@ -39,14 +68,14 @@ func (g *Gateway) GetTransaction(transactionID string) (models.Transaction, erro if !exists { return models.Transaction{}, fmt.Errorf("transaction not found") } - return transaction, nil + return *transaction, nil } // SearchTransaction returns a transaction from the transactions map. -func (g *Gateway) SearchTransaction(buyerID, flavourID string) (*models.Transaction, bool) { +func (g *Gateway) SearchTransaction(buyerID, flavorID string) (*models.Transaction, bool) { for _, t := range g.Transactions { - if t.Buyer.NodeID == buyerID && t.FlavourID == flavourID { - return &t, true + if t.Buyer.NodeID == buyerID && t.FlavorID == flavorID { + return t, true } } return &models.Transaction{}, false @@ -54,7 +83,7 @@ func (g *Gateway) SearchTransaction(buyerID, flavourID string) (*models.Transact // addNewTransacion add a new transaction to the transactions map. func (g *Gateway) addNewTransacion(transaction *models.Transaction) { - g.Transactions[transaction.TransactionID] = *transaction + g.Transactions[transaction.TransactionID] = transaction } // removeTransaction removes a transaction from the transactions map. @@ -82,3 +111,348 @@ func encodeResponseStatusCode(w http.ResponseWriter, data interface{}, statusCod w.WriteHeader(statusCode) _, _ = w.Write(resp) } + +func encodeK8SliceSelector(selector models.K8SliceSelector) (string, error) { + var values string + + if selector.Architecture != nil { + klog.Info("Encoding Architecture") + architectureEncoded := encodeStringFilter(reflect.ValueOf(selector.Architecture)) + for key, value := range architectureEncoded { + values += fmt.Sprintf("filter[architecture]%s=%s&", key, value) + } + } + if selector.CPU != nil { + klog.Info("Encoding CPU") + cpuEncoded := encoderResourceQuantityFilter(reflect.ValueOf(selector.CPU)) + for key, value := range cpuEncoded { + values += fmt.Sprintf("filter[cpu]%s=%s&", key, value) + } + } + if selector.Memory != nil { + klog.Info("Encoding Memory") + memoryEncoded := encoderResourceQuantityFilter(reflect.ValueOf(selector.Memory)) + for key, value := range memoryEncoded { + values += fmt.Sprintf("filter[memory]%s=%s&", key, value) + } + } + if selector.Pods != nil { + klog.Info("Encoding Pods") + podsEncoded := encoderResourceQuantityFilter(reflect.ValueOf(selector.Pods)) + for key, value := range podsEncoded { + values += fmt.Sprintf("filter[pods]%s=%s&", key, value) + } + } + if selector.Storage != nil { + klog.Info("Encoding Storage") + storageEncoded := encoderResourceQuantityFilter(reflect.ValueOf(selector.Storage)) + for key, value := range storageEncoded { + values += fmt.Sprintf("[storage]%s=%s&", key, value) + } + } + + // Remove trailing "&" if present + if values != "" && values[len(values)-1] == '&' { + values = values[:len(values)-1] + } + + return values, nil +} + +func encoderResourceQuantityFilter(v reflect.Value) map[string]string { + filter, ok := v.Interface().(*models.ResourceQuantityFilter) + if filter == nil { + return nil + } + if !ok { + klog.Warning("Not a ResourceQuantityFilter") + return nil + } + + var values = map[string]string{} + + switch filter.Name { + case models.MatchFilter: + var matchFilter models.ResourceQuantityMatchFilter + klog.Info("MatchFilter") + if err := json.Unmarshal(filter.Data, &matchFilter); err != nil { + return nil + } + value := matchFilter.Value + values["[match]"] = value.String() + case models.RangeFilter: + var rangeFilter models.ResourceQuantityRangeFilter + klog.Info("RangeFilter") + if err := json.Unmarshal(filter.Data, &rangeFilter); err != nil { + klog.Warningf("Error unmarshaling RangeFilter: %v", err) + return nil + } + klog.Info("RangeFilter: ", rangeFilter) + if rangeFilter.Min != nil { + minValue := *rangeFilter.Min + values["[range][min]"] = minValue.String() + } + if rangeFilter.Max != nil { + maxValue := *rangeFilter.Max + values["[range][max]"] = maxValue.String() + } + default: + klog.Warningf("Unsupported filter type: %s", filter.Name) + return nil + } + return values +} + +func encodeStringFilter(v reflect.Value) map[string]string { + filter, ok := v.Interface().(*models.StringFilter) + if filter == nil { + return nil + } + if !ok { + klog.Warning("Not a StringFilter") + return nil + } + + var values = map[string]string{} + + switch filter.Name { + case models.MatchFilter: + var matchFilter models.StringMatchFilter + klog.Info("MatchFilter") + if err := json.Unmarshal(filter.Data, &matchFilter); err != nil { + return nil + } + value := matchFilter.Value + values["[match]"] = value + default: + klog.Warningf("Unsupported filter type: %s", filter.Name) + return nil + } + return values +} + +func decodeStringFilter(filterTypeName models.FilterType, + original *models.StringFilter, value []string) (*models.StringFilter, error) { + var result = models.StringFilter{} + + switch filterTypeName { + case models.MatchFilter: + // Check you can't have multiple match filters for the same resource in the same selector + if original != nil { + return nil, fmt.Errorf("multiple match filters for the same resource") + } + // Set the match filter + matchFilter := models.StringMatchFilter{ + Value: value[0], + } + // Encode the match filter to a json.RawMessage + matchFilterBytes, err := json.Marshal(matchFilter) + if err != nil { + return nil, err + } + + result = models.StringFilter{ + Name: models.MatchFilter, + Data: matchFilterBytes, + } + default: + return nil, fmt.Errorf("invalid filter type: %s", filterTypeName) + } + + klog.Infof("Decoded filter: %v", result) + klog.Infof("Decoded filter type: %v", result.Name) + + return &result, nil +} + +func decodeResourceQuantityFilter(filterTypeName models.FilterType, + original *models.ResourceQuantityFilter, parts, value []string) (*models.ResourceQuantityFilter, error) { + var result = models.ResourceQuantityFilter{} + + switch filterTypeName { + case models.MatchFilter: + // Check you can't have multiple match filters for the same resource in the same selector + if original != nil { + return nil, fmt.Errorf("multiple match filters for the same resource") + } + // Set the match filter + var matchFilter models.ResourceQuantityMatchFilter + match, err := resourceLib.ParseQuantity(value[0]) + if err != nil { + return nil, err + } + matchFilter.Value = match + // Encode the match filter to a json.RawMessage + matchFilterBytes, err := json.Marshal(matchFilter) + if err != nil { + return nil, err + } + + result = models.ResourceQuantityFilter{ + Name: models.MatchFilter, + Data: matchFilterBytes, + } + case models.RangeFilter: + // You can have multiple range filters for the same resource in the same selector, but they must be different + // Check if the filter is already set + if original != nil { + // FILTER ALREADY SET + + result = *original + + // Check if the filter is a range filter + if result.Name != models.RangeFilter { + return nil, fmt.Errorf("cannot have range filter for a resource with another filter type") + } + // The filter is a range filter + // Get the range filter + var rangeFilter models.ResourceQuantityRangeFilter + if err := json.Unmarshal(result.Data, &rangeFilter); err != nil { + return nil, err + } + // Check the value of the filter is not already set + switch parts[3][:len(parts[3])-1] { + case models.RangeMinKey: + if rangeFilter.Min != nil { + return nil, fmt.Errorf("min value already set") + } + // Set the minValue value + minValue, err := resourceLib.ParseQuantity(value[0]) + if err != nil { + return nil, err + } + rangeFilter.Min = &minValue + case models.RangeMaxKey: + if rangeFilter.Max != nil { + return nil, fmt.Errorf("max value already set") + } + // Set the maxValue value + maxValue, err := resourceLib.ParseQuantity(value[0]) + if err != nil { + return nil, err + } + rangeFilter.Max = &maxValue + default: + return nil, fmt.Errorf("invalid key format: %s", parts[3]) + } + // Encode the range filter to a json.RawMessage + rangeFilterBytes, err := json.Marshal(rangeFilter) + if err != nil { + return nil, err + } + + result.Data = rangeFilterBytes + } else { + // FILTER NOT SET + + // Set the range filter + var rangeFilter models.ResourceQuantityRangeFilter + switch parts[3][:len(parts[3])-1] { + case models.RangeMinKey: + minValue, err := resourceLib.ParseQuantity(value[0]) + if err != nil { + return nil, err + } + rangeFilter.Min = &minValue + case models.RangeMaxKey: + maxValue, err := resourceLib.ParseQuantity(value[0]) + if err != nil { + return nil, err + } + rangeFilter.Max = &maxValue + default: + return nil, fmt.Errorf("invalid key format: %s", parts[3]) + } + // Encode the range filter to a json.RawMessage + rangeFilterBytes, err := json.Marshal(rangeFilter) + if err != nil { + return nil, err + } + + result = models.ResourceQuantityFilter{ + Name: models.RangeFilter, + Data: rangeFilterBytes, + } + } + default: + return nil, fmt.Errorf("invalid filter type: %s", filterTypeName) + } + + klog.Infof("Decoded filter: %v", result) + klog.Infof("Decoded filter type: %v", result.Name) + + return &result, nil +} + +func decodeK8SliceSelector(values url.Values) (*models.K8SliceSelector, error) { + selector := models.K8SliceSelector{} + + // Define the regex pattern for the expected keys + keyPattern := regexp.MustCompile(`^filter\[(architecture|cpu|memory|pods|storage)\]\[(match|range)\](?:\[(min|max|regex)\])?$`) + + for key, value := range values { + // Check if the key matches the expected pattern + if !keyPattern.MatchString(key) { + return nil, fmt.Errorf("invalid key format: %s", key) + } + + klog.Infof("Decoding key: %s", key) + + parts := strings.Split(key, "[") + resource := parts[1][:len(parts[1])-1] + filterType := parts[2][:len(parts[2])-1] + + // Convert filterType to FilterType + var filterTypeName models.FilterType + switch filterType { + case "match": + filterTypeName = models.MatchFilter + case "range": + filterTypeName = models.RangeFilter + default: + return nil, fmt.Errorf("invalid filter type: %s", filterType) + } + + switch resource { + case "architecture": + filter, err := decodeStringFilter(filterTypeName, selector.Architecture, value) + if err != nil { + return nil, err + } + selector.Architecture = filter + klog.Infof("Selector Architecture: %v", selector.Architecture) + case "cpu": + filter, err := decodeResourceQuantityFilter(filterTypeName, selector.CPU, parts, value) + if err != nil { + return nil, err + } + selector.CPU = filter + klog.Infof("Selector CPU: %v", selector.CPU) + case "memory": + filter, err := decodeResourceQuantityFilter(filterTypeName, selector.Memory, parts, value) + if err != nil { + return nil, err + } + selector.Memory = filter + klog.Infof("Selector Memory: %v", selector.Memory) + case "pods": + filter, err := decodeResourceQuantityFilter(filterTypeName, selector.Pods, parts, value) + if err != nil { + return nil, err + } + selector.Pods = filter + klog.Infof("Selector Pods: %v", selector.Pods) + case "storage": + filter, err := decodeResourceQuantityFilter(filterTypeName, selector.Storage, parts, value) + if err != nil { + return nil, err + } + selector.Storage = filter + klog.Infof("Selector Storage: %v", selector.Storage) + default: + return nil, fmt.Errorf("invalid resource: %s", resource) + } + } + + return &selector, nil +} diff --git a/pkg/rear-controller/grpc/doc.go b/pkg/rear-controller/grpc/doc.go index dd922c6..8b3f7d2 100644 --- a/pkg/rear-controller/grpc/doc.go +++ b/pkg/rear-controller/grpc/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-controller/grpc/liqo-resource-manager.go b/pkg/rear-controller/grpc/liqo-resource-manager.go index daf8b5d..6e47f58 100644 --- a/pkg/rear-controller/grpc/liqo-resource-manager.go +++ b/pkg/rear-controller/grpc/liqo-resource-manager.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -121,6 +121,8 @@ func (s *Server) NotifyChange(ctx context.Context, req *resourcemonitors.Cluster s.subscribers.Range(func(key, value interface{}) bool { stream := value.(resourcemonitors.ResourceReader_SubscribeServer) + klog.Infof("Key: %v, Value: %v", key, value) + err = stream.Send(req) if err != nil { err = fmt.Errorf("error: error during sending a notification %w", err) diff --git a/pkg/rear-controller/grpc/service.go b/pkg/rear-controller/grpc/service.go index 2a05174..cdc74dc 100644 --- a/pkg/rear-controller/grpc/service.go +++ b/pkg/rear-controller/grpc/service.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -49,36 +49,102 @@ func getContractResourcesByClusterID(cl client.Client, clusterID string) (map[st contract := contracts.Items[0] - return mapQuantityToResourceList(contract.Spec.Partition), nil + if contract.Spec.Configuration != nil { + return addResources(make(map[string]*resource.Quantity), contract.Spec.Configuration), nil + } + + return addResourceByFlavor(make(map[string]*resource.Quantity), &contract.Spec.Flavor), nil } func multipleContractLogic(contracts []reservationv1alpha1.Contract) map[string]*resource.Quantity { resources := make(map[string]*resource.Quantity) for i := range contracts { - resources = addResources(resources, contracts[i].Spec.Partition) + if contracts[i].Spec.Configuration != nil { + resources = addResources(resources, contracts[i].Spec.Configuration) + } else { + resources = addResourceByFlavor(resources, &contracts[i].Spec.Flavor) + } } return resources } +func addResourceByFlavor(resources map[string]*resource.Quantity, flavor *nodecorev1alpha1.Flavor) map[string]*resource.Quantity { + // Parse flavor + flavorType, flavorData, err := nodecorev1alpha1.ParseFlavorType(flavor) + if err != nil { + klog.Errorf("Error when parsing flavor: %s", err) + return nil + } + switch flavorType { + case nodecorev1alpha1.TypeK8Slice: + // Force casting + k8sliceFlavor := flavorData.(nodecorev1alpha1.K8Slice) + // For each characteristic, add the resource to the map + for key, value := range mapK8SliceToResources(&k8sliceFlavor) { + if prevRes, ok := resources[key]; !ok { + resources[key] = value + } else { + prevRes.Add(*value) + resources[key] = prevRes + } + } + default: + klog.Errorf("Flavor type %s not supported", flavorType) + return nil + } + + return resources +} + // This function adds the resources of a contract to the existing resourceList. -func addResources(resources map[string]*resource.Quantity, partition *nodecorev1alpha1.Partition) map[string]*resource.Quantity { - for key, value := range mapQuantityToResourceList(partition) { - if prevRes, ok := resources[key]; !ok { - resources[key] = value - } else { - prevRes.Add(*value) - resources[key] = prevRes +func addResources(resources map[string]*resource.Quantity, configuration *nodecorev1alpha1.Configuration) map[string]*resource.Quantity { + // Parse configuration + configurationType, configurationData, err := nodecorev1alpha1.ParseConfiguration(configuration) + if err != nil { + klog.Errorf("Error when parsing configuration: %s", err) + return nil + } + switch configurationType { + case nodecorev1alpha1.TypeK8Slice: + + // Force casting + k8sliceConfiguration := configurationData.(nodecorev1alpha1.K8SliceConfiguration) + + for key, value := range mapK8SliceConfigurationToResources(&k8sliceConfiguration) { + if prevRes, ok := resources[key]; !ok { + resources[key] = value + } else { + prevRes.Add(*value) + resources[key] = prevRes + } } + default: + klog.Errorf("Configuration type %s not supported", configurationType) + return nil + } + return resources +} + +func mapK8SliceConfigurationToResources(k8SliceConfiguration *nodecorev1alpha1.K8SliceConfiguration) map[string]*resource.Quantity { + resources := make(map[string]*resource.Quantity) + resources[corev1.ResourceCPU.String()] = &k8SliceConfiguration.CPU + resources[corev1.ResourceMemory.String()] = &k8SliceConfiguration.Memory + resources[corev1.ResourcePods.String()] = &k8SliceConfiguration.Pods + if k8SliceConfiguration.Storage != nil { + resources[corev1.ResourceStorage.String()] = k8SliceConfiguration.Storage + resources[corev1.ResourceEphemeralStorage.String()] = k8SliceConfiguration.Storage } return resources } -func mapQuantityToResourceList(partition *nodecorev1alpha1.Partition) map[string]*resource.Quantity { +func mapK8SliceToResources(k8Slice *nodecorev1alpha1.K8Slice) map[string]*resource.Quantity { resources := make(map[string]*resource.Quantity) - resources[corev1.ResourceCPU.String()] = &partition.CPU - resources[corev1.ResourceMemory.String()] = &partition.Memory - resources[corev1.ResourcePods.String()] = &partition.Pods - resources[corev1.ResourceStorage.String()] = &partition.Storage - resources[corev1.ResourceEphemeralStorage.String()] = &partition.EphemeralStorage + resources[corev1.ResourceCPU.String()] = &k8Slice.Characteristics.CPU + resources[corev1.ResourceMemory.String()] = &k8Slice.Characteristics.Memory + resources[corev1.ResourcePods.String()] = &k8Slice.Characteristics.Pods + if k8Slice.Characteristics.Storage != nil { + resources[corev1.ResourceStorage.String()] = k8Slice.Characteristics.Storage + resources[corev1.ResourceEphemeralStorage.String()] = k8Slice.Characteristics.Storage + } return resources } diff --git a/pkg/rear-manager/allocation_controller.go b/pkg/rear-manager/allocation_controller.go index 49e499c..26eb703 100644 --- a/pkg/rear-manager/allocation_controller.go +++ b/pkg/rear-manager/allocation_controller.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,11 +16,13 @@ package rearmanager import ( "context" + "encoding/json" liqodiscovery "github.com/liqotech/liqo/apis/discovery/v1alpha1" discovery "github.com/liqotech/liqo/pkg/discovery" fcutils "github.com/liqotech/liqo/pkg/utils/foreignCluster" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" @@ -42,6 +44,7 @@ import ( ) // clusterRole +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations/status,verbs=get;update;patch // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations/finalizers,verbs=update @@ -88,17 +91,59 @@ func (r *AllocationReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } - if allocation.Spec.Type == nodecorev1alpha1.Node { - return r.handleNodeAllocation(ctx, req, &allocation) + // Different actions based on the type of Flavor related to the contract of the Allocation + // Get the contract related to the Allocation + contract := &reservation.Contract{} + if err := r.Client.Get(ctx, types.NamespacedName{ + Name: allocation.Spec.Contract.Name, + Namespace: allocation.Spec.Contract.Namespace, + }, contract); err != nil { + klog.Errorf("Error when getting Contract %s: %v", allocation.Spec.Contract.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting Contract") + 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 } - if allocation.Spec.Type == nodecorev1alpha1.VirtualNode && allocation.Spec.Destination == nodecorev1alpha1.Local { - return r.handleVirtualNodeAllocation(ctx, req, &allocation) + // Parse Flavor type and data + flavorTypeIdentifier, _, err := nodecorev1alpha1.ParseFlavorType(&contract.Spec.Flavor) + if err != nil { + klog.Errorf("Error when parsing Flavor %s: %v", contract.Spec.Flavor.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when parsing Flavor") + 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 } - return ctrl.Result{}, nil + switch flavorTypeIdentifier { + case nodecorev1alpha1.TypeK8Slice: + // We are handling a K8Slice type + // Check if we are the provider of the Allocation + if IsProvider(ctx, &allocation, r.Client) { + // We need to handle the Allocation as a provider + return r.handleK8SliceProviderAllocation(ctx, req, &allocation, contract) + } + // We need to handle the Allocation as a consumer + return r.handleK8SliceConsumerAllocation(ctx, req, &allocation, contract) + // TODO: handle other types of Flavor in the Contract of the Allocation + default: + // We are handling a different type + klog.Errorf("Flavor %s is not a K8Slice type", contract.Spec.Flavor.Name) + allocation.SetStatus(nodecorev1alpha1.Error, "Flavor is not a K8Slice type") + 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 + } } +// checkInitialStatus checks if the Allocation is in an initial status, if not it sets it to Inactive. func (r *AllocationReconciler) checkInitialStatus(allocation *nodecorev1alpha1.Allocation) bool { if allocation.Status.Status != nodecorev1alpha1.Active && allocation.Status.Status != nodecorev1alpha1.Reserved && @@ -110,9 +155,10 @@ func (r *AllocationReconciler) checkInitialStatus(allocation *nodecorev1alpha1.A return false } -func (r *AllocationReconciler) handleNodeAllocation(ctx context.Context, - req ctrl.Request, allocation *nodecorev1alpha1.Allocation) (ctrl.Result, error) { +func (r *AllocationReconciler) handleK8SliceProviderAllocation(ctx context.Context, + req ctrl.Request, allocation *nodecorev1alpha1.Allocation, contract *reservation.Contract) (ctrl.Result, error) { allocStatus := allocation.Status.Status + // Get the contract related to the Allocation switch allocStatus { case nodecorev1alpha1.Active: // We need to check if the ForeignCluster is still ready @@ -120,36 +166,19 @@ func (r *AllocationReconciler) handleNodeAllocation(ctx context.Context, klog.Infof("Allocation %s is active", req.NamespacedName) return ctrl.Result{}, nil 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) - fc, err := fcutils.GetForeignClusterByID(ctx, r.Client, allocation.Spec.RemoteClusterID) - // check if not found - if err != nil { - if apierrors.IsNotFound(err) { - klog.Infof("ForeignCluster %s not found", allocation.Spec.RemoteClusterID) - allocation.SetStatus(nodecorev1alpha1.Reserved, "ForeignCluster not found, peering not yet started") - } else { - klog.Errorf("Error when getting ForeignCluster %s: %v", allocation.Spec.RemoteClusterID, err) - allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting ForeignCluster") - } - 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 - } - if fcutils.IsIncomingJoined(fc) && - fcutils.IsNetworkingEstablishedOrExternal(fc) && - fcutils.IsAuthenticated(fc) && - !fcutils.IsUnpeered(fc) { - klog.Infof("ForeignCluster %s is ready, incoming peering enstablished", allocation.Spec.RemoteClusterID) - allocation.SetStatus(nodecorev1alpha1.Active, "Incoming peering ready, Allocation is now Active") + // 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) + fc, err := fcutils.GetForeignClusterByID(ctx, r.Client, contract.Spec.PeeringTargetCredentials.ClusterID) + // check if not found + if err != nil { + if apierrors.IsNotFound(err) { + klog.Infof("ForeignCluster %s not found", contract.Spec.PeeringTargetCredentials.ClusterID) + allocation.SetStatus(nodecorev1alpha1.Reserved, "ForeignCluster not found, peering not yet started") } else { - klog.Infof("ForeignCluster %s is not ready yet", allocation.Spec.RemoteClusterID) - allocation.SetStatus(nodecorev1alpha1.Reserved, "Incoming peering not yet ready, Allocation is still Reserved") + klog.Errorf("Error when getting ForeignCluster %s: %v", contract.Spec.PeeringTargetCredentials.ClusterID, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting ForeignCluster") } if err := r.updateAllocationStatus(ctx, allocation); err != nil { klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) @@ -157,6 +186,20 @@ func (r *AllocationReconciler) handleNodeAllocation(ctx context.Context, } return ctrl.Result{}, nil } + if fcutils.IsIncomingJoined(fc) && + fcutils.IsNetworkingEstablishedOrExternal(fc) && + fcutils.IsAuthenticated(fc) && + !fcutils.IsUnpeered(fc) { + klog.Infof("ForeignCluster %s is ready, incoming peering enstablished", contract.Spec.PeeringTargetCredentials.ClusterID) + allocation.SetStatus(nodecorev1alpha1.Active, "Incoming peering ready, Allocation is now Active") + } else { + klog.Infof("ForeignCluster %s is not ready yet", contract.Spec.PeeringTargetCredentials.ClusterID) + allocation.SetStatus(nodecorev1alpha1.Reserved, "Incoming peering not yet ready, Allocation is still 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 + } // We can set the Allocation to Active klog.Infof("Allocation will be used locally, we can put it in 'Active' State", req.NamespacedName) allocation.SetStatus(nodecorev1alpha1.Active, "Allocation ready, will be used locally") @@ -171,42 +214,26 @@ func (r *AllocationReconciler) handleNodeAllocation(ctx context.Context, // We need to check if the ForeignCluster is again ready return ctrl.Result{}, nil case nodecorev1alpha1.Inactive: - // Alloction Type is Node, so we need to invalidate the Flavour + // Allocation is performed by the provider, so we need to invalidate the Flavor // and eventually create a new one detaching the right Partition from the old one klog.Infof("Allocation %s is inactive", req.NamespacedName) - // Get the contract related to the Allocation - contract := &reservation.Contract{} - if err := r.Client.Get(ctx, types.NamespacedName{ - Name: allocation.Spec.Contract.Name, - Namespace: allocation.Spec.Contract.Namespace, - }, contract); err != nil { - klog.Errorf("Error when getting Contract %s: %v", allocation.Spec.Contract.Name, err) - allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting Contract") - 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, err := services.GetFlavourByID(contract.Spec.Flavour.Name, r.Client) + // Get the Flavor related to the Allocation by the Contract + flavor, err := services.GetFlavorByID(contract.Spec.Flavor.Name, r.Client) if err != nil { - klog.Errorf("Error when getting Flavour %s: %v", contract.Spec.Flavour.Name, err) - allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting Flavour") + klog.Errorf("Error when getting Flavor %s: %v", contract.Spec.Flavor.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting Flavor") 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 } - flavourCopy := flavour.DeepCopy() - flavourCopy.Spec.OptionalFields.Availability = false - klog.Infof("Updating Flavour %s: Availability %t", flavourCopy.Name, flavourCopy.Spec.OptionalFields.Availability) - // TODO: Known issue, availability will be not updated - if err := r.Client.Update(ctx, flavourCopy); err != nil { - klog.Errorf("Error when updating Flavour %s: %v", flavourCopy.Name, err) - allocation.SetStatus(nodecorev1alpha1.Error, "Error when updating Flavour") + + // Reduce the availability of the Flavor + if err := reduceFlavorAvailability(ctx, flavor, contract, r.Client); err != nil { + klog.Errorf("Error when reducing Flavor %s availability: %v", contract.Spec.Flavor.Name, err) + allocation.SetStatus(nodecorev1alpha1.Error, "Error when reducing Flavor availability") if err := r.updateAllocationStatus(ctx, allocation); err != nil { klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) return ctrl.Result{}, err @@ -214,25 +241,7 @@ func (r *AllocationReconciler) handleNodeAllocation(ctx context.Context, return ctrl.Result{}, nil } - if contract.Spec.Partition != nil { - // We need to create a new Flavour with the right Partition - flavourRes := contract.Spec.Flavour.Spec.Characteristics - allocationRes := computeResources(contract) - - 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 - } - } - + // Switch to Reserved state allocation.SetStatus(nodecorev1alpha1.Reserved, "Resources reserved") if err := r.updateAllocationStatus(ctx, allocation); err != nil { klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) @@ -245,21 +254,27 @@ func (r *AllocationReconciler) handleNodeAllocation(ctx context.Context, } } -func (r *AllocationReconciler) handleVirtualNodeAllocation(ctx context.Context, - req ctrl.Request, allocation *nodecorev1alpha1.Allocation) (ctrl.Result, error) { +func (r *AllocationReconciler) handleK8SliceConsumerAllocation(ctx context.Context, + req ctrl.Request, allocation *nodecorev1alpha1.Allocation, contract *reservation.Contract) (ctrl.Result, error) { allocStatus := allocation.Status.Status switch allocStatus { case nodecorev1alpha1.Active: + // The Allocation is active, // We need to check if the ForeignCluster is ready - fc, err := fcutils.GetForeignClusterByID(ctx, r.Client, allocation.Spec.RemoteClusterID) - // check if not found + // Get the foreign cluster related to the Allocation + fc, err := fcutils.GetForeignClusterByID(ctx, r.Client, contract.Spec.PeeringTargetCredentials.ClusterID) if err != nil { if apierrors.IsNotFound(err) { - klog.Infof("ForeignCluster %s not found", allocation.Spec.RemoteClusterID) + // The ForeignCluster is not found + // We need to roll back to the establish peering phase + klog.Infof("ForeignCluster %s not found", contract.Spec.PeeringTargetCredentials.ClusterID) + // Change the status of the Allocation to Reserved allocation.SetStatus(nodecorev1alpha1.Reserved, "ForeignCluster not found, peering not yet started") } else { - klog.Errorf("Error when getting ForeignCluster %s: %v", allocation.Spec.RemoteClusterID, err) + // Error when getting the ForeignCluster + klog.Errorf("Error when getting ForeignCluster %s: %v", contract.Spec.PeeringTargetCredentials.ClusterID, err) + // Change the status of the Allocation to Error allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting ForeignCluster") } if err := r.updateAllocationStatus(ctx, allocation); err != nil { @@ -268,14 +283,20 @@ func (r *AllocationReconciler) handleVirtualNodeAllocation(ctx context.Context, } return ctrl.Result{}, nil } + + // Check if the ForeignCluster is ready with Liqo checks if fcutils.IsOutgoingJoined(fc) && fcutils.IsAuthenticated(fc) && fcutils.IsNetworkingEstablishedOrExternal(fc) && !fcutils.IsUnpeered(fc) { - klog.Infof("ForeignCluster %s is ready, outgoing peering enstablished", allocation.Spec.RemoteClusterID) + // The ForeignCluster is ready + klog.Infof("ForeignCluster %s is ready, outgoing peering established", contract.Spec.PeeringTargetCredentials.ClusterID) + // Change the status of the Allocation to Active allocation.SetStatus(nodecorev1alpha1.Active, "Outgoing peering ready, Allocation is Active") } else { - klog.Infof("ForeignCluster %s is not ready yet", allocation.Spec.RemoteClusterID) + // The ForeignCluster is not ready + klog.Infof("ForeignCluster %s is not ready yet", contract.Spec.PeeringTargetCredentials.ClusterID) + // Change the status of the Allocation to Reserved allocation.SetStatus(nodecorev1alpha1.Active, "Outgoing peering not yet ready, Allocation is Active") } @@ -286,31 +307,21 @@ func (r *AllocationReconciler) handleVirtualNodeAllocation(ctx context.Context, return ctrl.Result{}, nil case nodecorev1alpha1.Reserved: + // The Allocation is reserved, + // We need to establish the peering with the ForeignCluster klog.Infof("Allocation %s is reserved", req.NamespacedName) - klog.Infof("Allocation %s is trying to enstablish a peering", req.NamespacedName.Name) + klog.Infof("Allocation %s is trying to establish a peering", req.NamespacedName.Name) klog.InfofDepth(1, "Allocation %s is retrieving credentials", req.NamespacedName) - // Get the contract from the allocation - contract := &reservation.Contract{} - if err := r.Client.Get(ctx, types.NamespacedName{ - Name: allocation.Spec.Contract.Name, - Namespace: allocation.Spec.Contract.Namespace, - }, contract); err != nil { - klog.Errorf("Error when getting Contract %s: %v", allocation.Spec.Contract.Name, err) - allocation.SetStatus(nodecorev1alpha1.Error, "Error when getting Contract") - 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.InfofDepth(1, "Allocation %s has retrieved contract %s from namespace %s", req.NamespacedName, contract.Name, contract.Namespace) - credentials := contract.Spec.SellerCredentials + // Get the Liqo credentials for the peering target cluster, that in this scenario is the provider + credentials := contract.Spec.PeeringTargetCredentials + // Establish peering klog.InfofDepth(1, "Allocation %s is peering with cluster %s", req.NamespacedName, credentials.ClusterName) _, err := virtualfabricmanager.PeerWithCluster(ctx, r.Client, credentials.ClusterID, credentials.ClusterName, credentials.Endpoint, credentials.Token) @@ -323,7 +334,10 @@ func (r *AllocationReconciler) handleVirtualNodeAllocation(ctx context.Context, } return ctrl.Result{}, err } + // Peering established klog.Infof("Allocation %s has started the peering with cluster %s", req.NamespacedName.Name, credentials.ClusterName) + + // Change the status of the Allocation to Active allocation.SetStatus(nodecorev1alpha1.Active, "Allocation is now Active") if err := r.updateAllocationStatus(ctx, allocation); err != nil { klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) @@ -336,6 +350,8 @@ func (r *AllocationReconciler) handleVirtualNodeAllocation(ctx context.Context, return ctrl.Result{}, nil case nodecorev1alpha1.Inactive: klog.Infof("Allocation %s is inactive", req.NamespacedName) + + // Change the status of the Allocation to Reserved allocation.SetStatus(nodecorev1alpha1.Reserved, "Allocation is now Reserved") if err := r.updateAllocationStatus(ctx, allocation); err != nil { klog.Errorf("Error when updating Allocation %s status: %v", req.NamespacedName, err) @@ -348,40 +364,143 @@ func (r *AllocationReconciler) handleVirtualNodeAllocation(ctx context.Context, } } -func computeResources(contract *reservation.Contract) *nodecorev1alpha1.Characteristics { - if contract.Spec.Partition != nil { - return &nodecorev1alpha1.Characteristics{ - Architecture: contract.Spec.Partition.Architecture, - Cpu: contract.Spec.Partition.CPU, - Memory: contract.Spec.Partition.Memory, - Pods: contract.Spec.Partition.Pods, - EphemeralStorage: contract.Spec.Partition.EphemeralStorage, - Gpu: contract.Spec.Partition.Gpu, - PersistentStorage: contract.Spec.Partition.Storage, - } +// computeK8SliceCharacteristics computes the new K8SliceCharacteristics from the origin and the partition obtained by the K8SliceConfiguration. +func computeK8SliceCharacteristics(origin *nodecorev1alpha1.K8SliceCharacteristics, + part *nodecorev1alpha1.K8SliceConfiguration) *nodecorev1alpha1.K8SliceCharacteristics { + // Compulsory fields + newCPU := origin.CPU.DeepCopy() + newMemory := origin.Memory.DeepCopy() + newPods := origin.Pods.DeepCopy() + newCPU.Sub(part.CPU) + newMemory.Sub(part.Memory) + newPods.Sub(part.Pods) + + return &nodecorev1alpha1.K8SliceCharacteristics{ + Architecture: origin.Architecture, + CPU: newCPU, + Memory: newMemory, + Pods: newPods, + Gpu: func() *nodecorev1alpha1.GPU { + switch { + case part.Gpu != nil && origin != nil: + // Gpu is present in the origin and in the partition + newGpuCores := origin.Gpu.Cores.DeepCopy() + newGpuCores.Sub(part.Gpu.Cores) + newGpuMemory := origin.Gpu.Memory.DeepCopy() + newGpuMemory.Sub(part.Gpu.Memory) + return &nodecorev1alpha1.GPU{ + Model: origin.Gpu.Model, + Cores: newGpuCores, + Memory: newGpuMemory, + } + case part.Gpu == nil && origin.Gpu != nil: + // Gpu is present in the origin but not in the partition + return origin.Gpu.DeepCopy() + default: + // Gpu is not present in the origin and in the partition + return nil + } + }(), + Storage: func() *resource.Quantity { + switch { + case part.Storage != nil && origin.Storage != nil: + // Storage is present in the origin and in the partition + newStorage := origin.Storage.DeepCopy() + newStorage.Sub(*part.Storage) + return &newStorage + case part.Storage == nil && origin.Storage != nil: + // Storage is present in the origin but not in the partition + newStorage := origin.Storage.DeepCopy() + return &newStorage + default: + // Storage is not present in the origin and in the partition + return nil + } + }(), } - return contract.Spec.Flavour.Spec.Characteristics.DeepCopy() } -func computeCharacteristics(origin, part *nodecorev1alpha1.Characteristics) *nodecorev1alpha1.Characteristics { - newCPU := origin.Cpu.DeepCopy() - newMemory := origin.Memory.DeepCopy() - newStorage := origin.PersistentStorage.DeepCopy() - newGpu := origin.Gpu.DeepCopy() - newEphemeralStorage := origin.EphemeralStorage.DeepCopy() - newCPU.Sub(part.Cpu) - newMemory.Sub(part.Memory) - newStorage.Sub(part.PersistentStorage) - newGpu.Sub(part.Gpu) - newEphemeralStorage.Sub(part.EphemeralStorage) - return &nodecorev1alpha1.Characteristics{ - Architecture: origin.Architecture, - Cpu: newCPU, - Memory: newMemory, - Gpu: newGpu, - PersistentStorage: newStorage, - EphemeralStorage: newEphemeralStorage, +// reduceFlavorAvailability reduces the availability of a Flavor and manage the creation fo new one in case of specific configuration. +func reduceFlavorAvailability(ctx context.Context, flavor *nodecorev1alpha1.Flavor, contract *reservation.Contract, r client.Client) error { + flavor.Spec.Availability = false + klog.Infof("Updating Flavor %s: Availability %t", flavor.Name, flavor.Spec.Availability) + + // TODO: check update is actually working + if err := r.Update(ctx, flavor); err != nil { + klog.Errorf("Error when updating Flavor %s: %v", flavor.Name, err) + return err } + + klog.Info("Flavor availability updated") + + if contract.Spec.Configuration != nil { + // Depending on the flavor type we may need to perform different action + // Parse Flavor to get Flavor type + flavorTypeIdentifier, flavorTypeData, err := nodecorev1alpha1.ParseFlavorType(flavor) + if err != nil { + klog.Errorf("Error when parsing Flavor %s: %v", flavor.Name, err) + return err + } + + switch flavorTypeIdentifier { + case nodecorev1alpha1.TypeK8Slice: + // We are handling a K8Slice type + // Force casting + k8SliceFlavor := flavorTypeData.(nodecorev1alpha1.K8Slice) + // Get configuration from the contract + configurationTypeIdentifier, configurationData, err := nodecorev1alpha1.ParseConfiguration(contract.Spec.Configuration) + if err != nil { + klog.Errorf("Error when parsing Configuration %s: %v", contract.Spec.Configuration.ConfigurationTypeIdentifier, err) + return err + } + if configurationTypeIdentifier != nodecorev1alpha1.TypeK8Slice { + klog.Errorf("Configuration %s is not a K8Slice type", contract.Spec.Configuration.ConfigurationTypeIdentifier, err) + return err + } + // Force casting + k8SliceConfiguration := configurationData.(nodecorev1alpha1.K8SliceConfiguration) + + // Compute new Flavor that will be created based on the configuration + // Get new K8SliceCharacteristics from the origin and the partition obtained by the K8SliceConfiguration + newCharacteristics := computeK8SliceCharacteristics(&k8SliceFlavor.Characteristics, &k8SliceConfiguration) + + // Forge new flavor type + newK8Slice := &nodecorev1alpha1.K8Slice{ + Characteristics: *newCharacteristics, + Policies: *k8SliceFlavor.Policies.DeepCopy(), + Properties: *k8SliceFlavor.Properties.DeepCopy(), + } + + // Serialize new K8Slice to fit in the FlavorType->Data field + newK8SliceBytes, err := json.Marshal(newK8Slice) + if err != nil { + klog.Errorf("Error when marshaling new K8Slice %s: %v", contract.Spec.Flavor.Name, err) + return err + } + + // Create new FlavorType + newFlavorType := &nodecorev1alpha1.FlavorType{ + TypeIdentifier: nodecorev1alpha1.TypeK8Slice, + TypeData: runtime.RawExtension{Raw: newK8SliceBytes}, + } + + newFlavor := resourceforge.ForgeFlavorFromRef(flavor, newFlavorType) + + // Create new Flavor + if err := r.Create(ctx, newFlavor); err != nil { + klog.Errorf("Error when creating Flavor %s: %v", newFlavor.Name, err) + return err + } + + klog.Infof("Flavor %s created", newFlavor.Name) + default: + klog.Errorf("Flavor type %s is not supported", flavorTypeIdentifier) + return nil + } + } + + klog.Info("No configuration found, no further action needed") + return nil } func (r *AllocationReconciler) updateAllocationStatus(ctx context.Context, allocation *nodecorev1alpha1.Allocation) error { @@ -416,7 +535,7 @@ func (r *AllocationReconciler) fcToAllocation(_ context.Context, o client.Object { NamespacedName: types.NamespacedName{ Name: *allocationName, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, }, } diff --git a/pkg/rear-manager/allocation_wh.go b/pkg/rear-manager/allocation_wh.go index 19e397c..d014727 100644 --- a/pkg/rear-manager/allocation_wh.go +++ b/pkg/rear-manager/allocation_wh.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-manager/doc.go b/pkg/rear-manager/doc.go index 0012d95..e861d7d 100644 --- a/pkg/rear-manager/doc.go +++ b/pkg/rear-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rear-manager/solver_controller.go b/pkg/rear-manager/solver_controller.go index 3f5eef9..ca7875b 100644 --- a/pkg/rear-manager/solver_controller.go +++ b/pkg/rear-manager/solver_controller.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package rearmanager import ( "context" + "encoding/json" fcutils "github.com/liqotech/liqo/pkg/utils/foreignCluster" "k8s.io/apimachinery/pkg/api/errors" @@ -52,9 +53,9 @@ type SolverReconciler struct { // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=solvers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=solvers/status,verbs=get;update;patch // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=solvers/finalizers,verbs=update -// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavours/finalizers,verbs=update +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=flavors/finalizers,verbs=update // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations/status,verbs=get;update;patch // +kubebuilder:rbac:groups=nodecore.fluidos.eu,resources=allocations/finalizers,verbs=update @@ -105,6 +106,7 @@ func (r *SolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr findCandidateStatus := solver.Status.FindCandidate reserveAndBuyStatus := solver.Status.ReserveAndBuy + peeringStatus := solver.Status.Peering // Check if the Solver has expired or failed, in this case do nothing and return if solver.Status.SolverPhase.Phase == nodecorev1alpha1.PhaseFailed || @@ -142,13 +144,14 @@ func (r *SolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, nil } - if solver.Spec.EnstablishPeering { - if reserveAndBuyStatus == nodecorev1alpha1.PhaseSolved && findCandidateStatus == nodecorev1alpha1.PhaseSolved { + if solver.Spec.EstablishPeering { + if reserveAndBuyStatus == nodecorev1alpha1.PhaseSolved && findCandidateStatus == nodecorev1alpha1.PhaseSolved && + peeringStatus != nodecorev1alpha1.PhaseSolved { return r.handlePeering(ctx, req, &solver) } } else { - klog.Infof("Solver %s Solved : No need to enstablish a peering", req.NamespacedName.Name) - solver.SetPhase(nodecorev1alpha1.PhaseSolved, "No need to enstablish a peering") + klog.Infof("Solver %s Solved : No need to establish a peering", req.NamespacedName.Name) + solver.SetPhase(nodecorev1alpha1.PhaseSolved, "No need to establish a peering") err := r.updateSolverStatus(ctx, &solver) if err != nil { klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) @@ -202,6 +205,8 @@ func (r *SolverReconciler) handleFindCandidate(ctx context.Context, req ctrl.Req } // If no PeeringCandidate is available, Create a Discovery klog.Infof("Solver %s has not found any candidate. Trying a Discovery", req.NamespacedName.Name) + + // Change the status of the Solver to Running solver.SetFindCandidateStatus(nodecorev1alpha1.PhaseRunning) solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Solver is trying a Discovery") @@ -214,7 +219,7 @@ func (r *SolverReconciler) handleFindCandidate(ctx context.Context, req ctrl.Req case nodecorev1alpha1.PhaseRunning: // Check solver expiration - if tools.CheckExpiration(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { + if tools.CheckExpirationSinceTime(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { klog.Infof("Solver %s has expired", req.NamespacedName.Name) solver.SetPhase(nodecorev1alpha1.PhaseTimeout, "Solver has expired before finding a candidate") @@ -264,7 +269,7 @@ func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Req reserveAndBuyStatus := solver.Status.ReserveAndBuy switch reserveAndBuyStatus { case nodecorev1alpha1.PhaseIdle: - var partition *nodecorev1alpha1.Partition + var configuration *nodecorev1alpha1.Configuration klog.Infof("Creating the Reservation %s", req.NamespacedName.Name) var pcList []advertisementv1alpha1.PeeringCandidate pc := advertisementv1alpha1.PeeringCandidate{} @@ -299,14 +304,55 @@ func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Req if solver.Spec.Selector != nil { // Forge the Partition - partition = resourceforge.ForgePartition(solver.Spec.Selector) + + // 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 } // Get the NodeIdentity nodeIdentity := getters.GetNodeIdentity(ctx, r.Client) // Forge the Reservation - reservation := resourceforge.ForgeReservation(&pc, partition, *nodeIdentity) + 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 @@ -323,7 +369,7 @@ func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Req return ctrl.Result{}, nil case nodecorev1alpha1.PhaseRunning: // Check solver expiration - if tools.CheckExpiration(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { + if tools.CheckExpirationSinceTime(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { klog.Infof("Solver %s has expired", req.NamespacedName.Name) solver.SetPhase(nodecorev1alpha1.PhaseTimeout, "Solver has expired before reserving the resources") @@ -335,7 +381,7 @@ func (r *SolverReconciler) handleReserveAndBuy(ctx context.Context, req ctrl.Req } reservation := &reservationv1alpha1.Reservation{} - resNamespaceName := types.NamespacedName{Name: namings.ForgeReservationName(solver.Name), Namespace: flags.FluidoNamespace} + resNamespaceName := types.NamespacedName{Name: namings.ForgeReservationName(solver.Name), Namespace: flags.FluidosNamespace} // Get the Reservation err := r.Get(ctx, resNamespaceName, reservation) @@ -379,7 +425,7 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, peeringStatus := solver.Status.Peering switch peeringStatus { case nodecorev1alpha1.PhaseIdle: - klog.Infof("Solver %s is trying to enstablish a peering", req.NamespacedName.Name) + 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} contract := reservationv1alpha1.Contract{} @@ -388,8 +434,11 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, klog.Errorf("Error when getting Contract for Solver %s: %s", solver.Name, err) return ctrl.Result{}, err } - vnName := namings.ForgeVirtualNodeName(contract.Spec.SellerCredentials.ClusterName) - allocation := resourceforge.ForgeAllocation(&contract, solver.Name, vnName, nodecorev1alpha1.Local, nodecorev1alpha1.VirtualNode) + // VirtualNode Name is not used in the current implementation + vnName := namings.ForgeVirtualNodeName(contract.Spec.PeeringTargetCredentials.ClusterName) + klog.Infof("Virtual Node Name: %s", vnName) + + allocation := resourceforge.ForgeAllocation(&contract, solver.Name) 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 @@ -401,7 +450,7 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, } solver.SetPeeringStatus(nodecorev1alpha1.PhaseRunning) solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Allocation created") - solver.Status.Credentials = contract.Spec.SellerCredentials + 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 @@ -415,23 +464,25 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, if err != nil { if errors.IsNotFound(err) { klog.Infof("ForeignCluster %s not found", solver.Status.Credentials.ClusterID) - } else { - klog.Errorf("Error when getting ForeignCluster %s: %v", solver.Status.Credentials.ClusterID, err) - solver.SetPeeringStatus(nodecorev1alpha1.PhaseFailed) + // Retry later + return ctrl.Result{Requeue: true}, nil } + klog.Errorf("Error when getting ForeignCluster %s: %v", solver.Status.Credentials.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) return ctrl.Result{}, err } return ctrl.Result{}, nil } + klog.Infof("ForeignCluster %s found", solver.Status.Credentials.ClusterID) if fcutils.IsOutgoingJoined(fc) && fcutils.IsAuthenticated(fc) && fcutils.IsNetworkingEstablishedOrExternal(fc) && !fcutils.IsUnpeered(fc) { - klog.Infof("Solver %s has enstablished a peering", req.NamespacedName.Name) + klog.Infof("Solver %s has established a peering", req.NamespacedName.Name) solver.SetPeeringStatus(nodecorev1alpha1.PhaseSolved) - solver.SetPhase(nodecorev1alpha1.PhaseSolved, "Solver has enstablished a peering") + solver.SetPhase(nodecorev1alpha1.PhaseSolved, "Solver has established a peering") if err := r.updateSolverStatus(ctx, solver); err != nil { klog.Errorf("Error when updating Solver %s status: %s", req.NamespacedName, err) return ctrl.Result{}, err @@ -440,7 +491,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.CheckExpiration(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { + if tools.CheckExpirationSinceTime(solver.Status.SolverPhase.LastChangeTime, flags.ExpirationPhaseRunning) { klog.Infof("Solver %s has expired", req.NamespacedName.Name) solver.SetPhase(nodecorev1alpha1.PhaseTimeout, "Solver has expired before reserving the resources") solver.SetPeeringStatus(nodecorev1alpha1.PhaseFailed) @@ -450,15 +501,18 @@ func (r *SolverReconciler) handlePeering(ctx context.Context, req ctrl.Request, } return ctrl.Result{}, nil } - return ctrl.Result{}, nil + return ctrl.Result{Requeue: true}, nil case nodecorev1alpha1.PhaseFailed: - klog.Infof("Solver %s has failed to enstablish a peering", req.NamespacedName.Name) - solver.SetPhase(nodecorev1alpha1.PhaseFailed, "Solver has failed to enstablish a peering") + klog.Infof("Solver %s has failed to establish a peering", req.NamespacedName.Name) + solver.SetPhase(nodecorev1alpha1.PhaseFailed, "Solver has failed to establish a peering") 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.PhaseSolved: + klog.Infof("Solver %s has established a peering", req.NamespacedName.Name) + return ctrl.Result{}, nil default: solver.SetPeeringStatus(nodecorev1alpha1.PhaseIdle) // Update the Solver status @@ -521,9 +575,9 @@ func (r *SolverReconciler) selectAndBookPeeringCandidate( 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 - } } @@ -536,7 +590,7 @@ func (r *SolverReconciler) createOrGetDiscovery(ctx context.Context, solver *nod // Get the Discovery if err := r.Get(ctx, types.NamespacedName{Name: namings.ForgeDiscoveryName(solver.Name), - Namespace: flags.FluidoNamespace}, discovery); client.IgnoreNotFound(err) != nil { + Namespace: flags.FluidosNamespace}, discovery); client.IgnoreNotFound(err) != nil { klog.Errorf("Error when getting Discovery for Solver %s: %s", solver.Name, err) return nil, err } else if err != nil { @@ -566,7 +620,7 @@ func (r *SolverReconciler) SetupWithManager(mgr ctrl.Manager) error { ), builder.WithPredicates(reservationPredicate())). Watches(&nodecorev1alpha1.Allocation{}, handler.EnqueueRequestsFromMapFunc( r.allocationToSolver, - ), builder.WithPredicates(allocationPredicate())). + ), builder.WithPredicates(allocationPredicate(mgr.GetClient()))). Complete(r) } @@ -588,12 +642,12 @@ func reservationPredicate() predicate.Predicate { } } -func allocationPredicate() predicate.Predicate { +func allocationPredicate(c client.Client) predicate.Predicate { return predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { return (e.ObjectNew.(*nodecorev1alpha1.Allocation).Status.Status == nodecorev1alpha1.Active || e.ObjectNew.(*nodecorev1alpha1.Allocation).Status.Status == nodecorev1alpha1.Released) && - e.ObjectNew.(*nodecorev1alpha1.Allocation).Spec.Type == nodecorev1alpha1.VirtualNode && + !IsProvider(context.Background(), e.ObjectNew.(*nodecorev1alpha1.Allocation), c) && e.ObjectNew.(*nodecorev1alpha1.Allocation).Spec.IntentID != "" }, } @@ -605,7 +659,7 @@ func (r *SolverReconciler) discoveryToSolver(_ context.Context, o client.Object) { NamespacedName: types.NamespacedName{ Name: solverName, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, }, } @@ -617,7 +671,7 @@ func (r *SolverReconciler) reservationToSolver(_ context.Context, o client.Objec { NamespacedName: types.NamespacedName{ Name: solverName, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, }, } @@ -629,8 +683,34 @@ func (r *SolverReconciler) allocationToSolver(_ context.Context, o client.Object { NamespacedName: types.NamespacedName{ Name: solverName, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, }, } } + +// IsProvider returns true if the allocation is a provider allocation base on the contract reference. +func IsProvider(ctx context.Context, allocation *nodecorev1alpha1.Allocation, c client.Client) bool { + // Get the contract reference + contractRef := allocation.Spec.Contract + // Get the contract + contract := &reservationv1alpha1.Contract{} + if err := c.Get(ctx, types.NamespacedName{ + Namespace: contractRef.Namespace, + Name: contractRef.Name, + }, contract); err != nil { + return false + } + + // Get the seller information + seller := contract.Spec.Seller + + ni := getters.GetNodeIdentity(ctx, c) + + // Check if the seller is the same as the local node + if seller.NodeID != ni.NodeID || seller.IP != ni.IP || seller.Domain != ni.Domain { + return false + } + + return true +} diff --git a/pkg/utils/common/common.go b/pkg/utils/common/common.go index efb99dc..e4926b0 100644 --- a/pkg/utils/common/common.go +++ b/pkg/utils/common/common.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,8 +15,11 @@ package common import ( + "encoding/json" "fmt" + "regexp" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/klog/v2" advertisementv1alpha1 "github.com/fluidos-project/node/apis/advertisement/v1alpha1" @@ -27,150 +30,219 @@ import ( "github.com/fluidos-project/node/pkg/utils/parseutil" ) -// FilterFlavoursBySelector returns the Flavour CRs in the cluster that match the selector. -func FilterFlavoursBySelector(flavours []nodecorev1alpha1.Flavour, selector *models.Selector) ([]nodecorev1alpha1.Flavour, error) { - var flavoursSelected []nodecorev1alpha1.Flavour - - // Get the Flavours that match the selector - for i := range flavours { - f := flavours[i] - if string(f.Spec.Type) == selector.FlavourType { - // filter function - if FilterFlavour(selector, &f) { - flavoursSelected = append(flavoursSelected, f) +// FilterFlavorsBySelector returns the Flavor CRs in the cluster that match the selector. +func FilterFlavorsBySelector(flavors []nodecorev1alpha1.Flavor, selector models.Selector) ([]nodecorev1alpha1.Flavor, error) { + var flavorsSelected []nodecorev1alpha1.Flavor + + klog.Infof("Filtering flavors by selector: %v", selector) + klog.Infof("Selector type: %s", selector.GetSelectorType()) + + // Map the selector flavor type to the FlavorTypeIdentifier + selectorType := models.MapFromFlavorTypeName(selector.GetSelectorType()) + + // Get the Flavors that match the selector + for i := range flavors { + f := flavors[i] + if f.Spec.FlavorType.TypeIdentifier == selectorType { + klog.Infof("Flavor type matches selector type, which is %s", selector.GetSelectorType()) + if FilterFlavor(selector, &f) { + flavorsSelected = append(flavorsSelected, f) } } } - return flavoursSelected, nil + return flavorsSelected, nil } -// FilterFlavour filters the Flavour CRs in the cluster that match the selector. -func FilterFlavour(selector *models.Selector, f *nodecorev1alpha1.Flavour) bool { - if f.Spec.Characteristics.Architecture != selector.Architecture { - klog.Infof("Flavour %s has different architecture: %s - Selector: %s", f.Name, f.Spec.Characteristics.Architecture, selector.Architecture) +// FilterFlavor returns true if the Flavor CR fits the selector. +func FilterFlavor(selector models.Selector, flavorCR *nodecorev1alpha1.Flavor) bool { + flavorTypeIdentifier, flavorTypeData, err := nodecorev1alpha1.ParseFlavorType(flavorCR) + + if err != nil { + klog.Errorf("error parsing flavor type: %v", err) return false } - if selector.MatchSelector != nil { - if !filterByMatchSelector(selector, f) { + switch flavorTypeIdentifier { + case nodecorev1alpha1.TypeK8Slice: + // Check if selector type matches flavor type + if selector.GetSelectorType() != models.K8SliceNameDefault { + klog.Errorf("selector type %s does not match flavor type %s", selector.GetSelectorType(), models.K8SliceNameDefault) return false } + // Cast the selector to a K8Slice selector + k8sliceSelector := selector.(models.K8SliceSelector) + // Cast the flavor type data to a K8Slice CR + flavorTypeCR := flavorTypeData.(nodecorev1alpha1.K8Slice) + return filterFlavorK8Slice(&k8sliceSelector, &flavorTypeCR) + // TODO: Implement other flavor types filtering + default: + // Flavor type not supported + klog.Errorf("flavor type %s not supported", flavorCR.Spec.FlavorType.TypeIdentifier) + return false } +} - if selector.RangeSelector != nil && selector.MatchSelector == nil { - if !filterByRangeSelector(selector, f) { +func filterResourceQuantityFilter(selectorValue resource.Quantity, filter models.ResourceQuantityFilter) bool { + switch filter.Name { + case models.MatchFilter: + // Parse the filter to a match filter + var matchFilter models.ResourceQuantityMatchFilter + err := json.Unmarshal(filter.Data, &matchFilter) + if err != nil { + klog.Errorf("Error unmarshalling match filter: %v", err) + return false + } + // Check if the selector value matches the filter value + if selectorValue.Cmp(matchFilter.Value) != 0 { + klog.Infof("Match Filter: %d - Selector Value: %d", matchFilter.Value, selectorValue) + return false + } + case models.RangeFilter: + // Parse the filter to a range filter + var rangeFilter models.ResourceQuantityRangeFilter + err := json.Unmarshal(filter.Data, &rangeFilter) + if err != nil { + klog.Errorf("Error unmarshalling range filter: %v", err) return false } + // Check if the selector value is within the range + // If the rangeFilter.Min exists check if the selector value is greater or equal to it + if rangeFilter.Min != nil { + if selectorValue.Cmp(*rangeFilter.Min) < 0 { + klog.Infof("Range Filter: %d-%d - Selector Value: %d", rangeFilter.Min, rangeFilter.Max, selectorValue) + return false + } + } + // If the rangeFilter.Max exists check if the selector value is less or equal to it + if rangeFilter.Max != nil { + if selectorValue.Cmp(*rangeFilter.Max) > 0 { + klog.Infof("Range Filter: %d-%d - Selector Value: %d", rangeFilter.Min, rangeFilter.Max, selectorValue) + return false + } + } } - return true } -func filterByMatchSelector(selector *models.Selector, f *nodecorev1alpha1.Flavour) bool { - if selector.MatchSelector.CPU.CmpInt64(0) == 0 && f.Spec.Characteristics.Cpu.Cmp(selector.MatchSelector.CPU) != 0 { - klog.Infof("MatchSelector Cpu: %d - Flavour Cpu: %d", selector.MatchSelector.CPU, f.Spec.Characteristics.Cpu) - return false - } - - if selector.MatchSelector.Memory.CmpInt64(0) == 0 && f.Spec.Characteristics.Memory.Cmp(selector.MatchSelector.Memory) != 0 { - klog.Infof("MatchSelector Memory: %d - Flavour Memory: %d", selector.MatchSelector.Memory, f.Spec.Characteristics.Memory) - return false - } - - if selector.MatchSelector.Pods.CmpInt64(0) == 0 && f.Spec.Characteristics.Pods.Cmp(selector.MatchSelector.Pods) != 0 { - klog.Infof("MatchSelector Pods: %d - Flavour Pods: %d", selector.MatchSelector.Pods, f.Spec.Characteristics.Pods) - return false - } - - if selector.MatchSelector.EphemeralStorage.CmpInt64(0) == 0 && - f.Spec.Characteristics.EphemeralStorage.Cmp(selector.MatchSelector.EphemeralStorage) != 0 { - klog.Infof("MatchSelector EphemeralStorage: %d - Flavour EphemeralStorage: %d", - selector.MatchSelector.EphemeralStorage, f.Spec.Characteristics.EphemeralStorage) - return false - } - - if selector.MatchSelector.Storage.CmpInt64(0) == 0 && f.Spec.Characteristics.PersistentStorage.Cmp(selector.MatchSelector.Storage) != 0 { - klog.Infof("MatchSelector Storage: %d - Flavour Storage: %d", selector.MatchSelector.Storage, f.Spec.Characteristics.PersistentStorage) - return false - } - - if selector.MatchSelector.Gpu.CmpInt64(0) == 0 && f.Spec.Characteristics.Gpu.Cmp(selector.MatchSelector.Gpu) != 0 { - klog.Infof("MatchSelector GPU: %d - Flavour GPU: %d", selector.MatchSelector.Gpu, f.Spec.Characteristics.Gpu) +func filterStringFilter(selectorValue string, filter models.StringFilter) bool { + switch filter.Name { + case models.MatchFilter: + // Parse the filter to a match filter + var matchFilter models.StringMatchFilter + err := json.Unmarshal(filter.Data, &matchFilter) + if err != nil { + klog.Errorf("Error unmarshalling match filter: %v", err) + return false + } + // Check if the selector value matches the filter value + if selectorValue != matchFilter.Value { + klog.Infof("Match Filter: %s - Selector Value: %s", matchFilter.Value, selectorValue) + return false + } + case models.RangeFilter: + // Parse the filter to a range filter + var rangeFilter models.StringRangeFilter + err := json.Unmarshal(filter.Data, &rangeFilter) + if err != nil { + klog.Errorf("Error unmarshalling range filter: %v", err) + return false + } + // Check if the selector value matches the regex + match, err := regexp.MatchString(rangeFilter.Regex, selectorValue) + if err != nil { + klog.Errorf("Error matching regex: %v", err) + return false + } + if !match { + klog.Infof("Range Filter: %s - Selector Value: %s", rangeFilter.Regex, selectorValue) + return false + } + default: + klog.Errorf("Filter name %s not supported", filter.Name) return false } return true } -func filterByRangeSelector(selector *models.Selector, f *nodecorev1alpha1.Flavour) bool { - if selector.RangeSelector.MinCPU.CmpInt64(0) != 0 && f.Spec.Characteristics.Cpu.Cmp(selector.RangeSelector.MinCPU) < 0 { - klog.Infof("RangeSelector MinCpu: %d - Flavour Cpu: %d", selector.RangeSelector.MinCPU, f.Spec.Characteristics.Cpu) - return false - } - - if selector.RangeSelector.MinMemory.CmpInt64(0) != 0 && f.Spec.Characteristics.Memory.Cmp(selector.RangeSelector.MinMemory) < 0 { - klog.Infof("RangeSelector MinMemory: %d - Flavour Memory: %d", selector.RangeSelector.MinMemory, f.Spec.Characteristics.Memory) - return false - } - - if selector.RangeSelector.MinPods.CmpInt64(0) != 0 && f.Spec.Characteristics.Pods.Cmp(selector.RangeSelector.MinPods) < 0 { - klog.Infof("RangeSelector MinPods: %d - Flavour Pods: %d", selector.RangeSelector.MinPods, f.Spec.Characteristics.Pods) - return false - } - - if selector.RangeSelector.MinEph.CmpInt64(0) != 0 && f.Spec.Characteristics.EphemeralStorage.Cmp(selector.RangeSelector.MinEph) < 0 { - klog.Infof("RangeSelector MinEph: %d - Flavour EphemeralStorage: %d", selector.RangeSelector.MinEph, f.Spec.Characteristics.EphemeralStorage) - return false - } - - if selector.RangeSelector.MinStorage.CmpInt64(0) != 0 && f.Spec.Characteristics.PersistentStorage.Cmp(selector.RangeSelector.MinStorage) < 0 { - klog.Infof("RangeSelector MinStorage: %d - Flavour Storage: %d", selector.RangeSelector.MinStorage, f.Spec.Characteristics.PersistentStorage) - return false +// filterFlavorK8Slice return true if the K8Slice Flavor CR fits the K8Slice selector. +func filterFlavorK8Slice(k8SliceSelector *models.K8SliceSelector, flavorTypeK8SliceCR *nodecorev1alpha1.K8Slice) bool { + // Architecture Filter + if k8SliceSelector.Architecture != nil { + // Check if the flavor matches the Architecture filter + architectureFilterModel := *k8SliceSelector.Architecture + if !filterStringFilter(flavorTypeK8SliceCR.Characteristics.Architecture, architectureFilterModel) { + return false + } } - - if selector.RangeSelector.MinGpu.CmpInt64(0) != 0 && f.Spec.Characteristics.Gpu.Cmp(selector.RangeSelector.MinGpu) < 0 { - return false + // CPU Filter + if k8SliceSelector.CPU != nil { + // Check if the flavor matches the CPU filter + cpuFilterModel := *k8SliceSelector.CPU + if !filterResourceQuantityFilter(flavorTypeK8SliceCR.Characteristics.CPU, cpuFilterModel) { + return false + } } - if selector.RangeSelector.MaxCPU.CmpInt64(0) != 0 && f.Spec.Characteristics.Cpu.Cmp(selector.RangeSelector.MaxCPU) > 0 { - return false + // Memory Filter + if k8SliceSelector.Memory != nil { + // Check if the flavor matches the Memory filter + memoryFilterModel := *k8SliceSelector.Memory + if !filterResourceQuantityFilter(flavorTypeK8SliceCR.Characteristics.Memory, memoryFilterModel) { + return false + } } - if selector.RangeSelector.MaxMemory.CmpInt64(0) != 0 && f.Spec.Characteristics.Memory.Cmp(selector.RangeSelector.MaxMemory) > 0 { - return false + // Pods Filter + if k8SliceSelector.Pods != nil { + // Check if the flavor matches the Pods filter + podsFilterModel := *k8SliceSelector.Pods + if !filterResourceQuantityFilter(flavorTypeK8SliceCR.Characteristics.Pods, podsFilterModel) { + return false + } } - if selector.RangeSelector.MaxPods.CmpInt64(0) != 0 && f.Spec.Characteristics.Pods.Cmp(selector.RangeSelector.MaxPods) > 0 { - return false + // Storage Filter + if k8SliceSelector.Storage != nil { + // Check if the flavor matches the Storage filter + storageFilterModel := *k8SliceSelector.Storage + if !filterResourceQuantityFilter(*flavorTypeK8SliceCR.Characteristics.Storage, storageFilterModel) { + return false + } } - if selector.RangeSelector.MaxEph.CmpInt64(0) != 0 && f.Spec.Characteristics.EphemeralStorage.Cmp(selector.RangeSelector.MaxEph) > 0 { - return false - } + return true +} - if selector.RangeSelector.MaxStorage.CmpInt64(0) != 0 && f.Spec.Characteristics.PersistentStorage.Cmp(selector.RangeSelector.MaxStorage) > 0 { - return false +// FilterPeeringCandidate filters the peering candidate based on the solver's flavor selector. +func FilterPeeringCandidate(selector *nodecorev1alpha1.Selector, pc *advertisementv1alpha1.PeeringCandidate) bool { + // Parsing the selector + if selector == nil { + klog.Infof("No selector provided") + return true } - - if selector.RangeSelector.MaxGpu.CmpInt64(0) != 0 && f.Spec.Characteristics.Gpu.Cmp(selector.RangeSelector.MaxGpu) > 0 { + s, err := parseutil.ParseFlavorSelector(selector) + if err != nil { + klog.Errorf("Error parsing selector: %v", err) return false } - return true -} - -// FilterPeeringCandidate filters the peering candidate based on the solver's flavour selector. -func FilterPeeringCandidate(selector *nodecorev1alpha1.FlavourSelector, pc *advertisementv1alpha1.PeeringCandidate) bool { - s := parseutil.ParseFlavourSelector(selector) - return FilterFlavour(s, &pc.Spec.Flavour) + // Filter the peering candidate based on its flavor + return FilterFlavor(s, &pc.Spec.Flavor) } // CheckSelector ia a func to check if the syntax of the Selector is right. // Strict and range syntax cannot be used together. -func CheckSelector(selector *models.Selector) error { - if selector.MatchSelector != nil && selector.RangeSelector != nil { - return fmt.Errorf("selector syntax error: strict and range syntax cannot be used together") +func CheckSelector(selector models.Selector) error { + // Parse the selector to check the syntax + switch selector.GetSelectorType() { + case models.K8SliceNameDefault: + k8sliceSelector := selector.(models.K8SliceSelector) + klog.Infof("Checking K8Slice selector: %v", k8sliceSelector) + // Nothing is compulsory in the K8Slice selector + return nil + default: + return fmt.Errorf("selector type %s not supported", selector.GetSelectorType()) } - return nil } // SOLVER PHASE SETTERS @@ -208,19 +280,19 @@ func DiscoveryStatusCheck(solver *nodecorev1alpha1.Solver, discovery *advertisem // ReservationStatusCheck checks the status of the reservation. func ReservationStatusCheck(solver *nodecorev1alpha1.Solver, reservation *reservationv1alpha1.Reservation) { klog.Infof("Reservation %s is in phase %s", reservation.Name, reservation.Status.Phase.Phase) - flavourName := namings.RetrieveFlavourNameFromPC(reservation.Spec.PeeringCandidate.Name) + flavorName := namings.RetrieveFlavorNameFromPC(reservation.Spec.PeeringCandidate.Name) if reservation.Status.Phase.Phase == nodecorev1alpha1.PhaseSolved { - klog.Infof("Reservation %s has reserved and purchase the flavour %s", reservation.Name, flavourName) + 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: Flavour reserved and purchased") + solver.SetPhase(nodecorev1alpha1.PhaseRunning, "Reservation: Flavor reserved and purchased") } if reservation.Status.Phase.Phase == nodecorev1alpha1.PhaseFailed { klog.Infof("Reservation %s has failed. Reason: %s", reservation.Name, reservation.Status.Phase.Message) solver.Status.ReservationPhase = nodecorev1alpha1.PhaseFailed solver.Status.ReserveAndBuy = nodecorev1alpha1.PhaseFailed - solver.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation: Flavour reservation and purchase failed") + solver.SetPhase(nodecorev1alpha1.PhaseFailed, "Reservation: Flavor reservation and purchase failed") } if reservation.Status.Phase.Phase == nodecorev1alpha1.PhaseRunning { if reservation.Status.ReservePhase == nodecorev1alpha1.PhaseRunning { diff --git a/pkg/utils/common/doc.go b/pkg/utils/common/doc.go index 243015a..2a7dc30 100644 --- a/pkg/utils/common/doc.go +++ b/pkg/utils/common/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/consts/consts.go b/pkg/utils/consts/consts.go index d100640..5485a1f 100644 --- a/pkg/utils/consts/consts.go +++ b/pkg/utils/consts/consts.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/consts/doc.go b/pkg/utils/consts/doc.go index 588750f..6e5022e 100644 --- a/pkg/utils/consts/doc.go +++ b/pkg/utils/consts/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/flags/doc.go b/pkg/utils/flags/doc.go index d86d7fb..a5fa486 100644 --- a/pkg/utils/flags/doc.go +++ b/pkg/utils/flags/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/flags/flags.go b/pkg/utils/flags/flags.go index 87fe970..b8decf6 100644 --- a/pkg/utils/flags/flags.go +++ b/pkg/utils/flags/flags.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import "time" // Namespace flags. var ( - FluidoNamespace = "fluidos" + FluidosNamespace = "fluidos" ) // Expiration/Time flags. diff --git a/pkg/utils/getters/doc.go b/pkg/utils/getters/doc.go index 98b9988..df1053f 100644 --- a/pkg/utils/getters/doc.go +++ b/pkg/utils/getters/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/getters/getters.go b/pkg/utils/getters/getters.go index 28d8660..e6a4e35 100644 --- a/pkg/utils/getters/getters.go +++ b/pkg/utils/getters/getters.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,14 +31,14 @@ import ( "github.com/fluidos-project/node/pkg/utils/flags" ) -// GetNodeIdentity retrieves the list of local providers ip addresses from the Network Manager configMap. +// GetNodeIdentity retrieves the node identity from the local cluster. func GetNodeIdentity(ctx context.Context, cl client.Client) *nodecorev1alpha1.NodeIdentity { cm := &corev1.ConfigMap{} // Get the node identity err := cl.Get(ctx, types.NamespacedName{ Name: consts.NodeIdentityConfigMapName, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, cm) if err != nil { klog.Errorf("Error getting the configmap: %s", err) @@ -59,7 +59,7 @@ func GetLocalProviders(ctx context.Context, cl client.Client) []string { // Get the configmap err := cl.Get(ctx, types.NamespacedName{ Name: consts.NetworkConfigMapName, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, cm) if err != nil { klog.Errorf("Error getting the configmap: %s", err) diff --git a/pkg/utils/models/doc.go b/pkg/utils/models/doc.go index 45fc5cf..899f639 100644 --- a/pkg/utils/models/doc.go +++ b/pkg/utils/models/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/models/gateway.go b/pkg/utils/models/gateway.go index 15ffcb1..2b44e67 100644 --- a/pkg/utils/models/gateway.go +++ b/pkg/utils/models/gateway.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,21 +14,17 @@ package models -// PurchaseRequest is the request model for purchasing a Flavour. +// PurchaseRequest is the request model for purchasing a Flavor. type PurchaseRequest struct { - TransactionID string `json:"transactionID"` + // LiqoCredentials contains the Liqo credentials of the buyer. + // This field is optional and should be used only if the buyer is the Liqo peering target cluster, based on the Flavor you are going to purchase. + // This field may be dismissed in the future version of Liqo. + LiqoCredentials *LiqoCredentials `json:"liqoCredentials,omitempty"` } -// ResponsePurchase contain information after purchase a Flavour. -type ResponsePurchase struct { - Contract Contract `json:"contract"` - Status string `json:"status"` -} - -// ReserveRequest is the request model for reserving a Flavour. +// ReserveRequest is the request model for reserving a Flavor. type ReserveRequest struct { - FlavourID string `json:"flavourID"` - Buyer NodeIdentity `json:"buyerID"` - ClusterID string `json:"clusterID"` - Partition *Partition `json:"partition,omitempty"` + FlavorID string `json:"flavorID"` + Buyer NodeIdentity `json:"buyerID"` + Configuration *Configuration `json:"configuration,omitempty"` } diff --git a/pkg/utils/models/k8slice-models.go b/pkg/utils/models/k8slice-models.go new file mode 100644 index 0000000..eed02dc --- /dev/null +++ b/pkg/utils/models/k8slice-models.go @@ -0,0 +1,86 @@ +// Copyright 2022-2024 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 models + +import ( + "strings" + + "k8s.io/apimachinery/pkg/api/resource" + + nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" +) + +// GpuCharacteristics represents the characteristics of a Gpu. +type GpuCharacteristics struct { + Model string `json:"model"` + Cores resource.Quantity `json:"cores"` + Memory resource.Quantity `json:"memory"` +} + +// Cmp compares models.GpuCharacteristics with nodecorev1alpha1.GPU. +func (gpu *GpuCharacteristics) Cmp(other *nodecorev1alpha1.GPU) int { + if gpu.Model != other.Model { + return strings.Compare(gpu.Model, other.Model) + } + if cmp := gpu.Cores.Cmp(other.Cores); cmp != 0 { + return cmp + } + return gpu.Memory.Cmp(other.Memory) +} + +// K8SliceCharacteristics represents the characteristics of a Kubernetes slice. +type K8SliceCharacteristics struct { + Architecture string `json:"architecture"` + CPU resource.Quantity `json:"cpu"` + Memory resource.Quantity `json:"memory"` + Pods resource.Quantity `json:"pods"` + Gpu *GpuCharacteristics `json:"gpu,omitempty"` + Storage *resource.Quantity `json:"storage,omitempty"` +} + +// K8SliceProperties represents the properties of a Kubernetes slice. +type K8SliceProperties struct { + Latency int `json:"latency,omitempty"` + SecurityStandards []string `json:"securityStandards,omitempty"` + CarbonFootprint *CarbonFootprint `json:"carbonFootprint,omitempty"` + NetworkAuthorizations *NetworkAuthorizations `json:"networkAuthorizations,omitempty"` +} + +// K8SlicePartitionability represents the partitionability of a Kubernetes slice. +type K8SlicePartitionability struct { + CPUMin resource.Quantity `json:"cpuMin"` + MemoryMin resource.Quantity `json:"memoryMin"` + PodsMin resource.Quantity `json:"podsMin"` + CPUStep resource.Quantity `json:"cpuStep"` + MemoryStep resource.Quantity `json:"memoryStep"` + PodsStep resource.Quantity `json:"podsStep"` +} + +// K8SlicePolicies represents the policies of a Kubernetes slice. +type K8SlicePolicies struct { + Partitionability K8SlicePartitionability `json:"partitionability"` +} + +// K8Slice represents a Kubernetes slice. +type K8Slice struct { + Characteristics K8SliceCharacteristics `json:"charateristics"` + Properties K8SliceProperties `json:"properties"` + Policies K8SlicePolicies `json:"policies"` +} + +// GetFlavorTypeName returns the type of the Flavor. +func (K8Slice) GetFlavorTypeName() FlavorTypeName { + return K8SliceNameDefault +} diff --git a/pkg/utils/models/local-resource-manager.go b/pkg/utils/models/local-resource-manager.go index 8d57453..240bbc0 100644 --- a/pkg/utils/models/local-resource-manager.go +++ b/pkg/utils/models/local-resource-manager.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,6 +27,15 @@ type NodeInfo struct { ResourceMetrics ResourceMetrics `json:"resources"` } +// GPUMetrics represents GPU metrics. +type GPUMetrics struct { + Model string `json:"model"` + CoresTotal resource.Quantity `json:"totalCores"` + CoresAvailable resource.Quantity `json:"availableCores"` + MemoryTotal resource.Quantity `json:"totalMemory"` + MemoryAvailable resource.Quantity `json:"availableMemory"` +} + // ResourceMetrics represents resources of a certain node. type ResourceMetrics struct { CPUTotal resource.Quantity `json:"totalCPU"` @@ -36,4 +45,5 @@ type ResourceMetrics struct { PodsTotal resource.Quantity `json:"totalPods"` PodsAvailable resource.Quantity `json:"availablePods"` EphemeralStorage resource.Quantity `json:"ephemeralStorage"` + GPU GPUMetrics `json:"gpu"` } diff --git a/pkg/utils/models/models.go b/pkg/utils/models/models.go index c8e84f3..0cf2d28 100644 --- a/pkg/utils/models/models.go +++ b/pkg/utils/models/models.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,108 +15,229 @@ package models import ( + "encoding/json" "time" "k8s.io/apimachinery/pkg/api/resource" + + nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" +) + +// Flavor represents a Flavor object with its characteristics and policies. +type Flavor struct { + FlavorID string `json:"flavorID"` + ProviderID string `json:"providerID"` + Type FlavorType `json:"type"` + NetworkPropertyType string `json:"networkPropertyType,omitempty"` + Timestamp time.Time `json:"timestamp"` + Location *Location `json:"location,omitempty"` + Price Price `json:"price"` + Owner NodeIdentity `json:"owner"` + Availability bool `json:"availability"` +} + +const ( + // RangeMinKey is the key for the identifier of the minimum value in a range. + RangeMinKey = "min" + // RangeMaxKey is the key for the identifier of the maximum value in a range. + RangeMaxKey = "max" +) + +// FlavorType represents the type of a Flavor. +type FlavorType struct { + Name FlavorTypeName `json:"name"` + Data json.RawMessage `json:"data"` +} + +// FlavorTypeData represents the data of a FlavorType. +type FlavorTypeData interface { + GetFlavorTypeName() FlavorTypeName +} + +// FlavorTypeName represents the name of a FlavorType. +type FlavorTypeName string + +// CarbonFootprint represents the carbon footprint of a Flavor, with embodied and operational values. +type CarbonFootprint struct { + Embodied int `json:"embodied"` + Operational []int `json:"operational"` +} + +const ( + // K8SliceNameDefault is the default name for a K8Slice Flavor. + K8SliceNameDefault FlavorTypeName = "k8slice" + // VMNameDefault is the default name for a VM Flavor. + VMNameDefault FlavorTypeName = "vm" + // ServiceNameDefault is the default name for a Service Flavor. + ServiceNameDefault FlavorTypeName = "service" + // SensorNameDefault is the default name for a Sensor Flavor. + SensorNameDefault FlavorTypeName = "sensor" ) -// Flavour represents a Flavour object with its characteristics and policies. -type Flavour struct { - FlavourID string `json:"flavourID"` - ProviderID string `json:"providerID"` - Type string `json:"type"` - Characteristics Characteristics `json:"characteristics"` - Policy Policy `json:"policy"` - Owner NodeIdentity `json:"owner"` - Price Price `json:"price"` - ExpirationTime time.Time `json:"expirationTime"` - QuantityAvailable int `json:"quantityAvailable,omitempty"` - OptionalFields OptionalFields `json:"optionalFields"` -} - -// Characteristics represents the characteristics of a Flavour, such as CPU and RAM. -type Characteristics struct { - CPU resource.Quantity `json:"cpu,omitempty"` - Memory resource.Quantity `json:"memory,omitempty"` - Pods resource.Quantity `json:"pods,omitempty"` - PersistentStorage resource.Quantity `json:"storage,omitempty"` - EphemeralStorage resource.Quantity `json:"ephemeralStorage,omitempty"` - Gpu resource.Quantity `json:"gpu,omitempty"` - Architecture string `json:"architecture,omitempty"` -} - -// Policy represents the policy associated with a Flavour, which can be either Partitionable or Aggregatable. -type Policy struct { - Partitionable *Partitionable `json:"partitionable,omitempty"` - Aggregatable *Aggregatable `json:"aggregatable,omitempty"` -} - -// Partitionable represents the partitioning properties of a Flavour, such as the minimum and incremental values of CPU and RAM. -type Partitionable struct { - CPUMinimum resource.Quantity `json:"cpuMinimum"` - MemoryMinimum resource.Quantity `json:"memoryMinimum"` - PodsMinimum resource.Quantity `json:"podsMinimum"` - CPUStep resource.Quantity `json:"cpuStep"` - MemoryStep resource.Quantity `json:"memoryStep"` - PodsStep resource.Quantity `json:"podsStep"` -} - -// Aggregatable represents the aggregation properties of a Flavour, such as the minimum instance count. -type Aggregatable struct { - MinCount int `json:"minCount"` - MaxCount int `json:"maxCount"` -} - -// NodeIdentity represents the owner of a Flavour, with associated ID, IP, and domain name. +// Location represents the location of a Flavor, with latitude, longitude, altitude, and additional notes. +type Location struct { + Latitude string `json:"latitude,omitempty"` + Longitude string `json:"longitude,omitempty"` + Country string `json:"altitude,omitempty"` + City string `json:"city,omitempty"` + AdditionalNotes string `json:"additionalNotes,omitempty"` +} + +// NodeIdentityAdditionalInfo represents additional information about a NodeIdentity. +type NodeIdentityAdditionalInfo struct { + LiqoID string `json:"liqoID,omitempty"` +} + +// NodeIdentity represents the owner of a Flavor, with associated ID, IP, and domain name. type NodeIdentity struct { - NodeID string `json:"ID"` - IP string `json:"IP"` - Domain string `json:"domain"` + NodeID string `json:"ID"` + IP string `json:"IP"` + Domain string `json:"domain"` + AdditionalInformation *NodeIdentityAdditionalInfo `json:"additionalInformation,omitempty"` } -// Price represents the price of a Flavour, with the amount, currency, and period associated. +// Price represents the price of a Flavor, with the amount, currency, and period associated. type Price struct { Amount string `json:"amount"` Currency string `json:"currency"` Period string `json:"period"` } -// OptionalFields represents the optional fields of a Flavour, such as availability. -type OptionalFields struct { - Availability bool `json:"availability,omitempty"` - WorkerID string `json:"workerID"` -} - -// Selector represents the criteria for selecting Flavours. -type Selector struct { - FlavourType string `json:"type,omitempty"` - Architecture string `json:"architecture,omitempty"` - RangeSelector *RangeSelector `json:"rangeSelector,omitempty"` - MatchSelector *MatchSelector `json:"matchSelector,omitempty"` -} - -// MatchSelector represents the criteria for selecting Flavours through a strict match. -type MatchSelector struct { - CPU resource.Quantity `json:"cpu,omitempty"` - Memory resource.Quantity `json:"memory,omitempty"` - Pods resource.Quantity `json:"pods,omitempty"` - Storage resource.Quantity `json:"storage,omitempty"` - EphemeralStorage resource.Quantity `json:"ephemeralStorage,omitempty"` - Gpu resource.Quantity `json:"gpu,omitempty"` -} - -// RangeSelector represents the criteria for selecting Flavours through a range. -type RangeSelector struct { - MinCPU resource.Quantity `json:"minCpu,omitempty"` - MinMemory resource.Quantity `json:"minMemory,omitempty"` - MinPods resource.Quantity `json:"minPods,omitempty"` - MinStorage resource.Quantity `json:"minStorage,omitempty"` - MinEph resource.Quantity `json:"minEph,omitempty"` - MinGpu resource.Quantity `json:"minGpu,omitempty"` - MaxCPU resource.Quantity `json:"maxCpu,omitempty"` - MaxMemory resource.Quantity `json:"maxMemory,omitempty"` - MaxPods resource.Quantity `json:"maxPods,omitempty"` - MaxStorage resource.Quantity `json:"maxStorage,omitempty"` - MaxEph resource.Quantity `json:"maxEph,omitempty"` - MaxGpu resource.Quantity `json:"maxGpu,omitempty"` +// Selector represents the criteria for selecting Flavors. +type Selector interface { + GetSelectorType() FlavorTypeName +} + +// K8SliceSelector represents the criteria for selecting a K8Slice Flavor. +type K8SliceSelector struct { + Architecture *StringFilter `json:"architecture,omitempty"` + CPU *ResourceQuantityFilter `scheme:"cpu,omitempty"` + Memory *ResourceQuantityFilter `scheme:"memory,omitempty"` + Pods *ResourceQuantityFilter `scheme:"pods,omitempty"` + Storage *ResourceQuantityFilter `scheme:"storage,omitempty"` +} + +// GetSelectorType returns the type of the Selector. +func (ks K8SliceSelector) GetSelectorType() FlavorTypeName { + return K8SliceNameDefault +} + +// ResourceQuantityFilter represents a filter for a resource quantity. +type ResourceQuantityFilter struct { + Name FilterType `scheme:"name"` + Data json.RawMessage `scheme:"data"` +} + +// ResourceQuantityFilterData represents the data of a ResourceQuantityFilter. +type ResourceQuantityFilterData interface { + GetFilterType() FilterType +} + +// StringFilter represents a filter for a string. +type StringFilter struct { + Name FilterType `scheme:"name"` + Data json.RawMessage `scheme:"data"` +} + +// StringFilterData represents the data of a StringFilter. +type StringFilterData interface { + GetFilterType() FilterType +} + +// FilterType represents the type of a Filter. +type FilterType string + +const ( + // MatchFilter is the identifier for a match filter. + MatchFilter FilterType = "Match" + // RangeFilter is the identifier for a range filter. + RangeFilter FilterType = "Range" +) + +// ResourceQuantityMatchFilter represents a match filter for a resource quantity. +type ResourceQuantityMatchFilter struct { + Value resource.Quantity `scheme:"value"` +} + +// GetFilterType returns the type of the Filter. +func (fq ResourceQuantityMatchFilter) GetFilterType() FilterType { + return MatchFilter +} + +// ResourceQuantityRangeFilter represents a range filter for a resource quantity. +type ResourceQuantityRangeFilter struct { + Min *resource.Quantity `scheme:"min,omitempty"` + Max *resource.Quantity `scheme:"max,omitempty"` +} + +// GetFilterType returns the type of the Filter. +func (fq ResourceQuantityRangeFilter) GetFilterType() FilterType { + return RangeFilter +} + +// StringMatchFilter represents a match filter for a string. +type StringMatchFilter struct { + Value string `scheme:"value"` +} + +// GetFilterType returns the type of the Filter. +func (fq StringMatchFilter) GetFilterType() FilterType { + return MatchFilter +} + +// StringRangeFilter represents a range filter for a string. +type StringRangeFilter struct { + Regex string `scheme:"regex"` +} + +// MapToFlavorTypeName maps a nodecorev1alpha1.FlavorTypeIdentifier to a models.FlavorTypeName. +func MapToFlavorTypeName(flavorType nodecorev1alpha1.FlavorTypeIdentifier) FlavorTypeName { + switch flavorType { + case nodecorev1alpha1.TypeK8Slice: + return K8SliceNameDefault + case nodecorev1alpha1.TypeVM: + return VMNameDefault + case nodecorev1alpha1.TypeService: + return ServiceNameDefault + default: + return "" + } +} + +// MapFromFlavorTypeName maps a models.FlavorTypeName to a nodecorev1alpha1.FlavorTypeIdentifier. +func MapFromFlavorTypeName(flavorType FlavorTypeName) nodecorev1alpha1.FlavorTypeIdentifier { + switch flavorType { + case K8SliceNameDefault: + return nodecorev1alpha1.TypeK8Slice + case VMNameDefault: + return nodecorev1alpha1.TypeVM + case ServiceNameDefault: + return nodecorev1alpha1.TypeService + default: + return "" + } +} + +// MapToFilterType maps a nodecorev1alpha1.FilterType to a models.FilterType. +func MapToFilterType(filterType nodecorev1alpha1.FilterType) FilterType { + switch filterType { + case nodecorev1alpha1.TypeMatchFilter: + return MatchFilter + case nodecorev1alpha1.TypeRangeFilter: + return RangeFilter + default: + return "" + } +} + +// MapFromFilterType maps a models.FilterType to a nodecorev1alpha1.FilterType. +func MapFromFilterType(filterType FilterType) nodecorev1alpha1.FilterType { + switch filterType { + case MatchFilter: + return nodecorev1alpha1.TypeMatchFilter + case RangeFilter: + return nodecorev1alpha1.TypeRangeFilter + default: + return "" + } } diff --git a/pkg/utils/models/network-identity-models.go b/pkg/utils/models/network-identity-models.go new file mode 100644 index 0000000..04c6d36 --- /dev/null +++ b/pkg/utils/models/network-identity-models.go @@ -0,0 +1,68 @@ +// Copyright 2022-2024 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 models + +import "encoding/json" + +// ResourceSelectorIdentifier represents the identifier of a ResourceSelector. +type ResourceSelectorIdentifier string + +const ( + // PodNamespaceSelectorType is the type of a PodNamespaceSelector. + PodNamespaceSelectorType ResourceSelectorIdentifier = "PodNamespaceSelector" + // CIDRSelectorType is the type of a CIDRSelector. + CIDRSelectorType ResourceSelectorIdentifier = "CIDRSelector" +) + +// NetworkIntent represents the network intent of a Flavor, with source and destination. +type NetworkIntent struct { + Name string `json:"name"` + Source SourceDestination `json:"source"` + Destination SourceDestination `json:"destination"` + DestinationPort string `json:"destinationPort"` + ProtocolType string `json:"protocolType"` +} + +// NetworkAuthorizations represents the network authorizations of a Flavor, with denied and mandatory communications. +type NetworkAuthorizations struct { + DeniedCommunications []NetworkIntent `json:"deniedCommunications"` + MandatoryCommunications []NetworkIntent `json:"mandatoryCommunications"` +} + +// SourceDestination represents the source or destination of a network intent. +type SourceDestination struct { + IsHotCluster bool `json:"isHotCluster"` + ResourceSelector ResourceSelector `json:"resourceSelectors"` +} + +// ResourceSelector represents the resource selector of a SourceDestination. +type ResourceSelector struct { + TypeIdentifier ResourceSelectorIdentifier `json:"typeIdentifier"` + Selector json.RawMessage `json:"selector"` +} + +// CIDRSelector represents the CIDR selector of a SourceDestination. +type CIDRSelector string + +// PodNamespaceSelector represents the pod namespace selector of a SourceDestination. +type PodNamespaceSelector struct { + Pod []keyValuePair `json:"pod"` + Namespace []keyValuePair `json:"namespace"` +} + +type keyValuePair struct { + Key string `json:"key"` + Value string `json:"value"` +} diff --git a/pkg/utils/models/reservation.go b/pkg/utils/models/reservation.go index 18d9577..f4c9dd7 100644 --- a/pkg/utils/models/reservation.go +++ b/pkg/utils/models/reservation.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,44 +14,63 @@ package models -import "k8s.io/apimachinery/pkg/api/resource" - -// Partition represents the partitioning properties of a Flavour. -type Partition struct { - Architecture string `json:"architecture"` - CPU resource.Quantity `json:"cpu"` - Memory resource.Quantity `json:"memory"` - Pods resource.Quantity `json:"pods"` - EphemeralStorage resource.Quantity `json:"ephemeral-storage,omitempty"` - Gpu resource.Quantity `json:"gpu,omitempty"` - Storage resource.Quantity `json:"storage,omitempty"` +import ( + "encoding/json" + + "k8s.io/apimachinery/pkg/api/resource" +) + +// Configuration represents the configuration properties of a Flavor. +type Configuration struct { + Type FlavorTypeName `json:"type"` + Data json.RawMessage `json:"data"` +} + +// ConfigurationData represents the data of a Configuration. +type ConfigurationData interface { + GetConfigurationType() FlavorTypeName +} + +// K8SliceConfiguration represents the configuration properties of a K8Slice Flavor. +type K8SliceConfiguration struct { + CPU resource.Quantity `json:"cpu"` + Memory resource.Quantity `json:"memory"` + Pods resource.Quantity `json:"pods"` + Gpu *GpuCharacteristics `json:"gpu,omitempty"` + Storage *resource.Quantity `json:"storage,omitempty"` +} + +// GetConfigurationType returns the type of the Configuration. +func (p *K8SliceConfiguration) GetConfigurationType() FlavorTypeName { + return K8SliceNameDefault } -// Transaction contains information regarding the transaction for a flavour. +// Transaction contains information regarding the transaction for a flavor. type Transaction struct { - TransactionID string `json:"transactionID"` - FlavourID string `json:"flavourID"` - Partition *Partition `json:"partition,omitempty"` - Buyer NodeIdentity `json:"buyer"` - ClusterID string `json:"clusterID"` - StartTime string `json:"startTime"` + TransactionID string `json:"transactionID"` + FlavorID string `json:"flavorID"` + Configuration *Configuration `json:"configuration,omitempty"` + Buyer NodeIdentity `json:"buyer"` + ClusterID string `json:"clusterID"` + ExpirationTime string `json:"expirationTime"` } // Contract represents a Contract object with its characteristics. type Contract struct { - ContractID string `json:"contractID"` - TransactionID string `json:"transactionID"` - Flavour Flavour `json:"flavour"` - Buyer NodeIdentity `json:"buyerID"` - BuyerClusterID string `json:"buyerClusterID"` - Seller NodeIdentity `json:"seller"` - SellerCredentials LiqoCredentials `json:"sellerCredentials"` - ExpirationTime string `json:"expirationTime,omitempty"` - ExtraInformation map[string]string `json:"extraInformation,omitempty"` - Partition *Partition `json:"partition,omitempty"` + ContractID string `json:"contractID"` + TransactionID string `json:"transactionID"` + Flavor Flavor `json:"flavor"` + Buyer NodeIdentity `json:"buyerID"` + BuyerClusterID string `json:"buyerClusterID"` + Seller NodeIdentity `json:"seller"` + PeeringTargetCredentials LiqoCredentials `json:"sellerCredentials"` + ExpirationTime string `json:"expirationTime,omitempty"` + ExtraInformation map[string]string `json:"extraInformation,omitempty"` + Configuration *Configuration `json:"configuration,omitempty"` + NetworkRequests string `json:"networkAuthorizations,omitempty"` } -// LiqoCredentials contains the credentials of a Liqo cluster to enstablish a peering. +// LiqoCredentials contains the credentials of a Liqo cluster to establish a peering. type LiqoCredentials struct { ClusterID string `json:"clusterID"` ClusterName string `json:"clusterName"` diff --git a/pkg/utils/namings/doc.go b/pkg/utils/namings/doc.go index cb185f3..7d847b5 100644 --- a/pkg/utils/namings/doc.go +++ b/pkg/utils/namings/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/namings/namings.go b/pkg/utils/namings/namings.go index e479804..f8bfdfa 100644 --- a/pkg/utils/namings/namings.go +++ b/pkg/utils/namings/namings.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -33,20 +33,20 @@ func ForgeVirtualNodeName(clusterName string) string { } // ForgeContractName creates a name for the Contract. -func ForgeContractName(flavourID string) string { - hash := ForgeHashString(flavourID, 4) - return fmt.Sprintf("contract-%s-%s", flavourID, hash) +func ForgeContractName(flavorID string) string { + hash := ForgeHashString(flavorID, 4) + return fmt.Sprintf("contract-%s-%s", flavorID, hash) } // ForgeAllocationName generates a name for the Allocation. -func ForgeAllocationName(flavourID string) string { - hash := ForgeHashString(flavourID, 4) - return fmt.Sprintf("allocation-%s-%s", flavourID, hash) +func ForgeAllocationName(flavorID string) string { + hash := ForgeHashString(flavorID, 4) + return fmt.Sprintf("allocation-%s-%s", flavorID, hash) } // ForgePeeringCandidateName generates a name for the PeeringCandidate. -func ForgePeeringCandidateName(flavourID string) string { - return fmt.Sprintf("peeringcandidate-%s", flavourID) +func ForgePeeringCandidateName(flavorID string) string { + return fmt.Sprintf("peeringcandidate-%s", flavorID) } // ForgeReservationName generates a name for the Reservation. @@ -54,8 +54,8 @@ func ForgeReservationName(solverID string) string { return fmt.Sprintf("reservation-%s", solverID) } -// ForgeFlavourName returns the name of the flavour following the pattern Domain-resourceType-rand(4). -func ForgeFlavourName(workerID, resourceType, domain string) string { +// ForgeFlavorName returns the name of the flavor following the pattern Domain-resourceType-rand(4). +func ForgeFlavorName(resourceType, domain string) string { var resType string if resourceType == "" { resType = flags.ResourceType @@ -67,7 +67,22 @@ func ForgeFlavourName(workerID, resourceType, domain string) string { klog.Errorf("Error when generating random string: %s", err) } - return domain + "-" + resType + "-" + ForgeHashString(workerID+r, 8) + name := domain + "-" + resType + "-" + r + + // Force lowercase + return strings.ToLower(name) +} + +// ForgePartitionName generates a name for the Partition. +func ForgePartitionName(partitionType string) string { + // Generate Random String + r, err := ForgeRandomString() + if err != nil { + klog.Errorf("Error when generating random string: %s", err) + } + + // Append the random string to the partition type + return fmt.Sprintf("%s-%s", partitionType, r) } // ForgeDiscoveryName returns the name of the discovery following the pattern solverID-discovery. @@ -99,8 +114,8 @@ func ForgeTransactionID() (string, error) { return transactionID, nil } -// RetrieveFlavourNameFromPC generates a name for the Flavour from the PeeringCandidate. -func RetrieveFlavourNameFromPC(pcName string) string { +// RetrieveFlavorNameFromPC generates a name for the Flavor from the PeeringCandidate. +func RetrieveFlavorNameFromPC(pcName string) string { return strings.TrimPrefix(pcName, "peeringcandidate-") } diff --git a/pkg/utils/parseutil/doc.go b/pkg/utils/parseutil/doc.go index 91a9c80..f1670f0 100644 --- a/pkg/utils/parseutil/doc.go +++ b/pkg/utils/parseutil/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/parseutil/parseutil.go b/pkg/utils/parseutil/parseutil.go index 40417a3..81c0deb 100644 --- a/pkg/utils/parseutil/parseutil.go +++ b/pkg/utils/parseutil/parseutil.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,73 +15,335 @@ package parseutil import ( + "encoding/json" + "fmt" + "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/klog/v2" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" reservationv1alpha1 "github.com/fluidos-project/node/apis/reservation/v1alpha1" "github.com/fluidos-project/node/pkg/utils/models" ) -// ParseFlavourSelector parses FlavourSelector into a Selector. -func ParseFlavourSelector(selector *nodecorev1alpha1.FlavourSelector) *models.Selector { - s := &models.Selector{ - Architecture: selector.Architecture, - FlavourType: selector.FlavourType, +// ParseFlavorSelector parses FlavorSelector into a Selector. +func ParseFlavorSelector(selector *nodecorev1alpha1.Selector) (models.Selector, error) { + // Parse the Selector + klog.Infof("Parsing the selector %s", selector.FlavorType) + selectorIdentifier, selectorStruct, err := nodecorev1alpha1.ParseSolverSelector(selector) + if err != nil { + return nil, err } - if selector.MatchSelector != nil { - s.MatchSelector = &models.MatchSelector{ - CPU: selector.MatchSelector.CPU, - Memory: selector.MatchSelector.Memory, - Pods: selector.MatchSelector.Pods, - EphemeralStorage: selector.MatchSelector.EphemeralStorage, - Storage: selector.MatchSelector.Storage, - Gpu: selector.MatchSelector.Gpu, + klog.Infof("Selector type: %s", selectorIdentifier) + + switch selectorIdentifier { + case nodecorev1alpha1.TypeK8Slice: + // Check if the selectorStruct is nil + if selectorStruct == nil { + return models.K8SliceSelector{ + Architecture: nil, + CPU: nil, + Memory: nil, + Pods: nil, + Storage: nil, + }, nil } + // Force casting of selectorStruct to K8Slice + selectorStruct := selectorStruct.(nodecorev1alpha1.K8SliceSelector) + klog.Info("Forced casting of selectorStruct to K8Slice") + // Print the selectorStruct + klog.Infof("SelectorStruct: %v", selectorStruct) + // Generate the model for the K8Slice selector + k8SliceSelector, err := parseK8SliceFilters(&selectorStruct) + if err != nil { + return nil, err + } + + klog.Infof("K8SliceSelector: %v", k8SliceSelector) + + return *k8SliceSelector, nil + + case nodecorev1alpha1.TypeVM: + // Force casting of selectorStruct to VM + // TODO: Implement the parsing of the VM selector + return nil, fmt.Errorf("VM selector not implemented") + + case nodecorev1alpha1.TypeService: + // Force casting of selectorStruct to Service + // TODO: Implement the parsing of the Service selector + return nil, fmt.Errorf("service selector not implemented") + + default: + return nil, fmt.Errorf("unknown selector type") } +} + +// ParseResourceQuantityFilter parses a filter of type ResourceQuantityFilter into a ResourceQuantityFilter model. +func ParseResourceQuantityFilter(filter *nodecorev1alpha1.ResourceQuantityFilter) (models.ResourceQuantityFilter, error) { + var filterModel models.ResourceQuantityFilter + + if filter == nil { + return filterModel, nil + } + + klog.Infof("Parsing the filter %s", filter.Name) + switch filter.Name { + case nodecorev1alpha1.TypeMatchFilter: + klog.Info("Parsing the filter as a MatchFilter") + // Unmarshal the data into a ResourceMatchSelector + var matchFilter nodecorev1alpha1.ResourceMatchSelector + err := json.Unmarshal(filter.Data.Raw, &matchFilter) + if err != nil { + return filterModel, err + } + + matchFilterData := models.ResourceQuantityMatchFilter{ + Value: matchFilter.Value.DeepCopy(), + } + // Marshal the filter data into JSON + matchFilterDataJSON, err := json.Marshal(matchFilterData) + if err != nil { + return filterModel, err + } + + // Generate the model for the filter + filterModel = models.ResourceQuantityFilter{ + Name: models.MatchFilter, + Data: matchFilterDataJSON, + } + klog.Infof("Filter model: %v", filterModel) + case nodecorev1alpha1.TypeRangeFilter: + klog.Info("Parsing the filter as a RangeFilter") + // Unmarshal the data into a ResourceRangeSelector + var rangeFilter nodecorev1alpha1.ResourceRangeSelector + err := json.Unmarshal(filter.Data.Raw, &rangeFilter) + if err != nil { + return filterModel, err + } + + rangeFilterData := models.ResourceQuantityRangeFilter{ + Min: rangeFilter.Min, + Max: rangeFilter.Max, + } + + // Marshal the filter data into JSON + rangeFilterDataJSON, err := json.Marshal(rangeFilterData) + if err != nil { + return filterModel, err + } + + // Generate the model for the filter + filterModel = models.ResourceQuantityFilter{ + Name: models.RangeFilter, + Data: rangeFilterDataJSON, + } + klog.Infof("Filter model: %v", filterModel) + default: + return filterModel, fmt.Errorf("unknown filter type") + } + + return filterModel, nil +} + +// ParseStringFilter parses a filter of type StringFilter into a StringFilter model. +func ParseStringFilter(filter *nodecorev1alpha1.StringFilter) (models.StringFilter, error) { + var filterModel models.StringFilter + + if filter == nil { + return filterModel, nil + } + + klog.Infof("Parsing the filter %s", filter.Name) + switch filter.Name { + case nodecorev1alpha1.TypeMatchFilter: + klog.Info("Parsing the filter as a MatchFilter") + // Unmarshal the data into a StringMatchSelector + var matchFilter nodecorev1alpha1.StringMatchSelector + err := json.Unmarshal(filter.Data.Raw, &matchFilter) + if err != nil { + return filterModel, err + } - if selector.RangeSelector != nil { - s.RangeSelector = &models.RangeSelector{ - MinCPU: selector.RangeSelector.MinCpu, - MinMemory: selector.RangeSelector.MinMemory, - MinPods: selector.RangeSelector.MinPods, - MinEph: selector.RangeSelector.MinEph, - MinStorage: selector.RangeSelector.MinStorage, - MinGpu: selector.RangeSelector.MinGpu, - MaxCPU: selector.RangeSelector.MaxCpu, - MaxMemory: selector.RangeSelector.MaxMemory, - MaxPods: selector.RangeSelector.MaxPods, - MaxEph: selector.RangeSelector.MaxEph, - MaxStorage: selector.RangeSelector.MaxStorage, - MaxGpu: selector.RangeSelector.MaxGpu, + matchFilterData := models.StringMatchFilter{ + Value: matchFilter.Value, } + // Marshal the filter data into JSON + matchFilterDataJSON, err := json.Marshal(matchFilterData) + if err != nil { + return filterModel, err + } + + // Generate the model for the filter + filterModel = models.StringFilter{ + Name: models.MatchFilter, + Data: matchFilterDataJSON, + } + klog.Infof("Filter model: %v", filterModel) + case nodecorev1alpha1.TypeRangeFilter: + klog.Info("Parsing the filter as a RangeFilter") + // Unmarshal the data into a StringRangeSelector + var rangeFilter nodecorev1alpha1.StringRangeSelector + err := json.Unmarshal(filter.Data.Raw, &rangeFilter) + if err != nil { + return filterModel, err + } + + rangeFilterData := models.StringRangeFilter{ + Regex: rangeFilter.Regex, + } + + // Marshal the filter data into JSON + rangeFilterDataJSON, err := json.Marshal(rangeFilterData) + if err != nil { + return filterModel, err + } + + // Generate the model for the filter + filterModel = models.StringFilter{ + Name: models.RangeFilter, + Data: rangeFilterDataJSON, + } + klog.Infof("Filter model: %v", filterModel) + default: + return filterModel, fmt.Errorf("unknown filter type") } - return s + return filterModel, nil } -// ParsePartition creates a Partition Object from a Partition CR. -func ParsePartition(partition *nodecorev1alpha1.Partition) *models.Partition { - return &models.Partition{ - CPU: partition.CPU, - Memory: partition.Memory, - Pods: partition.Pods, - EphemeralStorage: partition.EphemeralStorage, - Storage: partition.Storage, - Gpu: partition.Gpu, +func parseK8SliceFilters(k8sSelector *nodecorev1alpha1.K8SliceSelector) (*models.K8SliceSelector, error) { + var architectureFilterModel models.StringFilter + var cpuFilterModel, memoryFilterModel, podsFilterModel, storageFilterModel models.ResourceQuantityFilter + + // Parse the Architecture filter + klog.Info("Parsing the Architecture filter") + architectureFilterModel, err := ParseStringFilter(k8sSelector.ArchitectureFilter) + if err != nil { + return nil, err + } + if architectureFilterModel.Data == nil { + klog.Info("Architecture filter is nil") + } + + // Parse the CPU filter + klog.Info("Parsing the CPU filter") + cpuFilterModel, err = ParseResourceQuantityFilter(k8sSelector.CPUFilter) + if err != nil { + return nil, err + } + if cpuFilterModel.Data == nil { + klog.Info("CPU filter is nil") + } + + // Parse the Memory filter + klog.Info("Parsing the Memory filter") + memoryFilterModel, err = ParseResourceQuantityFilter(k8sSelector.MemoryFilter) + if err != nil { + return nil, err + } + if memoryFilterModel.Data == nil { + klog.Info("Memory filter is nil") + } + + // Parse the Pods filter + klog.Info("Parsing the Pods filter") + podsFilterModel, err = ParseResourceQuantityFilter(k8sSelector.PodsFilter) + if err != nil { + return nil, err + } + if podsFilterModel.Data == nil { + klog.Info("Pods filter is nil") + } + + // Parse the Storage filter + klog.Info("Parsing the Storage filter") + storageFilterModel, err = ParseResourceQuantityFilter(k8sSelector.StorageFilter) + if err != nil { + return nil, err + } + if storageFilterModel.Data == nil { + klog.Info("Storage filter is nil") + } + + // Generate the model for the K8Slice selector + k8SliceSelector := models.K8SliceSelector{ + Architecture: func() *models.StringFilter { + if k8sSelector.ArchitectureFilter != nil { + return &architectureFilterModel + } + return nil + }(), + CPU: func() *models.ResourceQuantityFilter { + if k8sSelector.CPUFilter != nil { + return &cpuFilterModel + } + return nil + }(), + Memory: func() *models.ResourceQuantityFilter { + if k8sSelector.MemoryFilter != nil { + return &memoryFilterModel + } + return nil + }(), + Pods: func() *models.ResourceQuantityFilter { + if k8sSelector.PodsFilter != nil { + return &podsFilterModel + } + return nil + }(), + Storage: func() *models.ResourceQuantityFilter { + if k8sSelector.StorageFilter != nil { + return &storageFilterModel + } + return nil + }(), } + + return &k8SliceSelector, nil } -// ParsePartitionFromObj creates a Partition CR from a Partition Object. -func ParsePartitionFromObj(partition *models.Partition) *nodecorev1alpha1.Partition { - return &nodecorev1alpha1.Partition{ - Architecture: partition.Architecture, - CPU: partition.CPU, - Memory: partition.Memory, - Pods: partition.Pods, - Gpu: partition.Gpu, - Storage: partition.Storage, - EphemeralStorage: partition.EphemeralStorage, +// ParseConfiguration creates a Configuration Object from a Configuration CR. +func ParseConfiguration(configuration *nodecorev1alpha1.Configuration) *models.Configuration { + // Parse the Configuration + configurationType, configurationStruct, err := nodecorev1alpha1.ParseConfiguration(configuration) + if err != nil { + return nil + } + + switch configurationType { + case nodecorev1alpha1.TypeK8Slice: + // Force casting of configuration to K8Slice + configuration := configurationStruct.(nodecorev1alpha1.K8SliceConfiguration) + k8sliceConfigurationJSON := models.K8SliceConfiguration{ + CPU: configuration.CPU, + Memory: configuration.Memory, + Pods: configuration.Pods, + Gpu: func() *models.GpuCharacteristics { + if configuration.Gpu != nil { + return &models.GpuCharacteristics{ + Model: configuration.Gpu.Model, + Cores: configuration.Gpu.Cores, + Memory: configuration.Gpu.Memory, + } + } + return nil + }(), + Storage: configuration.Storage, + } + // Marshal the K8Slice configuration to JSON + configurationData, err := json.Marshal(k8sliceConfigurationJSON) + if err != nil { + klog.Errorf("Error marshaling the K8Slice configuration: %s", err) + return nil + } + return &models.Configuration{ + Type: models.K8SliceNameDefault, + Data: configurationData, + } + // TODO: Implement the other configuration types, if any + default: + return nil } } @@ -94,81 +356,244 @@ func ParseNodeIdentity(node nodecorev1alpha1.NodeIdentity) models.NodeIdentity { } } -// ParseFlavour creates a Flavour Object from a Flavour CR. -func ParseFlavour(flavour *nodecorev1alpha1.Flavour) *models.Flavour { - return &models.Flavour{ - FlavourID: flavour.Name, - Type: string(flavour.Spec.Type), - ProviderID: flavour.Spec.ProviderID, - Characteristics: models.Characteristics{ - Architecture: flavour.Spec.Characteristics.Architecture, - CPU: flavour.Spec.Characteristics.Cpu, - Memory: flavour.Spec.Characteristics.Memory, - Pods: flavour.Spec.Characteristics.Pods, - PersistentStorage: flavour.Spec.Characteristics.PersistentStorage, - EphemeralStorage: flavour.Spec.Characteristics.EphemeralStorage, - Gpu: flavour.Spec.Characteristics.Gpu, - }, - Owner: ParseNodeIdentity(flavour.Spec.Owner), - Policy: models.Policy{ - Partitionable: func() *models.Partitionable { - if flavour.Spec.Policy.Partitionable != nil { - return &models.Partitionable{ - CPUMinimum: flavour.Spec.Policy.Partitionable.CpuMin, - MemoryMinimum: flavour.Spec.Policy.Partitionable.MemoryMin, - PodsMinimum: flavour.Spec.Policy.Partitionable.PodsMin, - CPUStep: flavour.Spec.Policy.Partitionable.CpuStep, - MemoryStep: flavour.Spec.Policy.Partitionable.MemoryStep, - PodsStep: flavour.Spec.Policy.Partitionable.PodsStep, +// ParseSourceDestination creates a SourceDestination Object from a SourceDestination CR. +func ParseSourceDestination(sourceDestination nodecorev1alpha1.SourceDestination) (*models.SourceDestination, error) { + var resourceSelector models.ResourceSelector + // Parse the ResourceSelector + typeIdentifier, selector, err := nodecorev1alpha1.ParseResourceSelector(sourceDestination.ResourceSelector) + if err != nil { + return nil, err + } + switch typeIdentifier { + case nodecorev1alpha1.CIDRSelectorType: + // Force casting of selector to CIDRSelector + cidrSelector := selector.(nodecorev1alpha1.CIDRSelector) + // Marshal the CIDRSelector to JSON + cidrSelectorData, err := json.Marshal(cidrSelector) + if err != nil { + return nil, err + } + resourceSelector = models.ResourceSelector{ + TypeIdentifier: models.CIDRSelectorType, + Selector: cidrSelectorData, + } + case nodecorev1alpha1.PodNamespaceSelectorType: + // Force casting of selector to PodNamespaceSelector + podNamespaceSelector := selector.(nodecorev1alpha1.PodNamespaceSelector) + // Marshal the PodNamespaceSelector to JSON + podNamespaceSelectorData, err := json.Marshal(podNamespaceSelector) + if err != nil { + return nil, err + } + resourceSelector = models.ResourceSelector{ + TypeIdentifier: models.CIDRSelectorType, + Selector: podNamespaceSelectorData, + } + default: + return nil, fmt.Errorf("unknown resource selector type") + } + sourceDestinationModel := models.SourceDestination{ + IsHotCluster: sourceDestination.IsHotCluster, + ResourceSelector: resourceSelector, + } + + return &sourceDestinationModel, nil +} + +// ParseNetworkAuthorizations creates a NetworkAuthorizations Object from a NetworkAuthorizations CR. +func ParseNetworkAuthorizations(networkAuthorizations *nodecorev1alpha1.NetworkAuthorizations) (*models.NetworkAuthorizations, error) { + var deniedCommunications []models.NetworkIntent + var mandatoryCommunications []models.NetworkIntent + + // DeniedCommuncations + for i := range networkAuthorizations.DeniedCommunications { + // Parse the NetworkIntent + networkIntent := networkAuthorizations.DeniedCommunications[i] + source, err := ParseSourceDestination(networkIntent.Source) + if err != nil { + return nil, err + } + destination, err := ParseSourceDestination(networkIntent.Destination) + if err != nil { + return nil, err + } + deniedCommunications = append(deniedCommunications, models.NetworkIntent{ + Name: networkIntent.Name, + Source: *source, + Destination: *destination, + DestinationPort: networkIntent.DestinationPort, + ProtocolType: networkIntent.ProtocolType, + }) + } + + // MandatoryCommunications + for i := range networkAuthorizations.MandatoryCommunications { + // Parse the NetworkIntent + networkIntent := networkAuthorizations.MandatoryCommunications[i] + source, err := ParseSourceDestination(networkIntent.Source) + if err != nil { + return nil, err + } + destination, err := ParseSourceDestination(networkIntent.Destination) + if err != nil { + return nil, err + } + mandatoryCommunications = append(mandatoryCommunications, models.NetworkIntent{ + Name: networkIntent.Name, + Source: *source, + Destination: *destination, + DestinationPort: networkIntent.DestinationPort, + ProtocolType: networkIntent.ProtocolType, + }) + } + + return &models.NetworkAuthorizations{ + DeniedCommunications: deniedCommunications, + MandatoryCommunications: mandatoryCommunications, + }, nil +} + +// ParseFlavor creates a Flavor Object from a Flavor CR. +func ParseFlavor(flavor *nodecorev1alpha1.Flavor) *models.Flavor { + var modelFlavor models.Flavor + + flavorType, flavorTypeStruct, errParse := nodecorev1alpha1.ParseFlavorType(flavor) + if errParse != nil { + return nil + } + + var modelFlavorType models.FlavorType + + switch flavorType { + case nodecorev1alpha1.TypeK8Slice: + // Force casting of flavorTypeStruct to K8Slice + flavorTypeStruct := flavorTypeStruct.(nodecorev1alpha1.K8Slice) + modelFlavorTypeData := models.K8Slice{ + Characteristics: models.K8SliceCharacteristics{ + Architecture: flavorTypeStruct.Characteristics.Architecture, + CPU: flavorTypeStruct.Characteristics.CPU, + Memory: flavorTypeStruct.Characteristics.Memory, + Pods: flavorTypeStruct.Characteristics.Pods, + Gpu: func() *models.GpuCharacteristics { + if flavorTypeStruct.Characteristics.Gpu != nil { + return &models.GpuCharacteristics{ + Model: flavorTypeStruct.Characteristics.Gpu.Model, + Cores: flavorTypeStruct.Characteristics.Gpu.Cores, + Memory: flavorTypeStruct.Characteristics.Gpu.Memory, + } } - } - return nil - }(), - Aggregatable: func() *models.Aggregatable { - if flavour.Spec.Policy.Aggregatable != nil { - return &models.Aggregatable{ - MinCount: flavour.Spec.Policy.Aggregatable.MinCount, - MaxCount: flavour.Spec.Policy.Aggregatable.MaxCount, + return nil + }(), + Storage: flavorTypeStruct.Characteristics.Storage, + }, + Properties: models.K8SliceProperties{ + Latency: flavorTypeStruct.Properties.Latency, + SecurityStandards: flavorTypeStruct.Properties.SecurityStandards, + CarbonFootprint: func() *models.CarbonFootprint { + if flavorTypeStruct.Properties.CarbonFootprint != nil { + return &models.CarbonFootprint{ + Embodied: flavorTypeStruct.Properties.CarbonFootprint.Embodied, + Operational: flavorTypeStruct.Properties.CarbonFootprint.Operational, + } } + return nil + }(), + NetworkAuthorizations: func() *models.NetworkAuthorizations { + if flavorTypeStruct.Properties.NetworkAuthorizations != nil { + na, err := ParseNetworkAuthorizations(flavorTypeStruct.Properties.NetworkAuthorizations) + if err != nil { + return nil + } + return na + } + return nil + }(), + }, + Policies: models.K8SlicePolicies{ + Partitionability: models.K8SlicePartitionability{ + CPUMin: flavorTypeStruct.Policies.Partitionability.CPUMin, + MemoryMin: flavorTypeStruct.Policies.Partitionability.MemoryMin, + PodsMin: flavorTypeStruct.Policies.Partitionability.PodsMin, + CPUStep: flavorTypeStruct.Policies.Partitionability.CPUStep, + MemoryStep: flavorTypeStruct.Policies.Partitionability.MemoryStep, + PodsStep: flavorTypeStruct.Policies.Partitionability.PodsStep, + }, + }, + } + + // Encode the K8Slice data into JSON + encodedFlavorTypeData, err := json.Marshal(modelFlavorTypeData) + if err != nil { + klog.Errorf("Error encoding the K8Slice data: %s", err) + return nil + } + + modelFlavorType = models.FlavorType{ + Name: models.K8SliceNameDefault, + Data: encodedFlavorTypeData, + } + // TODO: Implement the other flavor types + default: + klog.Errorf("Unknown flavor type: %s", flavorType) + return nil + } + + modelFlavor = models.Flavor{ + FlavorID: flavor.Name, + Type: modelFlavorType, + ProviderID: flavor.Spec.ProviderID, + NetworkPropertyType: flavor.Spec.NetworkPropertyType, + Timestamp: flavor.CreationTimestamp.Time, + Location: func() *models.Location { + if flavor.Spec.Location != nil { + location := models.Location{ + Latitude: flavor.Spec.Location.Latitude, + Longitude: flavor.Spec.Location.Longitude, + Country: flavor.Spec.Location.Country, + City: flavor.Spec.Location.City, + AdditionalNotes: flavor.Spec.Location.AdditionalNotes, } - return nil - }(), - }, + return &location + } + return nil + }(), Price: models.Price{ - Amount: flavour.Spec.Price.Amount, - Currency: flavour.Spec.Price.Currency, - Period: flavour.Spec.Price.Period, - }, - OptionalFields: models.OptionalFields{ - Availability: flavour.Spec.OptionalFields.Availability, - WorkerID: flavour.Spec.OptionalFields.WorkerID, + Amount: flavor.Spec.Price.Amount, + Currency: flavor.Spec.Price.Currency, + Period: flavor.Spec.Price.Period, }, + Owner: ParseNodeIdentity(flavor.Spec.Owner), + Availability: flavor.Spec.Availability, } + + return &modelFlavor } // ParseContract creates a Contract Object. func ParseContract(contract *reservationv1alpha1.Contract) *models.Contract { return &models.Contract{ ContractID: contract.Name, - Flavour: *ParseFlavour(&contract.Spec.Flavour), + Flavor: *ParseFlavor(&contract.Spec.Flavor), Buyer: ParseNodeIdentity(contract.Spec.Buyer), BuyerClusterID: contract.Spec.BuyerClusterID, TransactionID: contract.Spec.TransactionID, - Partition: func() *models.Partition { - if contract.Spec.Partition != nil { - return ParsePartition(contract.Spec.Partition) + Configuration: func() *models.Configuration { + if contract.Spec.Configuration != nil { + configuration := ParseConfiguration(contract.Spec.Configuration) + return configuration } return nil }(), Seller: ParseNodeIdentity(contract.Spec.Seller), - SellerCredentials: models.LiqoCredentials{ - ClusterID: contract.Spec.SellerCredentials.ClusterID, - ClusterName: contract.Spec.SellerCredentials.ClusterName, - Token: contract.Spec.SellerCredentials.Token, - Endpoint: contract.Spec.SellerCredentials.Endpoint, + PeeringTargetCredentials: models.LiqoCredentials{ + ClusterID: contract.Spec.PeeringTargetCredentials.ClusterID, + ClusterName: contract.Spec.PeeringTargetCredentials.ClusterName, + Token: contract.Spec.PeeringTargetCredentials.Token, + Endpoint: contract.Spec.PeeringTargetCredentials.Endpoint, }, ExpirationTime: contract.Spec.ExpirationTime, ExtraInformation: contract.Spec.ExtraInformation, + NetworkRequests: contract.Spec.NetworkRequests, } } diff --git a/pkg/utils/resourceforge/doc.go b/pkg/utils/resourceforge/doc.go index b442f50..378eb9c 100644 --- a/pkg/utils/resourceforge/doc.go +++ b/pkg/utils/resourceforge/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/resourceforge/forge.go b/pkg/utils/resourceforge/forge.go index 9e9cb62..2e52134 100644 --- a/pkg/utils/resourceforge/forge.go +++ b/pkg/utils/resourceforge/forge.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,9 +15,14 @@ package resourceforge import ( + "encoding/json" + "fmt" "time" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/klog/v2" advertisementv1alpha1 "github.com/fluidos-project/node/apis/advertisement/v1alpha1" nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" @@ -29,15 +34,15 @@ import ( "github.com/fluidos-project/node/pkg/utils/tools" ) -// ForgeDiscovery creates a Discovery CR from a FlavourSelector and a solverID. -func ForgeDiscovery(selector *nodecorev1alpha1.FlavourSelector, solverID string) *advertisementv1alpha1.Discovery { +// ForgeDiscovery creates a Discovery CR from a FlavorSelector and a solverID. +func ForgeDiscovery(selector *nodecorev1alpha1.Selector, solverID string) *advertisementv1alpha1.Discovery { return &advertisementv1alpha1.Discovery{ ObjectMeta: metav1.ObjectMeta{ Name: namings.ForgeDiscoveryName(solverID), - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, Spec: advertisementv1alpha1.DiscoverySpec{ - Selector: func() *nodecorev1alpha1.FlavourSelector { + Selector: func() *nodecorev1alpha1.Selector { if selector != nil { return selector } @@ -49,21 +54,21 @@ func ForgeDiscovery(selector *nodecorev1alpha1.FlavourSelector, solverID string) } } -// ForgePeeringCandidate creates a PeeringCandidate CR from a Flavour and a Discovery. -func ForgePeeringCandidate(flavourPeeringCandidate *nodecorev1alpha1.Flavour, +// ForgePeeringCandidate creates a PeeringCandidate CR from a Flavor and a Discovery. +func ForgePeeringCandidate(flavorPeeringCandidate *nodecorev1alpha1.Flavor, solverID string, available bool) (pc *advertisementv1alpha1.PeeringCandidate) { pc = &advertisementv1alpha1.PeeringCandidate{ ObjectMeta: metav1.ObjectMeta{ - Name: namings.ForgePeeringCandidateName(flavourPeeringCandidate.Name), - Namespace: flags.FluidoNamespace, + Name: namings.ForgePeeringCandidateName(flavorPeeringCandidate.Name), + Namespace: flags.FluidosNamespace, }, Spec: advertisementv1alpha1.PeeringCandidateSpec{ - Flavour: nodecorev1alpha1.Flavour{ + Flavor: nodecorev1alpha1.Flavor{ ObjectMeta: metav1.ObjectMeta{ - Name: flavourPeeringCandidate.Name, - Namespace: flavourPeeringCandidate.Namespace, + Name: flavorPeeringCandidate.Name, + Namespace: flavorPeeringCandidate.Namespace, }, - Spec: flavourPeeringCandidate.Spec, + Spec: flavorPeeringCandidate.Spec, }, Available: available, }, @@ -74,21 +79,21 @@ func ForgePeeringCandidate(flavourPeeringCandidate *nodecorev1alpha1.Flavour, // ForgeReservation creates a Reservation CR from a PeeringCandidate. func ForgeReservation(pc *advertisementv1alpha1.PeeringCandidate, - partition *nodecorev1alpha1.Partition, + configuration *nodecorev1alpha1.Configuration, ni nodecorev1alpha1.NodeIdentity) *reservationv1alpha1.Reservation { solverID := pc.Spec.SolverID reservation := &reservationv1alpha1.Reservation{ ObjectMeta: metav1.ObjectMeta{ Name: namings.ForgeReservationName(solverID), - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, Spec: reservationv1alpha1.ReservationSpec{ SolverID: solverID, Buyer: ni, Seller: nodecorev1alpha1.NodeIdentity{ - Domain: pc.Spec.Flavour.Spec.Owner.Domain, - NodeID: pc.Spec.Flavour.Spec.Owner.NodeID, - IP: pc.Spec.Flavour.Spec.Owner.IP, + Domain: pc.Spec.Flavor.Spec.Owner.Domain, + NodeID: pc.Spec.Flavor.Spec.Owner.NodeID, + IP: pc.Spec.Flavor.Spec.Owner.IP, }, PeeringCandidate: nodecorev1alpha1.GenericRef{ Name: pc.Name, @@ -96,47 +101,54 @@ func ForgeReservation(pc *advertisementv1alpha1.PeeringCandidate, }, Reserve: true, Purchase: true, - Partition: func() *nodecorev1alpha1.Partition { - if partition != nil { - return partition + Configuration: func() *nodecorev1alpha1.Configuration { + if configuration != nil { + return configuration } return nil }(), }, } - if partition != nil { - reservation.Spec.Partition = partition + if configuration != nil { + reservation.Spec.Configuration = configuration } return reservation } // ForgeContract creates a Contract CR. -func ForgeContract(flavour *nodecorev1alpha1.Flavour, transaction *models.Transaction, +func ForgeContract(flavor *nodecorev1alpha1.Flavor, transaction *models.Transaction, lc *nodecorev1alpha1.LiqoCredentials) *reservationv1alpha1.Contract { return &reservationv1alpha1.Contract{ ObjectMeta: metav1.ObjectMeta{ - Name: namings.ForgeContractName(flavour.Name), - Namespace: flags.FluidoNamespace, + Name: namings.ForgeContractName(flavor.Name), + Namespace: flags.FluidosNamespace, }, Spec: reservationv1alpha1.ContractSpec{ - Flavour: *flavour, + Flavor: *flavor, Buyer: nodecorev1alpha1.NodeIdentity{ Domain: transaction.Buyer.Domain, IP: transaction.Buyer.IP, NodeID: transaction.Buyer.NodeID, }, - BuyerClusterID: transaction.ClusterID, - Seller: flavour.Spec.Owner, - SellerCredentials: *lc, - TransactionID: transaction.TransactionID, - Partition: func() *nodecorev1alpha1.Partition { - if transaction.Partition != nil { - return parseutil.ParsePartitionFromObj(transaction.Partition) + BuyerClusterID: transaction.ClusterID, + Seller: flavor.Spec.Owner, + PeeringTargetCredentials: *lc, + TransactionID: transaction.TransactionID, + Configuration: func() *nodecorev1alpha1.Configuration { + if transaction.Configuration != nil { + configuration, err := ForgeConfiguration(*transaction.Configuration) + if err != nil { + klog.Errorf("Error when parsing configuration: %s", err) + return nil + } + return configuration } return nil }(), ExpirationTime: time.Now().Add(flags.ExpirationContract).Format(time.RFC3339), ExtraInformation: nil, + // TODO: Add logic to network requests + NetworkRequests: "", }, Status: reservationv1alpha1.ContractStatus{ Phase: nodecorev1alpha1.PhaseStatus{ @@ -147,45 +159,53 @@ func ForgeContract(flavour *nodecorev1alpha1.Flavour, transaction *models.Transa } } -// ForgePartitionable creates the partition characteristics of a flavour. -func ForgePartitionable() (partitionable *nodecorev1alpha1.Partitionable) { - return &nodecorev1alpha1.Partitionable{ - CpuMin: parseutil.ParseQuantityFromString(flags.CPUMin), - MemoryMin: parseutil.ParseQuantityFromString(flags.MemoryMin), - PodsMin: parseutil.ParseQuantityFromString(flags.PodsMin), - CpuStep: parseutil.ParseQuantityFromString(flags.CPUStep), - MemoryStep: parseutil.ParseQuantityFromString(flags.MemoryStep), - PodsStep: parseutil.ParseQuantityFromString(flags.PodsStep), +// ForgeFlavorFromMetrics creates a new flavor custom resource from the metrics of the node. +func ForgeFlavorFromMetrics(node *models.NodeInfo, ni nodecorev1alpha1.NodeIdentity, + ownerReferences []metav1.OwnerReference) (flavor *nodecorev1alpha1.Flavor) { + k8SliceType := nodecorev1alpha1.K8Slice{ + Characteristics: nodecorev1alpha1.K8SliceCharacteristics{ + Architecture: node.Architecture, + CPU: node.ResourceMetrics.CPUAvailable, + Memory: node.ResourceMetrics.MemoryAvailable, + Pods: node.ResourceMetrics.PodsAvailable, + Storage: &node.ResourceMetrics.EphemeralStorage, + Gpu: &nodecorev1alpha1.GPU{ + Model: node.ResourceMetrics.GPU.Model, + Cores: node.ResourceMetrics.GPU.CoresAvailable, + Memory: node.ResourceMetrics.GPU.MemoryAvailable, + }, + }, + Properties: nodecorev1alpha1.Properties{}, + Policies: nodecorev1alpha1.Policies{ + Partitionability: nodecorev1alpha1.Partitionability{ + CPUMin: parseutil.ParseQuantityFromString(flags.CPUMin), + MemoryMin: parseutil.ParseQuantityFromString(flags.MemoryMin), + PodsMin: parseutil.ParseQuantityFromString(flags.PodsMin), + CPUStep: parseutil.ParseQuantityFromString(flags.CPUStep), + MemoryStep: parseutil.ParseQuantityFromString(flags.MemoryStep), + PodsStep: parseutil.ParseQuantityFromString(flags.PodsStep), + }, + }, } -} -// ForgeFlavourFromMetrics creates a new flavour custom resource from the metrics of the node. -func ForgeFlavourFromMetrics(node *models.NodeInfo, ni nodecorev1alpha1.NodeIdentity) (flavour *nodecorev1alpha1.Flavour) { - // TODO MinCount and MaxCount error handeling - return &nodecorev1alpha1.Flavour{ + // Serialize K8SliceType to JSON + k8SliceTypeJSON, err := json.Marshal(k8SliceType) + if err != nil { + klog.Errorf("Error when marshaling K8SliceType: %s", err) + return nil + } + + return &nodecorev1alpha1.Flavor{ ObjectMeta: metav1.ObjectMeta{ - Name: namings.ForgeFlavourName(node.UID, "", ni.Domain), - Namespace: flags.FluidoNamespace, + Name: namings.ForgeFlavorName(string(nodecorev1alpha1.TypeK8Slice), ni.Domain), + Namespace: flags.FluidosNamespace, + OwnerReferences: ownerReferences, }, - Spec: nodecorev1alpha1.FlavourSpec{ - ProviderID: ni.NodeID, - Type: nodecorev1alpha1.K8S, - QuantityAvailable: 1, - Characteristics: nodecorev1alpha1.Characteristics{ - Architecture: node.Architecture, - Cpu: node.ResourceMetrics.CPUAvailable, - Memory: node.ResourceMetrics.MemoryAvailable, - Pods: node.ResourceMetrics.PodsAvailable, - EphemeralStorage: node.ResourceMetrics.EphemeralStorage, - PersistentStorage: parseutil.ParseQuantityFromString("0"), - Gpu: parseutil.ParseQuantityFromString("0"), - }, - Policy: nodecorev1alpha1.Policy{ - Partitionable: ForgePartitionable(), - Aggregatable: &nodecorev1alpha1.Aggregatable{ - MinCount: int(flags.MinCount), - MaxCount: int(flags.MaxCount), - }, + Spec: nodecorev1alpha1.FlavorSpec{ + ProviderID: ni.NodeID, + FlavorType: nodecorev1alpha1.FlavorType{ + TypeIdentifier: nodecorev1alpha1.TypeK8Slice, + TypeData: runtime.RawExtension{Raw: k8SliceTypeJSON}, }, Owner: ni, Price: nodecorev1alpha1.Price{ @@ -193,35 +213,36 @@ func ForgeFlavourFromMetrics(node *models.NodeInfo, ni nodecorev1alpha1.NodeIden Currency: flags.CURRENCY, Period: flags.PERIOD, }, - OptionalFields: nodecorev1alpha1.OptionalFields{ - Availability: true, - // This previously was the node UID that maybe is not the best choice to manage the scheduling - // Does this make any sense in the case we want to aggregate the Flavours? - WorkerID: node.Name, + Availability: true, + // TODO: test without network property and location + NetworkPropertyType: "networkProperty", + Location: &nodecorev1alpha1.Location{ + Latitude: "10", + Longitude: "58", + Country: "Italy", + City: "Turin", + AdditionalNotes: "None", }, }, } } -// 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{ +// ForgeFlavorFromRef creates a new flavor starting from a Reference Flavor and the new Characteristics. +func ForgeFlavorFromRef(f *nodecorev1alpha1.Flavor, newFlavorType *nodecorev1alpha1.FlavorType) (flavor *nodecorev1alpha1.Flavor) { + return &nodecorev1alpha1.Flavor{ ObjectMeta: metav1.ObjectMeta{ - Name: namings.ForgeFlavourName(f.Spec.OptionalFields.WorkerID, string(f.Spec.Type), f.Spec.Owner.Domain), - Namespace: flags.FluidoNamespace, + Name: namings.ForgeFlavorName(string(f.Spec.FlavorType.TypeIdentifier), f.Spec.Owner.Domain), + Namespace: flags.FluidosNamespace, + OwnerReferences: f.GetOwnerReferences(), }, - 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, - QuantityAvailable: f.Spec.QuantityAvailable, - OptionalFields: nodecorev1alpha1.OptionalFields{ - Availability: true, - WorkerID: f.Spec.OptionalFields.WorkerID, - }, + Spec: nodecorev1alpha1.FlavorSpec{ + ProviderID: f.Spec.ProviderID, + FlavorType: *newFlavorType, + Owner: f.Spec.Owner, + Price: f.Spec.Price, + Availability: true, + NetworkPropertyType: f.Spec.NetworkPropertyType, + Location: f.Spec.Location, }, } } @@ -233,15 +254,15 @@ func ForgeTransactionObj(id string, req *models.ReserveRequest) *models.Transact return &models.Transaction{ TransactionID: id, Buyer: req.Buyer, - ClusterID: req.ClusterID, - FlavourID: req.FlavourID, - Partition: func() *models.Partition { - if req.Partition != nil { - return req.Partition + ClusterID: req.Buyer.AdditionalInformation.LiqoID, + FlavorID: req.FlavorID, + Configuration: func() *models.Configuration { + if req.Configuration != nil { + return req.Configuration } return nil }(), - StartTime: tools.GetTimeNow(), + ExpirationTime: tools.GetExpirationTime(), } } @@ -249,19 +270,20 @@ func ForgeTransactionObj(id string, req *models.ReserveRequest) *models.Transact func ForgeContractObj(contract *reservationv1alpha1.Contract) models.Contract { return models.Contract{ ContractID: contract.Name, - Flavour: *parseutil.ParseFlavour(&contract.Spec.Flavour), + Flavor: *parseutil.ParseFlavor(&contract.Spec.Flavor), Buyer: parseutil.ParseNodeIdentity(contract.Spec.Buyer), BuyerClusterID: contract.Spec.BuyerClusterID, Seller: parseutil.ParseNodeIdentity(contract.Spec.Seller), - SellerCredentials: models.LiqoCredentials{ - ClusterID: contract.Spec.SellerCredentials.ClusterID, - ClusterName: contract.Spec.SellerCredentials.ClusterName, - Token: contract.Spec.SellerCredentials.Token, - Endpoint: contract.Spec.SellerCredentials.Endpoint, + PeeringTargetCredentials: models.LiqoCredentials{ + ClusterID: contract.Spec.PeeringTargetCredentials.ClusterID, + ClusterName: contract.Spec.PeeringTargetCredentials.ClusterName, + Token: contract.Spec.PeeringTargetCredentials.Token, + Endpoint: contract.Spec.PeeringTargetCredentials.Endpoint, }, - Partition: func() *models.Partition { - if contract.Spec.Partition != nil { - return parseutil.ParsePartition(contract.Spec.Partition) + Configuration: func() *models.Configuration { + if contract.Spec.Configuration != nil { + configuration := parseutil.ParseConfiguration(contract.Spec.Configuration) + return configuration } return nil }(), @@ -276,23 +298,20 @@ func ForgeContractObj(contract *reservationv1alpha1.Contract) models.Contract { } } -// ForgeResponsePurchaseObj creates a new response purchase. -func ForgeResponsePurchaseObj(contract *models.Contract) *models.ResponsePurchase { - return &models.ResponsePurchase{ - Contract: *contract, - Status: "Completed", - } -} - // ForgeContractFromObj creates a Contract from a reservation. -func ForgeContractFromObj(contract *models.Contract) *reservationv1alpha1.Contract { +func ForgeContractFromObj(contract *models.Contract) (*reservationv1alpha1.Contract, error) { + // Forge flavorCR + flavorCR, err := ForgeFlavorFromObj(&contract.Flavor) + if err != nil { + return nil, err + } return &reservationv1alpha1.Contract{ ObjectMeta: metav1.ObjectMeta{ Name: contract.ContractID, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, Spec: reservationv1alpha1.ContractSpec{ - Flavour: *ForgeFlavourFromObj(&contract.Flavour), + Flavor: *flavorCR, Buyer: nodecorev1alpha1.NodeIdentity{ Domain: contract.Buyer.Domain, IP: contract.Buyer.IP, @@ -304,16 +323,21 @@ func ForgeContractFromObj(contract *models.Contract) *reservationv1alpha1.Contra IP: contract.Seller.IP, Domain: contract.Seller.Domain, }, - SellerCredentials: nodecorev1alpha1.LiqoCredentials{ - ClusterID: contract.SellerCredentials.ClusterID, - ClusterName: contract.SellerCredentials.ClusterName, - Token: contract.SellerCredentials.Token, - Endpoint: contract.SellerCredentials.Endpoint, + PeeringTargetCredentials: nodecorev1alpha1.LiqoCredentials{ + ClusterID: contract.PeeringTargetCredentials.ClusterID, + ClusterName: contract.PeeringTargetCredentials.ClusterName, + Token: contract.PeeringTargetCredentials.Token, + Endpoint: contract.PeeringTargetCredentials.Endpoint, }, TransactionID: contract.TransactionID, - Partition: func() *nodecorev1alpha1.Partition { - if contract.Partition != nil { - return parseutil.ParsePartitionFromObj(contract.Partition) + Configuration: func() *nodecorev1alpha1.Configuration { + if contract.Configuration != nil { + configuration, err := ForgeConfiguration(*contract.Configuration) + if err != nil { + klog.Errorf("Error when parsing configuration: %s", err) + return nil + } + return configuration } return nil }(), @@ -331,7 +355,7 @@ func ForgeContractFromObj(contract *models.Contract) *reservationv1alpha1.Contra StartTime: tools.GetTimeNow(), }, }, - } + }, nil } // ForgeTransactionFromObj creates a transaction from a Transaction object. @@ -339,20 +363,30 @@ func ForgeTransactionFromObj(transaction *models.Transaction) *reservationv1alph return &reservationv1alpha1.Transaction{ ObjectMeta: metav1.ObjectMeta{ Name: transaction.TransactionID, - Namespace: flags.FluidoNamespace, + Namespace: flags.FluidosNamespace, }, Spec: reservationv1alpha1.TransactionSpec{ - FlavourID: transaction.FlavourID, - StartTime: transaction.StartTime, + FlavorID: transaction.FlavorID, + ExpirationTime: transaction.ExpirationTime, Buyer: nodecorev1alpha1.NodeIdentity{ Domain: transaction.Buyer.Domain, IP: transaction.Buyer.IP, NodeID: transaction.Buyer.NodeID, + LiqoID: func() string { + if transaction.Buyer.AdditionalInformation != nil { + return transaction.Buyer.AdditionalInformation.LiqoID + } + return "" + }(), }, - ClusterID: transaction.ClusterID, - Partition: func() *nodecorev1alpha1.Partition { - if transaction.Partition != nil { - return parseutil.ParsePartitionFromObj(transaction.Partition) + Configuration: func() *nodecorev1alpha1.Configuration { + if transaction.Configuration != nil { + configuration, err := ForgeConfiguration(*transaction.Configuration) + if err != nil { + klog.Errorf("Error when parsing configuration: %s", err) + return nil + } + return configuration } return nil }(), @@ -360,101 +394,446 @@ func ForgeTransactionFromObj(transaction *models.Transaction) *reservationv1alph } } -// ForgeFlavourFromObj creates a Flavour CR from a Flavour Object (REAR). -func ForgeFlavourFromObj(flavour *models.Flavour) *nodecorev1alpha1.Flavour { - f := &nodecorev1alpha1.Flavour{ - ObjectMeta: metav1.ObjectMeta{ - Name: flavour.FlavourID, - Namespace: flags.FluidoNamespace, - }, - Spec: nodecorev1alpha1.FlavourSpec{ - ProviderID: flavour.Owner.NodeID, - Type: nodecorev1alpha1.K8S, - QuantityAvailable: flavour.QuantityAvailable, - Characteristics: nodecorev1alpha1.Characteristics{ - Cpu: flavour.Characteristics.CPU, - Memory: flavour.Characteristics.Memory, - Pods: flavour.Characteristics.Pods, - Architecture: flavour.Characteristics.Architecture, - EphemeralStorage: flavour.Characteristics.EphemeralStorage, - PersistentStorage: flavour.Characteristics.PersistentStorage, - Gpu: flavour.Characteristics.Gpu, - }, - Policy: nodecorev1alpha1.Policy{ - // Check if flavour.Partitionable is not nil before setting Partitionable - Partitionable: func() *nodecorev1alpha1.Partitionable { - if flavour.Policy.Partitionable != nil { - return &nodecorev1alpha1.Partitionable{ - CpuMin: flavour.Policy.Partitionable.CPUMinimum, - MemoryMin: flavour.Policy.Partitionable.MemoryMinimum, - CpuStep: flavour.Policy.Partitionable.CPUStep, - MemoryStep: flavour.Policy.Partitionable.MemoryStep, +// ForgeConfiguration creates a Configuration CR from a Configuration object. +func ForgeConfiguration(configuration models.Configuration) (*nodecorev1alpha1.Configuration, error) { + // Parse the Configuration + switch configuration.Type { + case models.K8SliceNameDefault: + // Force casting of configurationStruct to K8Slice + var configurationStruct models.K8SliceConfiguration + err := json.Unmarshal(configuration.Data, &configurationStruct) + if err != nil { + return nil, err + } + k8SliceConfiguration := &nodecorev1alpha1.K8SliceConfiguration{ + CPU: configurationStruct.CPU, + Memory: configurationStruct.Memory, + Pods: configurationStruct.Pods, + Gpu: func() *nodecorev1alpha1.GPU { + if configurationStruct.Gpu != nil { + return &nodecorev1alpha1.GPU{ + Model: configurationStruct.Gpu.Model, + Cores: configurationStruct.Gpu.Cores, + Memory: configurationStruct.Gpu.Memory, + } + } + return nil + }(), + Storage: configurationStruct.Storage, + } + + // Marshal the K8Slice configuration to JSON + configurationData, err := json.Marshal(k8SliceConfiguration) + if err != nil { + return nil, err + } + + return &nodecorev1alpha1.Configuration{ + ConfigurationTypeIdentifier: nodecorev1alpha1.TypeK8Slice, + ConfigurationData: runtime.RawExtension{Raw: configurationData}, + }, nil + // TODO: Implement the other configuration types, if any + default: + return nil, fmt.Errorf("unknown configuration type") + } +} + +// ForgeResourceSelectorFromObj creates a ResourceSelector CR from a ResourceSelector Object. +func ForgeResourceSelectorFromObj(resourceSelector *models.ResourceSelector) *nodecorev1alpha1.ResourceSelector { + // Parse ResourceSelector + switch resourceSelector.TypeIdentifier { + case models.CIDRSelectorType: + // unmarshal CIDRSelector + var resourceSelectorStruct models.CIDRSelector + err := json.Unmarshal(resourceSelector.Selector, &resourceSelectorStruct) + if err != nil { + klog.Errorf("Error when unmarshaling CIDRSelector: %s", err) + return nil + } + // Create CIDRSelector nodecorev1alpha1 + cidrSelectorCR := nodecorev1alpha1.CIDRSelector(resourceSelectorStruct) + // Marshal CIDRSelector to JSON + resourceSelectorData, err := json.Marshal(cidrSelectorCR) + if err != nil { + klog.Errorf("Error when marshaling CIDRSelector: %s", err) + return nil + } + return &nodecorev1alpha1.ResourceSelector{ + TypeIdentifier: nodecorev1alpha1.CIDRSelectorType, + Selector: runtime.RawExtension{Raw: resourceSelectorData}, + } + case models.PodNamespaceSelectorType: + // Force casting of resourceSelector to PodNamespaceSelector type + var resourceSelectorStruct models.PodNamespaceSelector + err := json.Unmarshal(resourceSelector.Selector, &resourceSelectorStruct) + if err != nil { + klog.Errorf("Error when unmarshaling PodNamespaceSelector: %s", err) + return nil + } + // Create PodNamespaceSelector nodecorev1alpha1 + podNamespaceSelectorCR := nodecorev1alpha1.PodNamespaceSelector{ + // Copy map of models.PodNamespaceSelector.Pod to nodecorev1alpha1.PodNamespaceSelector.Pod + Pod: func() map[string]string { + podMap := make(map[string]string) + for i := range resourceSelectorStruct.Pod { + keyValuePair := resourceSelectorStruct.Pod[i] + podMap[keyValuePair.Key] = keyValuePair.Value + } + return podMap + }(), + // Copy map of models.PodNamespaceSelector.Namespace to nodecorev1alpha1.PodNamespaceSelector.Namespace + Namespace: func() map[string]string { + namespaceMap := make(map[string]string) + for i := range resourceSelectorStruct.Namespace { + keyValuePair := resourceSelectorStruct.Namespace[i] + namespaceMap[keyValuePair.Key] = keyValuePair.Value + } + return namespaceMap + }(), + } + // Marshal PodNamespaceSelector to JSON + resourceSelectorData, err := json.Marshal(podNamespaceSelectorCR) + if err != nil { + klog.Errorf("Error when marshaling PodNamespaceSelector: %s", err) + return nil + } + return &nodecorev1alpha1.ResourceSelector{ + TypeIdentifier: nodecorev1alpha1.PodNamespaceSelectorType, + Selector: runtime.RawExtension{Raw: resourceSelectorData}, + } + default: + klog.Errorf("Resource selector type not recognized") + return nil + } +} + +// ForgeSourceDestinationFromObj creates a SourceDestination CR from a SourceDestination Object. +func ForgeSourceDestinationFromObj(sourceDestination *models.SourceDestination) *nodecorev1alpha1.SourceDestination { + // Parse ResourceSelector + resourceSelector := ForgeResourceSelectorFromObj(&sourceDestination.ResourceSelector) + if resourceSelector == nil { + klog.Errorf("Error when parsing resource selector from source destination") + return nil + } + return &nodecorev1alpha1.SourceDestination{ + IsHotCluster: sourceDestination.IsHotCluster, + ResourceSelector: *resourceSelector, + } +} + +// ForgeNetworkIntentFromObj creates a NetworkIntent CR from a NetworkIntent Object. +func ForgeNetworkIntentFromObj(networkIntent *models.NetworkIntent) *nodecorev1alpha1.NetworkIntent { + // Parse NetworkIntent + source := ForgeSourceDestinationFromObj(&networkIntent.Source) + if source == nil { + klog.Errorf("Error when parsing source from network intent") + return nil + } + destination := ForgeSourceDestinationFromObj(&networkIntent.Destination) + if destination == nil { + klog.Errorf("Error when parsing destination from network intent") + return nil + } + return &nodecorev1alpha1.NetworkIntent{ + Name: networkIntent.Name, + Source: *source, + Destination: *destination, + DestinationPort: networkIntent.DestinationPort, + ProtocolType: networkIntent.ProtocolType, + } +} + +// ForgeNetworkAuthorizationsFromObj creates a NetworkAuthorizations CR from a NetworkAuthorizations Object. +func ForgeNetworkAuthorizationsFromObj(networkAuthorizations *models.NetworkAuthorizations) *nodecorev1alpha1.NetworkAuthorizations { + // DeniedCommunications + var deniedCommunicationsModel []nodecorev1alpha1.NetworkIntent + var mandatoryCommunicationsModel []nodecorev1alpha1.NetworkIntent + for i := range networkAuthorizations.DeniedCommunications { + deniedCommunication := networkAuthorizations.DeniedCommunications[i] + // Parse the DeniedCommunication + ni := ForgeNetworkIntentFromObj(&deniedCommunication) + if ni == nil { + klog.Errorf("Error when parsing denied communication from network authorizations") + } else { + deniedCommunicationsModel = append(deniedCommunicationsModel, *ni) + } + } + // MandatoryCommunications + for i := range networkAuthorizations.MandatoryCommunications { + mandatoryCommunication := networkAuthorizations.MandatoryCommunications[i] + // Parse the MandatoryCommunication + ni := ForgeNetworkIntentFromObj(&mandatoryCommunication) + if ni == nil { + klog.Errorf("Error when parsing mandatory communication from network authorizations") + } else { + mandatoryCommunicationsModel = append(mandatoryCommunicationsModel, *ni) + } + } + return &nodecorev1alpha1.NetworkAuthorizations{ + DeniedCommunications: deniedCommunicationsModel, + MandatoryCommunications: mandatoryCommunicationsModel, + } +} + +// ForgeFlavorFromObj creates a Flavor CR from a Flavor Object (REAR). +func ForgeFlavorFromObj(flavor *models.Flavor) (*nodecorev1alpha1.Flavor, error) { + var flavorType nodecorev1alpha1.FlavorType + + switch flavor.Type.Name { + case models.K8SliceNameDefault: + // Unmarshal K8SliceType + var flavorTypeDataModel models.K8Slice + err := json.Unmarshal(flavor.Type.Data, &flavorTypeDataModel) + if err != nil { + klog.Errorf("Error when unmarshalling K8SliceType: %s", err) + return nil, err + } + flavorTypeData := nodecorev1alpha1.K8Slice{ + Characteristics: nodecorev1alpha1.K8SliceCharacteristics{ + Architecture: flavorTypeDataModel.Characteristics.Architecture, + CPU: flavorTypeDataModel.Characteristics.CPU, + Memory: flavorTypeDataModel.Characteristics.Memory, + Pods: flavorTypeDataModel.Characteristics.Pods, + Storage: flavorTypeDataModel.Characteristics.Storage, + Gpu: func() *nodecorev1alpha1.GPU { + if flavorTypeDataModel.Characteristics.Gpu != nil { + return &nodecorev1alpha1.GPU{ + Model: flavorTypeDataModel.Characteristics.Gpu.Model, + Cores: flavorTypeDataModel.Characteristics.Gpu.Cores, + Memory: flavorTypeDataModel.Characteristics.Gpu.Memory, } } return nil }(), - Aggregatable: func() *nodecorev1alpha1.Aggregatable { - if flavour.Policy.Aggregatable != nil { - return &nodecorev1alpha1.Aggregatable{ - MinCount: flavour.Policy.Aggregatable.MinCount, - MaxCount: flavour.Policy.Aggregatable.MaxCount, + }, + Properties: nodecorev1alpha1.Properties{ + Latency: flavorTypeDataModel.Properties.Latency, + SecurityStandards: flavorTypeDataModel.Properties.SecurityStandards, + CarbonFootprint: func() *nodecorev1alpha1.CarbonFootprint { + if flavorTypeDataModel.Properties.CarbonFootprint != nil { + return &nodecorev1alpha1.CarbonFootprint{ + Embodied: flavorTypeDataModel.Properties.CarbonFootprint.Embodied, + Operational: flavorTypeDataModel.Properties.CarbonFootprint.Operational, } } return nil }(), + NetworkAuthorizations: func() *nodecorev1alpha1.NetworkAuthorizations { + if flavorTypeDataModel.Properties.NetworkAuthorizations != nil { + return ForgeNetworkAuthorizationsFromObj(flavorTypeDataModel.Properties.NetworkAuthorizations) + } + return nil + }(), }, + Policies: nodecorev1alpha1.Policies{ + Partitionability: nodecorev1alpha1.Partitionability{ + CPUMin: flavorTypeDataModel.Policies.Partitionability.CPUMin, + MemoryMin: flavorTypeDataModel.Policies.Partitionability.MemoryMin, + PodsMin: flavorTypeDataModel.Policies.Partitionability.PodsMin, + CPUStep: flavorTypeDataModel.Policies.Partitionability.CPUStep, + MemoryStep: flavorTypeDataModel.Policies.Partitionability.MemoryStep, + PodsStep: flavorTypeDataModel.Policies.Partitionability.PodsStep, + }, + }, + } + flavorTypeDataJSON, err := json.Marshal(flavorTypeData) + if err != nil { + klog.Errorf("Error when marshaling K8SliceType: %s", err) + return nil, err + } + flavorType = nodecorev1alpha1.FlavorType{ + TypeIdentifier: nodecorev1alpha1.TypeK8Slice, + TypeData: runtime.RawExtension{Raw: flavorTypeDataJSON}, + } + + default: + klog.Errorf("Flavor type not recognized") + return nil, fmt.Errorf("flavor type not recognized") + } + f := &nodecorev1alpha1.Flavor{ + ObjectMeta: metav1.ObjectMeta{ + Name: flavor.FlavorID, + Namespace: flags.FluidosNamespace, + }, + Spec: nodecorev1alpha1.FlavorSpec{ + ProviderID: flavor.Owner.NodeID, + FlavorType: flavorType, Owner: nodecorev1alpha1.NodeIdentity{ - Domain: flavour.Owner.Domain, - IP: flavour.Owner.IP, - NodeID: flavour.Owner.NodeID, + Domain: flavor.Owner.Domain, + IP: flavor.Owner.IP, + NodeID: flavor.Owner.NodeID, }, Price: nodecorev1alpha1.Price{ - Amount: flavour.Price.Amount, - Currency: flavour.Price.Currency, - Period: flavour.Price.Period, - }, - OptionalFields: nodecorev1alpha1.OptionalFields{ - Availability: flavour.OptionalFields.Availability, - WorkerID: flavour.OptionalFields.WorkerID, + Amount: flavor.Price.Amount, + Currency: flavor.Price.Currency, + Period: flavor.Price.Period, }, + Availability: flavor.Availability, + NetworkPropertyType: flavor.NetworkPropertyType, + Location: func() *nodecorev1alpha1.Location { + if flavor.Location != nil { + return &nodecorev1alpha1.Location{ + Latitude: flavor.Location.Latitude, + Longitude: flavor.Location.Longitude, + Country: flavor.Location.Country, + City: flavor.Location.City, + AdditionalNotes: flavor.Location.AdditionalNotes, + } + } + return nil + }(), }, } - return f + return f, nil } -// ForgePartition creates a Partition from a FlavourSelector. -func ForgePartition(selector *nodecorev1alpha1.FlavourSelector) *nodecorev1alpha1.Partition { - return &nodecorev1alpha1.Partition{ - Architecture: selector.Architecture, - CPU: selector.RangeSelector.MinCpu, - Memory: selector.RangeSelector.MinMemory, - Pods: selector.RangeSelector.MinPods, - EphemeralStorage: selector.RangeSelector.MinEph, - Storage: selector.RangeSelector.MinStorage, - Gpu: selector.RangeSelector.MinGpu, +// ForgeK8SliceConfiguration creates a Configuration from a FlavorSelector. +func ForgeK8SliceConfiguration(selector nodecorev1alpha1.K8SliceSelector, flavor *nodecorev1alpha1.K8Slice) *nodecorev1alpha1.K8SliceConfiguration { + var cpu, memory, pods resource.Quantity + var gpu *nodecorev1alpha1.GPU + var storage *resource.Quantity + + klog.Info("Parsing K8Slice selector") + + if selector.CPUFilter != nil { + // Parse CPU filter + klog.Info("Parsing CPU filter") + cpuFilterType, cpuFilterData, err := nodecorev1alpha1.ParseResourceQuantityFilter(selector.CPUFilter) + if err != nil { + klog.Errorf("Error when parsing CPU filter: %s", err) + return nil + } + // Define configuration value based on filter type + switch cpuFilterType { + // Match Filter + case nodecorev1alpha1.TypeMatchFilter: + cpu = cpuFilterData.(nodecorev1alpha1.ResourceMatchSelector).Value + // Range Filter + case nodecorev1alpha1.TypeRangeFilter: + // Check if min value is set + if cpuFilterData.(nodecorev1alpha1.ResourceRangeSelector).Min != nil { + rrs := cpuFilterData.(nodecorev1alpha1.ResourceRangeSelector) + cpu = *rrs.Min + } + + // Default + default: + klog.Errorf("CPU filter type not recognized") + return nil + } + } else { + cpu = flavor.Characteristics.CPU + } + + if selector.MemoryFilter != nil { + // Parse Memory filter + klog.Info("Parsing Memory filter") + memoryFilterType, memoryFilterData, err := nodecorev1alpha1.ParseResourceQuantityFilter(selector.MemoryFilter) + if err != nil { + klog.Errorf("Error when parsing Memory filter: %s", err) + return nil + } + // Define configuration value based on filter type + switch memoryFilterType { + // Match Filter + case nodecorev1alpha1.TypeMatchFilter: + memory = memoryFilterData.(nodecorev1alpha1.ResourceMatchSelector).Value + // Range Filter + case nodecorev1alpha1.TypeRangeFilter: + // Check if min value is set + if memoryFilterData.(nodecorev1alpha1.ResourceRangeSelector).Min != nil { + rrs := memoryFilterData.(nodecorev1alpha1.ResourceRangeSelector) + memory = *rrs.Min + } + // Default + default: + klog.Errorf("Memory filter type not recognized") + return nil + } + } else { + memory = flavor.Characteristics.Memory + } + + if selector.PodsFilter != nil { + // Parse Pods filter + klog.Info("Parsing Pods filter") + podsFilterType, podsFilterData, err := nodecorev1alpha1.ParseResourceQuantityFilter(selector.PodsFilter) + if err != nil { + klog.Errorf("Error when parsing Pods filter: %s", err) + return nil + } + // Define configuration value based on filter type + switch podsFilterType { + // Match Filter + case nodecorev1alpha1.TypeMatchFilter: + pods = podsFilterData.(nodecorev1alpha1.ResourceMatchSelector).Value + // Range Filter + case nodecorev1alpha1.TypeRangeFilter: + // Check if min value is set + if podsFilterData.(nodecorev1alpha1.ResourceRangeSelector).Min != nil { + rrs := podsFilterData.(nodecorev1alpha1.ResourceRangeSelector) + pods = *rrs.Min + } + + // Default + default: + klog.Errorf("Pods filter type not recognized") + return nil + } + } else { + pods = flavor.Characteristics.Pods + } + + if selector.StorageFilter != nil { + // Parse Storage filter + klog.Info("Parsing Storage filter") + storageFilterType, storageFilterData, err := nodecorev1alpha1.ParseResourceQuantityFilter(selector.StorageFilter) + if err != nil { + klog.Errorf("Error when parsing Storage filter: %s", err) + return nil + } + // Define configuration value based on filter type + switch storageFilterType { + // Match Filter + case nodecorev1alpha1.TypeMatchFilter: + value := storageFilterData.(nodecorev1alpha1.ResourceMatchSelector).Value + storage = &value + // Range Filter + case nodecorev1alpha1.TypeRangeFilter: + // Check if min value is set + if storageFilterData.(nodecorev1alpha1.ResourceRangeSelector).Min != nil { + rrs := storageFilterData.(nodecorev1alpha1.ResourceRangeSelector) + if rrs.Min != nil { + storage = rrs.Min + } + } + // Default + default: + klog.Errorf("Storage filter type not recognized") + return nil + } + } + + // Compose configuration based on values gathered from filters + return &nodecorev1alpha1.K8SliceConfiguration{ + CPU: cpu, + Memory: memory, + Pods: pods, + Gpu: gpu, + Storage: storage, } } // ForgeAllocation creates an Allocation from a Contract. -func ForgeAllocation(contract *reservationv1alpha1.Contract, intentID, nodeName string, - destination nodecorev1alpha1.Destination, nodeType nodecorev1alpha1.NodeType) *nodecorev1alpha1.Allocation { +func ForgeAllocation(contract *reservationv1alpha1.Contract, intentID string) *nodecorev1alpha1.Allocation { return &nodecorev1alpha1.Allocation{ ObjectMeta: metav1.ObjectMeta{ - Name: namings.ForgeAllocationName(contract.Spec.Flavour.Name), - Namespace: flags.FluidoNamespace, + Name: namings.ForgeAllocationName(contract.Spec.Flavor.Name), + Namespace: flags.FluidosNamespace, }, Spec: nodecorev1alpha1.AllocationSpec{ - RemoteClusterID: func() string { - if nodeType == nodecorev1alpha1.Node { - return contract.Spec.BuyerClusterID - } - return contract.Spec.SellerCredentials.ClusterID - }(), - IntentID: intentID, - NodeName: nodeName, - Type: nodeType, - Destination: destination, - Forwarding: false, + IntentID: intentID, + Forwarding: false, Contract: nodecorev1alpha1.GenericRef{ Name: contract.Name, Namespace: contract.Namespace, diff --git a/pkg/utils/services/doc.go b/pkg/utils/services/doc.go index 4146496..43a3d3e 100644 --- a/pkg/utils/services/doc.go +++ b/pkg/utils/services/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/services/flavours_services.go b/pkg/utils/services/flavours_services.go index 6eefe3f..588ae59 100644 --- a/pkg/utils/services/flavours_services.go +++ b/pkg/utils/services/flavours_services.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,39 +25,39 @@ import ( "github.com/fluidos-project/node/pkg/utils/flags" ) -// FlavourService is the interface that wraps the basic Flavour methods and allows to manage the concurrent access to the Flavour CRs. -type FlavourService interface { +// FlavorService is the interface that wraps the basic Flavor methods and allows to manage the concurrent access to the Flavor CRs. +type FlavorService interface { sync.Mutex - GetAllFlavours() ([]nodecorev1alpha1.Flavour, error) - GetFlavourByID(flavourID string) (*nodecorev1alpha1.Flavour, error) + GetAllFlavors() ([]nodecorev1alpha1.Flavor, error) + GetFlavorByID(flavorID string) (*nodecorev1alpha1.Flavor, error) } -// GetAllFlavours returns all the Flavours in the cluster. -func GetAllFlavours(cl client.Client) ([]nodecorev1alpha1.Flavour, error) { - var flavourList nodecorev1alpha1.FlavourList +// GetAllFlavors returns all the Flavors in the cluster. +func GetAllFlavors(cl client.Client) ([]nodecorev1alpha1.Flavor, error) { + var flavorList nodecorev1alpha1.FlavorList - // List all Flavour CRs - err := cl.List(context.Background(), &flavourList) + // List all Flavor CRs + err := cl.List(context.Background(), &flavorList) if err != nil { - klog.Errorf("Error when listing Flavours: %s", err) + klog.Errorf("Error when listing Flavors: %s", err) return nil, err } - return flavourList.Items, nil + return flavorList.Items, nil } -// GetFlavourByID returns the entire Flavour CR (not only spec) in the cluster that matches the flavourID. -func GetFlavourByID(flavourID string, cl client.Client) (*nodecorev1alpha1.Flavour, error) { - // Get the flavour with the given ID (that is the name of the CR) - flavour := &nodecorev1alpha1.Flavour{} +// GetFlavorByID returns the entire Flavor CR (not only spec) in the cluster that matches the flavorID. +func GetFlavorByID(flavorID string, cl client.Client) (*nodecorev1alpha1.Flavor, error) { + // Get the flavor with the given ID (that is the name of the CR) + flavor := &nodecorev1alpha1.Flavor{} err := cl.Get(context.Background(), client.ObjectKey{ - Namespace: flags.FluidoNamespace, - Name: flavourID, - }, flavour) + Namespace: flags.FluidosNamespace, + Name: flavorID, + }, flavor) if err != nil { - klog.Errorf("Error when getting Flavour %s: %s", flavourID, err) + klog.Errorf("Error when getting Flavor %s: %s", flavorID, err) return nil, err } - return flavour, nil + return flavor, nil } diff --git a/pkg/utils/tools/doc.go b/pkg/utils/tools/doc.go index 0231463..3813092 100644 --- a/pkg/utils/tools/doc.go +++ b/pkg/utils/tools/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/utils/tools/tools.go b/pkg/utils/tools/tools.go index b7324fd..121503a 100644 --- a/pkg/utils/tools/tools.go +++ b/pkg/utils/tools/tools.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,12 +25,27 @@ func GetTimeNow() string { return time.Now().Format(time.RFC3339) } -// CheckExpiration checks if the timestamp has expired. -func CheckExpiration(timestamp string, expTime time.Duration) bool { - t, err := time.Parse(time.RFC3339, timestamp) +// GetExpirationTime returns the current time plus 24 hours in RFC3339 format. +func GetExpirationTime() string { + return time.Now().Add(time.Hour * 24).Format(time.RFC3339) +} + +// CheckExpiration checks if the expirationTimestamp has already expired. +func CheckExpiration(expirationTimestamp string) bool { + t, err := time.Parse(time.RFC3339, expirationTimestamp) + if err != nil { + klog.Errorf("Error parsing the transaction start time: %s", err) + return false + } + return time.Now().After(t) +} + +// CheckExpirationSinceTime checks if the expirationTimestamp has already expired. +func CheckExpirationSinceTime(sinceTimestamp string, expirationLapse time.Duration) bool { + t, err := time.Parse(time.RFC3339, sinceTimestamp) if err != nil { klog.Errorf("Error parsing the transaction start time: %s", err) return false } - return time.Since(t) > expTime + return time.Since(t) > expirationLapse } diff --git a/pkg/virtual-fabric-manager/doc.go b/pkg/virtual-fabric-manager/doc.go index c547531..329c4d4 100644 --- a/pkg/virtual-fabric-manager/doc.go +++ b/pkg/virtual-fabric-manager/doc.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/virtual-fabric-manager/services.go b/pkg/virtual-fabric-manager/services.go index a199b4d..8cddbed 100644 --- a/pkg/virtual-fabric-manager/services.go +++ b/pkg/virtual-fabric-manager/services.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 FLUIDOS Project +// Copyright 2022-2024 FLUIDOS Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/quickstart/utils/consumer-values-nolrm.yaml b/quickstart/utils/consumer-values-no-ad.yaml similarity index 93% rename from quickstart/utils/consumer-values-nolrm.yaml rename to quickstart/utils/consumer-values-no-ad.yaml index 74f7b97..1a590cb 100644 --- a/quickstart/utils/consumer-values-nolrm.yaml +++ b/quickstart/utils/consumer-values-no-ad.yaml @@ -21,7 +21,7 @@ common: localResourceManager: # -- The number of REAR Controller, which can be increased for active/passive high availability. - replicas: 0 + replicas: 1 pod: # -- Annotations for the local-resource-manager pod. annotations: {} @@ -39,6 +39,8 @@ localResourceManager: nodeResourceLabel: "node-role.fluidos.eu/resources" # -- This flag defines the resource type of the generated flavours. resourceType: "k8s-fluidos" + # -- Enable the auto-discovery of the resources. + enableAutoDiscovery: false flavour: # -- The minimum number of CPUs that can be requested to purchase a flavour. cpuMin: "0" @@ -149,3 +151,13 @@ networkManager: ip: # -- The NodeID is a UUID that identifies the FLUIDOS Node. It is used to generate the FQDN of the owned FLUIDOS Nodes and it is unique in the FLUIDOS closed domain nodeID: + +webhook: + # -- Enable the webhook server for the local-resource-manager. + enabled: true + # -- Configuration for the webhook server. + issuer: "self-signed" + # -- Configuration for the webhook server. + deployment: + # -- The mount path for the webhook certificates. + certsMount: "/tmp/k8s-webhook-server/serving-certs/" \ No newline at end of file diff --git a/quickstart/utils/consumer-values.yaml b/quickstart/utils/consumer-values.yaml index f00beec..dcff52e 100644 --- a/quickstart/utils/consumer-values.yaml +++ b/quickstart/utils/consumer-values.yaml @@ -39,6 +39,8 @@ localResourceManager: nodeResourceLabel: "node-role.fluidos.eu/resources" # -- This flag defines the resource type of the generated flavours. resourceType: "k8s-fluidos" + # -- Enable the auto-discovery of the resources. + enableAutoDiscovery: true flavour: # -- The minimum number of CPUs that can be requested to purchase a flavour. cpuMin: "0" @@ -149,3 +151,13 @@ networkManager: ip: # -- The NodeID is a UUID that identifies the FLUIDOS Node. It is used to generate the FQDN of the owned FLUIDOS Nodes and it is unique in the FLUIDOS closed domain nodeID: + +webhook: + # -- Enable the webhook server for the local-resource-manager. + enabled: true + # -- Configuration for the webhook server. + issuer: "self-signed" + # -- Configuration for the webhook server. + deployment: + # -- The mount path for the webhook certificates. + certsMount: "/tmp/k8s-webhook-server/serving-certs/" \ No newline at end of file diff --git a/quickstart/utils/provider-values-nolrm.yaml b/quickstart/utils/provider-values-no-ad.yaml similarity index 93% rename from quickstart/utils/provider-values-nolrm.yaml rename to quickstart/utils/provider-values-no-ad.yaml index 0402450..b9a55d6 100644 --- a/quickstart/utils/provider-values-nolrm.yaml +++ b/quickstart/utils/provider-values-no-ad.yaml @@ -21,7 +21,7 @@ common: localResourceManager: # -- The number of REAR Controller, which can be increased for active/passive high availability. - replicas: 0 + replicas: 1 pod: # -- Annotations for the local-resource-manager pod. annotations: {} @@ -39,6 +39,8 @@ localResourceManager: nodeResourceLabel: "node-role.fluidos.eu/resources" # -- This flag defines the resource type of the generated flavours. resourceType: "k8s-fluidos" + # -- Enable the auto-discovery of the resources. + enableAutoDiscovery: false flavour: # -- The minimum number of CPUs that can be requested to purchase a flavour. cpuMin: "0" @@ -149,3 +151,14 @@ networkManager: ip: # -- The NodeID is a UUID that identifies the FLUIDOS Node. It is used to generate the FQDN of the owned FLUIDOS Nodes and it is unique in the FLUIDOS closed domain nodeID: + + +webhook: + # -- Enable the webhook server for the local-resource-manager. + enabled: true + # -- Configuration for the webhook server. + issuer: "self-signed" + # -- Configuration for the webhook server. + deployment: + # -- The mount path for the webhook certificates. + certsMount: "/tmp/k8s-webhook-server/serving-certs/" \ No newline at end of file diff --git a/quickstart/utils/provider-values.yaml b/quickstart/utils/provider-values.yaml index 37346f9..1152042 100644 --- a/quickstart/utils/provider-values.yaml +++ b/quickstart/utils/provider-values.yaml @@ -39,6 +39,8 @@ localResourceManager: nodeResourceLabel: "node-role.fluidos.eu/resources" # -- This flag defines the resource type of the generated flavours. resourceType: "k8s-fluidos" + # -- Enable the auto-discovery of the resources. + enableAutoDiscovery: true flavour: # -- The minimum number of CPUs that can be requested to purchase a flavour. cpuMin: "0" @@ -149,3 +151,13 @@ networkManager: ip: # -- The NodeID is a UUID that identifies the FLUIDOS Node. It is used to generate the FQDN of the owned FLUIDOS Nodes and it is unique in the FLUIDOS closed domain nodeID: + +webhook: + # -- Enable the webhook server for the local-resource-manager. + enabled: true + # -- Configuration for the webhook server. + issuer: "self-signed" + # -- Configuration for the webhook server. + deployment: + # -- The mount path for the webhook certificates. + certsMount: "/tmp/k8s-webhook-server/serving-certs/" \ No newline at end of file diff --git a/testbed/kind/README.md b/testbed/kind/README.md index 20b0d8d..4519d93 100644 --- a/testbed/kind/README.md +++ b/testbed/kind/README.md @@ -15,9 +15,9 @@ This guide has been made only for testing purposes. If you want to install FLUID This guide will create two different Kubernetes clusters: -- **fluidos-consumer**: This cluster (a.k.a., FLUIDOS node) will act as a consumer of FLUIDOS resources. It will be used to deploy a `solver` example CR that will simulate an _Intent resolution_ request. This cluster will use the REAR protocol to communicate with the Provider cluster and to receive available Flavours, reserving the one that best fits the request and purchasing it. +- **fluidos-consumer**: This cluster (a.k.a., FLUIDOS node) will act as a consumer of FLUIDOS resources. It will be used to deploy a `solver` example CR that will simulate an _Intent resolution_ request. This cluster will use the REAR protocol to communicate with the Provider cluster and to receive available Flavors, reserving the one that best fits the request and purchasing it. -- **fluidos-provider**: This cluster (a.k.a. FLUIDOS node) will act as a provider of FLUIDOS resources. It will offer its own Flavours on the specific request made by the consumer, reserving and selling it. +- **fluidos-provider**: This cluster (a.k.a. FLUIDOS node) will act as a provider of FLUIDOS resources. It will offer its own Flavors on the specific request made by the consumer, reserving and selling it. ### Prerequisites @@ -95,10 +95,10 @@ This allows for convenient monitoring of both consumer and provider clusters wit - `node-rear-manager-` - `node-rear-controller-` -7. You can also check the status of the generated flavours with the following command: +7. You can also check the status of the generated flavors with the following command: ```sh -kubectl get flavours.nodecore.fluidos.eu -n fluidos +kubectl get flavors.nodecore.fluidos.eu -n fluidos ``` The result should be something like this: @@ -152,7 +152,7 @@ fluidos solver-sample intent-sample true true f 5. Other resources have been created, you can check them with the following commands: ```sh -kubectl get flavours.nodecore.fluidos.eu -n fluidos +kubectl get flavors.nodecore.fluidos.eu -n fluidos kubectl get discoveries.advertisement.fluidos.eu -n fluidos kubectl get reservations.reservation.fluidos.eu -n fluidos kubectl get contracts.reservation.fluidos.eu -n fluidos diff --git a/tools/scripts/install_liqo.sh b/tools/scripts/install_liqo.sh index d2e0bae..842d94d 100644 --- a/tools/scripts/install_liqo.sh +++ b/tools/scripts/install_liqo.sh @@ -31,8 +31,11 @@ CLUSTER_NAME=$2 # Get the Kubeconfig KUBECONFIG_LIQO=$3 +helm repo update + # Install Liqo based on the provider liqoctl install "$PROVIDER" --cluster-name "$CLUSTER_NAME" --kubeconfig "$KUBECONFIG_LIQO" || { echo "Failed to install Liqo for provider: $PROVIDER"; exit 1; } +liqoctl install "$PROVIDER" --cluster-name "$CLUSTER_NAME" --kubeconfig "$KUBECONFIG_LIQO" || { echo "Failed to install Liqo for provider: $PROVIDER"; exit 1; } # liqoctl install "$PROVIDER" || { echo "Failed to install Liqo for provider: $PROVIDER"; exit 1; } echo "Liqo installation for provider $PROVIDER completed successfully." diff --git a/tools/scripts/installation.sh b/tools/scripts/installation.sh index 4329699..d7391b4 100644 --- a/tools/scripts/installation.sh +++ b/tools/scripts/installation.sh @@ -76,7 +76,7 @@ function install_components() { local_repositories=$3 # Get the local resource manager installation boolean from parameters - local_resource_manager=$4 + enable_auto_discovery=$4 # Get the kubernetes clusters type from parameters installation_type=$5 @@ -178,10 +178,10 @@ function install_components() { # Decide value file to use based on the role of the cluster if [ "$(jq -r '.role' <<< "${clusters[$cluster]}")" == "consumer" ]; then # Check if local resouce manager is enabled - if [ "$local_resource_manager" == "true" ]; then + if [ "$enable_auto_discovery" == "true" ]; then value_file="$SCRIPT_DIR/../../quickstart/utils/consumer-values.yaml" else - value_file="$SCRIPT_DIR/../../quickstart/utils/consumer-values-nolrm.yaml" + value_file="$SCRIPT_DIR/../../quickstart/utils/consumer-values-no-ad.yaml" fi # Get cluster IP and port ip_value="${clusters[$cluster]}" @@ -194,10 +194,10 @@ function install_components() { return 0 else # Check if local resouce manager is enabled - if [ "$local_resource_manager" == "true" ]; then + if [ "$enable_auto_discovery" == "true" ]; then value_file="$SCRIPT_DIR/../../quickstart/utils/provider-values.yaml" else - value_file="$SCRIPT_DIR/../../quickstart/utils/provider-values-nolrm.yaml" + value_file="$SCRIPT_DIR/../../quickstart/utils/provider-values-no-ad.yaml" fi # Get cluster IP and port ip_value="${clusters[$cluster]}" @@ -208,7 +208,7 @@ function install_components() { # Install liqo chmod +x "$SCRIPT_DIR"/install_liqo.sh - "$SCRIPT_DIR"/install_liqo.sh "$installation_type" "$cluster" "$KUBECONFIG" || { echo "Failed to install Liqo in cluster $cluster"; exit 1; } + "$SCRIPT_DIR"/install_liqo.sh "$installation_type" "$cluster" "$KUBECONFIG" || { echo "Failed to install Liqo in cluster $cluster"; exit 1; } chmod -x "$SCRIPT_DIR"/install_liqo.sh # Skipping the installation of the node Helm chart if the cluster is a provider and its installation type is not kind diff --git a/tools/scripts/setup.sh b/tools/scripts/setup.sh old mode 100644 new mode 100755 index b051c16..01f1403 --- a/tools/scripts/setup.sh +++ b/tools/scripts/setup.sh @@ -71,14 +71,14 @@ else return 1 fi -# Ask the user if they want to use a local resource manager -read -r -p "Do you want to use a local resource manager? [y/n] " local_resource_manager +# Ask the user if they want to use the resource auto discovery from the local resource manager +read -r -p "Do you want to enable resource auto discovery? [y/n] " enable_auto_discovery # Check if the input is y or n -if [ "$local_resource_manager" == "y" ]; then - local_resource_manager=true -elif [ "$local_resource_manager" == "n" ]; then - local_resource_manager=false +if [ "$enable_auto_discovery" == "y" ]; then + enable_auto_discovery=true +elif [ "$enable_auto_discovery" == "n" ]; then + enable_auto_discovery=false else echo "Invalid option." return 1 @@ -132,7 +132,7 @@ else fi # FLUIDOS node installation -install_components "$consumers_json" "$providers_json" $local_repositories $local_resource_manager $installation_type +install_components "$consumers_json" "$providers_json" $local_repositories $enable_auto_discovery $installation_type print_title "Installation completed successfully"