Skip to content

Commit

Permalink
Flavour aggregation (#2)
Browse files Browse the repository at this point in the history
* Added logic for node aggregation into flavours

* Added CRDS
  • Loading branch information
Iqqdd99 authored Jun 18, 2024
1 parent a19d7b6 commit 21d9d9f
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 38 deletions.
3 changes: 3 additions & 0 deletions apis/nodecore/v1alpha1/flavour_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ type FlavourSpec struct {
// 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,11 @@ spec:
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ spec:
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.
Expand Down
4 changes: 4 additions & 0 deletions deployments/node/crds/nodecore.fluidos.eu_flavours.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ spec:
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.
Expand Down
4 changes: 4 additions & 0 deletions deployments/node/crds/reservation.fluidos.eu_contracts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ spec:
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.
Expand Down
146 changes: 135 additions & 11 deletions pkg/local-resource-manager/controller_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ 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"
)
Expand All @@ -38,6 +40,108 @@ import (
// 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...")

Expand All @@ -47,24 +151,44 @@ func Start(ctx context.Context, cl client.Client) error {
return fmt.Errorf("error getting FLUIDOS Node identity")
}

klog.Info("Getting nodes resources...")
nodes, err := GetNodesResources(ctx, cl)
// Check current flavours
flavours := &nodecorev1alpha1.FlavourList{}
err := cl.List(ctx, flavours)
if err != nil {
log.Printf("Error getting nodes resources: %v", err)
log.Printf("Error getting flavours: %v", err)
return err
}

klog.Infof("Creating Flavours: found %d nodes", len(nodes))

// For each node create a Flavour
for i := range nodes {
flavour := resourceforge.ForgeFlavourFromMetrics(&nodes[i], *nodeIdentity)
err := cl.Create(ctx, flavour)
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 creating Flavour: %v", err)
log.Printf("Error getting nodes resources: %v", err)
return err
}
klog.Infof("Flavour created: %s", flavour.Name)

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
Expand Down
19 changes: 10 additions & 9 deletions pkg/utils/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ import (

// 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"`
OptionalFields OptionalFields `json:"optionalFields"`
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.
Expand Down
46 changes: 28 additions & 18 deletions pkg/utils/resourceforge/forge.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,16 +147,30 @@ 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),
}
}

// 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{
ObjectMeta: metav1.ObjectMeta{
Name: namings.ForgeFlavourName(node.UID, "", ni.Domain),
Namespace: flags.FluidoNamespace,
},
Spec: nodecorev1alpha1.FlavourSpec{
ProviderID: ni.NodeID,
Type: nodecorev1alpha1.K8S,
ProviderID: ni.NodeID,
Type: nodecorev1alpha1.K8S,
QuantityAvailable: 1,
Characteristics: nodecorev1alpha1.Characteristics{
Architecture: node.Architecture,
Cpu: node.ResourceMetrics.CPUAvailable,
Expand All @@ -167,14 +181,7 @@ func ForgeFlavourFromMetrics(node *models.NodeInfo, ni nodecorev1alpha1.NodeIden
Gpu: parseutil.ParseQuantityFromString("0"),
},
Policy: nodecorev1alpha1.Policy{
Partitionable: &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),
},
Partitionable: ForgePartitionable(),
Aggregatable: &nodecorev1alpha1.Aggregatable{
MinCount: int(flags.MinCount),
MaxCount: int(flags.MaxCount),
Expand All @@ -189,6 +196,7 @@ func ForgeFlavourFromMetrics(node *models.NodeInfo, ni nodecorev1alpha1.NodeIden
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,
},
},
Expand All @@ -203,12 +211,13 @@ func ForgeFlavourFromRef(f *nodecorev1alpha1.Flavour, char *nodecorev1alpha1.Cha
Namespace: flags.FluidoNamespace,
},
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,
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,
Expand Down Expand Up @@ -359,8 +368,9 @@ func ForgeFlavourFromObj(flavour *models.Flavour) *nodecorev1alpha1.Flavour {
Namespace: flags.FluidoNamespace,
},
Spec: nodecorev1alpha1.FlavourSpec{
ProviderID: flavour.Owner.NodeID,
Type: nodecorev1alpha1.K8S,
ProviderID: flavour.Owner.NodeID,
Type: nodecorev1alpha1.K8S,
QuantityAvailable: flavour.QuantityAvailable,
Characteristics: nodecorev1alpha1.Characteristics{
Cpu: flavour.Characteristics.CPU,
Memory: flavour.Characteristics.Memory,
Expand Down

0 comments on commit 21d9d9f

Please sign in to comment.