diff --git a/README.md b/README.md index 9b02ddc..773eb92 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,7 @@ Additional flags: ### Example Implementation -The repository includes an example handler implementation in `example/resourceslice/handler.go` that: - -- Generates CPU resources between 1 and 10 cores -- Generates Memory resources between 1 and 5 GB -- Allocates 110 pods -- Uses a deterministic hash of the ResourceSlice name for consistent resource allocation +The repository includes an example handler implementation in `examples/cappedresources/handler.go`, that accepts every resource request but caps the amount of resources the resource slice can use if they exceed the configured thresholds. ## Creating Custom Handlers diff --git a/example/resourceslice/handler.go b/example/resourceslice/handler.go deleted file mode 100644 index fbe9d81..0000000 --- a/example/resourceslice/handler.go +++ /dev/null @@ -1,63 +0,0 @@ -// Package resourceslice contains an example handler for ResourceSlice. -package resourceslice - -import ( - "context" - "hash/fnv" - - authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/klog/v2" - ctrl "sigs.k8s.io/controller-runtime" - - rshandler "github.com/liqotech/resource-slice-class-controller-template/pkg/resourceslice/handler" -) - -// Handler implements the Handler interface for ResourceSlice. -type Handler struct{} - -// NewHandler creates a new ResourceSliceHandler. -func NewHandler() rshandler.Handler { - return &Handler{} -} - -// Handle processes a ResourceSlice. -func (h *Handler) Handle(_ context.Context, resourceSlice *authv1beta1.ResourceSlice) (ctrl.Result, error) { - // Generate and update resources in status - resources, err := h.generateResourcesFromName(resourceSlice.Name) - if err != nil { - return ctrl.Result{}, err - } - - resourceSlice.Status.Resources = resources - - klog.V(4).InfoS("Updated ResourceSlice status", - "name", resourceSlice.Name, - "namespace", resourceSlice.Namespace, - "cpu", resources.Cpu().String(), - "memory", resources.Memory().String(), - "pods", resources.Pods().String()) - - return ctrl.Result{}, nil -} - -// generateResourcesFromName generates resource quantities based on the ResourceSlice name. -func (h *Handler) generateResourcesFromName(name string) (corev1.ResourceList, error) { - // Create a hash of the name - hash := fnv.New32a() - if _, err := hash.Write([]byte(name)); err != nil { - return nil, err - } - hashVal := hash.Sum32() - - // Use the hash to generate resource quantities (between 1 and 10) - cpuCount := (hashVal%10 + 1) - memoryGB := (hashVal%5 + 1) - - return corev1.ResourceList{ - corev1.ResourceCPU: *resource.NewQuantity(int64(cpuCount), resource.DecimalSI), - corev1.ResourceMemory: *resource.NewQuantity(int64(memoryGB*1024*1024*1024), resource.BinarySI), - corev1.ResourcePods: *resource.NewQuantity(110, resource.DecimalSI), - }, nil -} diff --git a/examples/cappedresources/doc.go b/examples/cappedresources/doc.go new file mode 100644 index 0000000..b816c5c --- /dev/null +++ b/examples/cappedresources/doc.go @@ -0,0 +1,3 @@ +// Package cappedresources contains an handler that accepts every resource request but caps +// the amount of resources the resource slice can use if they exceed the configured thresholds. +package cappedresources diff --git a/examples/cappedresources/handler.go b/examples/cappedresources/handler.go new file mode 100644 index 0000000..6c6c8cf --- /dev/null +++ b/examples/cappedresources/handler.go @@ -0,0 +1,58 @@ +package cappedresources + +import ( + "context" + + authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1" + corev1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + + rshandler "github.com/liqotech/resource-slice-class-controller-template/pkg/resourceslice/handler" +) + +// Handler implements the Handler interface for ResourceSlice. +type Handler struct { + capResources corev1.ResourceList +} + +// NewHandler creates a new capped resources handler. +func NewHandler(maxResources corev1.ResourceList) rshandler.Handler { + return &Handler{ + capResources: maxResources, + } +} + +// Handle processes the ResourceSlice. +func (h *Handler) Handle(_ context.Context, resourceSlice *authv1beta1.ResourceSlice) (ctrl.Result, error) { + // Generate and update resources in status + resources := h.getCappedResources(resourceSlice.Spec.Resources) + + resourceSlice.Status.Resources = resources + + klog.InfoS("Updated ResourceSlice status", + "name", resourceSlice.Name, + "namespace", resourceSlice.Namespace, + "cpu", resources.Cpu().String(), + "memory", resources.Memory().String(), + "pods", resources.Pods().String()) + + return ctrl.Result{}, nil +} + +// getCappedResources sets the requested resources, but caps the amount of resources +// to the maximum resources defined in the handler. +func (h *Handler) getCappedResources(reqResources corev1.ResourceList) corev1.ResourceList { + cappedResources := corev1.ResourceList{} + + for name, reqQuantity := range reqResources { + capQuantity, ok := h.capResources[name] + if ok && reqQuantity.Cmp(capQuantity) > 0 { + cappedResources[name] = capQuantity + } else { + cappedResources[name] = reqQuantity + } + } + + return cappedResources +} diff --git a/main.go b/main.go index 241db62..1ec81d5 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Package main contains an an example main to setup and run a controller handling a ResourceSlice class +// Package main contains an an example main to setup and run a controller handling a ResourceSlice class. package main import ( @@ -6,6 +6,8 @@ import ( "os" authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -14,7 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - examplehandler "github.com/liqotech/resource-slice-class-controller-template/example/resourceslice" + cappedresources "github.com/liqotech/resource-slice-class-controller-template/examples/cappedresources" "github.com/liqotech/resource-slice-class-controller-template/pkg/controller" ) @@ -66,7 +68,18 @@ func main() { } // Create the handler - rsHandler := examplehandler.NewHandler() + cappedQuantities := map[corev1.ResourceName]string{ + corev1.ResourceCPU: "4", + corev1.ResourceMemory: "8Gi", + corev1.ResourcePods: "110", + corev1.ResourceEphemeralStorage: "20Gi", + } + parsedQuantities, err := parseQuantities(cappedQuantities) + if err != nil { + klog.Errorf("unable to parse quantities: %v", err) + os.Exit(1) + } + rsHandler := cappedresources.NewHandler(parsedQuantities) if err = controller.NewResourceSliceReconciler( mgr.GetClient(), @@ -94,3 +107,15 @@ func main() { os.Exit(1) } } + +func parseQuantities(quantities map[corev1.ResourceName]string) (corev1.ResourceList, error) { + resources := corev1.ResourceList{} + for name, quantity := range quantities { + qnt, err := resource.ParseQuantity(quantity) + if err != nil { + return nil, err + } + resources[name] = qnt + } + return resources, nil +} diff --git a/pkg/controller/resource_slice_controller.go b/pkg/controller/resource_slice_controller.go index cf4fdd9..5de0938 100644 --- a/pkg/controller/resource_slice_controller.go +++ b/pkg/controller/resource_slice_controller.go @@ -4,13 +4,17 @@ package controller import ( "context" "fmt" + "strconv" authv1beta1 "github.com/liqotech/liqo/apis/authentication/v1beta1" + "github.com/liqotech/liqo/pkg/consts" "github.com/liqotech/liqo/pkg/liqo-controller-manager/authentication" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -40,8 +44,16 @@ func NewResourceSliceReconciler(cl client.Client, scheme *runtime.Scheme, record // SetupWithManager sets up the controller with the Manager. func (r *ResourceSliceReconciler) SetupWithManager(mgr ctrl.Manager) error { + // generate the predicate to filter just the ResourceSlices that are replicated + // (i.e., the ones for which we have the role of provider) + replicatedResSliceFilter, err := predicate.LabelSelectorPredicate(replicatedResourcesLabelSelector()) + if err != nil { + klog.Error(err) + return err + } + return ctrl.NewControllerManagedBy(mgr). - For(&authv1beta1.ResourceSlice{}). + For(&authv1beta1.ResourceSlice{}, builder.WithPredicates(replicatedResSliceFilter)). WithEventFilter(predicate.NewPredicateFuncs(func(obj client.Object) bool { resourceSlice, ok := obj.(*authv1beta1.ResourceSlice) if !ok { @@ -101,3 +113,20 @@ func (r *ResourceSliceReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Return the reconciliation result return res, nil } + +// replicatedResourcesLabelSelector is an helper function which returns a label selector to list all the replicated resources. +func replicatedResourcesLabelSelector() metav1.LabelSelector { + return metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: consts.ReplicationOriginLabel, + Operator: metav1.LabelSelectorOpExists, + }, + { + Key: consts.ReplicationStatusLabel, + Operator: metav1.LabelSelectorOpIn, + Values: []string{strconv.FormatBool(true)}, + }, + }, + } +}