Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flavour aggregation (#2) #86

Merged
merged 1 commit into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading