diff --git a/apis/advertisement/v1alpha1/discovery_types.go b/apis/advertisement/v1alpha1/discovery_types.go index d92b888..744a322 100644 --- a/apis/advertisement/v1alpha1/discovery_types.go +++ b/apis/advertisement/v1alpha1/discovery_types.go @@ -32,7 +32,7 @@ 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.FlavourSelector `json:"selector"` // This flag indicates that needs to be enstablished a subscription to the provider in case a match is found. // In order to have periodic updates of the status of the matching Flavour diff --git a/apis/advertisement/v1alpha1/zz_generated.deepcopy.go b/apis/advertisement/v1alpha1/zz_generated.deepcopy.go index f479f32..9d747cb 100644 --- a/apis/advertisement/v1alpha1/zz_generated.deepcopy.go +++ b/apis/advertisement/v1alpha1/zz_generated.deepcopy.go @@ -19,6 +19,7 @@ package v1alpha1 import ( + nodecorev1alpha1 "github.com/fluidos-project/node/apis/nodecore/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -84,7 +85,11 @@ func (in *DiscoveryList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DiscoverySpec) DeepCopyInto(out *DiscoverySpec) { *out = *in - in.Selector.DeepCopyInto(&out.Selector) + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(nodecorev1alpha1.FlavourSelector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DiscoverySpec. diff --git a/apis/nodecore/v1alpha1/common.go b/apis/nodecore/v1alpha1/common.go index f340480..4dfab3f 100644 --- a/apis/nodecore/v1alpha1/common.go +++ b/apis/nodecore/v1alpha1/common.go @@ -45,9 +45,9 @@ type NodeIdentity struct { } // toString() returns a string representation of the GenericRef. -func (r GenericRef) toString() string { +/* func (r GenericRef) toString() string { if r.Namespace != "" { return r.Namespace + "/" + r.Name } return r.Name -} +} */ diff --git a/apis/nodecore/v1alpha1/solver_types.go b/apis/nodecore/v1alpha1/solver_types.go index 22accff..533ed98 100644 --- a/apis/nodecore/v1alpha1/solver_types.go +++ b/apis/nodecore/v1alpha1/solver_types.go @@ -80,7 +80,7 @@ type RangeSelector struct { type SolverSpec struct { // Selector contains the flavour requirements for the solver. - Selector FlavourSelector `json:"selector"` + Selector *FlavourSelector `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. diff --git a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go index 4ea1249..4f86197 100644 --- a/apis/nodecore/v1alpha1/zz_generated.deepcopy.go +++ b/apis/nodecore/v1alpha1/zz_generated.deepcopy.go @@ -495,7 +495,11 @@ func (in *SolverList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SolverSpec) DeepCopyInto(out *SolverSpec) { *out = *in - in.Selector.DeepCopyInto(&out.Selector) + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(FlavourSelector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SolverSpec. diff --git a/apis/reservation/v1alpha1/contract_types.go b/apis/reservation/v1alpha1/contract_types.go index 7e2dd65..cab5cd8 100644 --- a/apis/reservation/v1alpha1/contract_types.go +++ b/apis/reservation/v1alpha1/contract_types.go @@ -41,7 +41,7 @@ type ContractSpec struct { // 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. - Partition Partition `json:"partition,omitempty"` + Partition *Partition `json:"partition,omitempty"` // This is the Node identity of the buyer FLUIDOS Node. Buyer nodecorev1alpha1.NodeIdentity `json:"buyer"` diff --git a/apis/reservation/v1alpha1/reservation_types.go b/apis/reservation/v1alpha1/reservation_types.go index 2c1b837..4705be0 100644 --- a/apis/reservation/v1alpha1/reservation_types.go +++ b/apis/reservation/v1alpha1/reservation_types.go @@ -46,7 +46,7 @@ type ReservationSpec struct { Seller nodecorev1alpha1.NodeIdentity `json:"seller"` // Parition is the partition of the flavour that is being reserved - Partition Partition `json:"partition,omitempty"` + Partition *Partition `json:"partition,omitempty"` // Reserve indicates if the reservation is a reserve or not Reserve bool `json:"reserve,omitempty"` diff --git a/apis/reservation/v1alpha1/transaction_types.go b/apis/reservation/v1alpha1/transaction_types.go index dc26a29..dbe6e58 100644 --- a/apis/reservation/v1alpha1/transaction_types.go +++ b/apis/reservation/v1alpha1/transaction_types.go @@ -31,7 +31,7 @@ type TransactionSpec struct { ClusterID string `json:"clusterID"` // Partition is the partition of the flavour that is being reserved - Partition Partition `json:"partition,omitempty"` + Partition *Partition `json:"partition,omitempty"` // StartTime is the time at which the reservation should start StartTime string `json:"startTime,omitempty"` diff --git a/apis/reservation/v1alpha1/zz_generated.deepcopy.go b/apis/reservation/v1alpha1/zz_generated.deepcopy.go index 9a77e86..bbcfeef 100644 --- a/apis/reservation/v1alpha1/zz_generated.deepcopy.go +++ b/apis/reservation/v1alpha1/zz_generated.deepcopy.go @@ -85,7 +85,11 @@ func (in *ContractList) DeepCopyObject() runtime.Object { func (in *ContractSpec) DeepCopyInto(out *ContractSpec) { *out = *in in.Flavour.DeepCopyInto(&out.Flavour) - in.Partition.DeepCopyInto(&out.Partition) + if in.Partition != nil { + in, out := &in.Partition, &out.Partition + *out = new(Partition) + (*in).DeepCopyInto(*out) + } out.Buyer = in.Buyer out.Seller = in.Seller out.SellerCredentials = in.SellerCredentials @@ -223,7 +227,11 @@ func (in *ReservationSpec) DeepCopyInto(out *ReservationSpec) { *out = *in out.Buyer = in.Buyer out.Seller = in.Seller - in.Partition.DeepCopyInto(&out.Partition) + if in.Partition != nil { + in, out := &in.Partition, &out.Partition + *out = new(Partition) + (*in).DeepCopyInto(*out) + } out.PeeringCandidate = in.PeeringCandidate } @@ -317,7 +325,11 @@ func (in *TransactionList) DeepCopyObject() runtime.Object { func (in *TransactionSpec) DeepCopyInto(out *TransactionSpec) { *out = *in out.Buyer = in.Buyer - in.Partition.DeepCopyInto(&out.Partition) + if in.Partition != nil { + in, out := &in.Partition, &out.Partition + *out = new(Partition) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TransactionSpec. diff --git a/cmd/rear-controller/main.go b/cmd/rear-controller/main.go index fb1686d..9f5beec 100644 --- a/cmd/rear-controller/main.go +++ b/cmd/rear-controller/main.go @@ -144,20 +144,39 @@ func main() { os.Exit(1) } + // Periodically clear the transaction cache if err := mgr.Add(manager.RunnableFunc(gw.CacheRefresher(flags.REFRESH_CACHE_INTERVAL))); err != nil { klog.Errorf("Unable to set up transaction cache refresher: %s", err) os.Exit(1) } + // Periodically check if Liqo is ready + if err := mgr.Add(manager.RunnableFunc(gw.LiqoChecker(flags.LIQO_CHECK_INTERVAL))); err != nil { + klog.Errorf("Unable to set up Liqo checker: %s", err) + os.Exit(1) + } + + // Start the REAR Gateway HTTP server + if err := mgr.Add(manager.RunnableFunc(gw.Start)); err != nil { + klog.Errorf("Unable to set up Gateway HTTP server: %s", err) + os.Exit(1) + } + + // Start the REAR GRPC server + if err := mgr.Add(manager.RunnableFunc(grpcServer.Start)); err != nil { + klog.Errorf("Unable to set up Gateway GRPC server: %s", err) + os.Exit(1) + } + // Start the REAR Gateway HTTP server - go func() { - gw.StartHttpServer() - }() + /* go func() { + gw.Start() + }() */ // Start the REAR GRPC server - go func() { + /* go func() { grpcServer.Start() - }() + }() */ // TODO: Uncomment this when the webhook is ready. For now it does not work (Ale) // pcv := discoverymanager.NewPCValidator(mgr.GetClient()) diff --git a/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml b/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml index f5895a7..d716922 100644 --- a/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml +++ b/deployments/node/crds/nodecore.fluidos.eu_solvers.yaml @@ -201,7 +201,6 @@ spec: type: object required: - intentID - - selector type: object status: description: SolverStatus defines the observed state of Solver diff --git a/deployments/node/files/fluidos-rear-controller-ClusterRole.yaml b/deployments/node/files/fluidos-rear-controller-ClusterRole.yaml index 684e3f9..044e17c 100644 --- a/deployments/node/files/fluidos-rear-controller-ClusterRole.yaml +++ b/deployments/node/files/fluidos-rear-controller-ClusterRole.yaml @@ -59,6 +59,14 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch - apiGroups: - nodecore.fluidos.eu resources: diff --git a/examples/kind/setup.sh b/examples/kind/setup.sh index 3b4a0cd..0c2c893 100644 --- a/examples/kind/setup.sh +++ b/examples/kind/setup.sh @@ -32,6 +32,8 @@ helm install node ../../deployments/node -n fluidos \ --set networkManager.configMaps.nodeIdentity.ip=$consumer_controlplane_ip:$consumer_node_port \ --set networkManager.configMaps.providers.local=$provider_controlplane_ip:$provider_node_port +liqoctl install kind --cluster-name fluidos-consumer + export KUBECONFIG=$PWD/provider/config kubectl apply -f ../../deployments/node/crds @@ -49,4 +51,6 @@ helm install node ../../deployments/node -n fluidos \ --set networkManager.configMaps.nodeIdentity.ip=$provider_controlplane_ip:$provider_node_port \ --set networkManager.configMaps.providers.local=$consumer_controlplane_ip:$consumer_node_port +liqoctl install kind --cluster-name fluidos-provider + diff --git a/pkg/rear-controller/gateway/client.go b/pkg/rear-controller/gateway/client.go index d964f3b..88adda5 100644 --- a/pkg/rear-controller/gateway/client.go +++ b/pkg/rear-controller/gateway/client.go @@ -33,20 +33,28 @@ import ( // 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) { + err := checkLiqoReadiness(g.LiqoReady) + if err != nil { + return nil, err + } + var transaction models.Transaction body := models.ReserveRequest{ FlavourID: flavourID, Buyer: models.NodeIdentity{ - NodeID: reservation.Spec.Buyer.NodeID, - IP: reservation.Spec.Buyer.IP, - Domain: reservation.Spec.Buyer.Domain, + NodeID: g.ID.NodeID, + IP: g.ID.IP, + Domain: g.ID.Domain, }, - Partition: parseutil.ParsePartition(reservation.Spec.Partition), } klog.Infof("Reservation %s for flavour %s", reservation.Name, flavourID) + if reservation.Spec.Partition != nil { + body.Partition = parseutil.ParsePartition(reservation.Spec.Partition) + } + selectorBytes, err := json.Marshal(body) if err != nil { return nil, err @@ -56,13 +64,13 @@ func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1 bodyBytes := bytes.NewBuffer(selectorBytes) url := fmt.Sprintf("http://%s%s%s", reservation.Spec.Seller.IP, RESERVE_FLAVOUR_PATH, flavourID) - klog.Infof("Sending POST request to %s", url) + klog.Infof("Sending request to %s", url) resp, err := makeRequest("POST", url, bodyBytes) - defer resp.Body.Close() if err != nil { return nil, err } + defer resp.Body.Close() // Check if the response status code is 200 (OK) if resp.StatusCode != http.StatusOK { @@ -82,7 +90,7 @@ func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1 return &transaction, fmt.Errorf("transactionID is empty") } - klog.Infof("Flavour %s reserved: transaction %v", flavourID, transaction) + klog.Infof("Flavour %s reserved: transaction ID %s", flavourID, transaction.TransactionID) g.addNewTransacion(transaction) @@ -91,6 +99,11 @@ func (g *Gateway) ReserveFlavour(ctx context.Context, reservation *reservationv1 // PurchaseFlavour purchases a flavour with the given flavourID func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, seller nodecorev1alpha1.NodeIdentity) (*models.ResponsePurchase, error) { + err := checkLiqoReadiness(g.LiqoReady) + if err != nil { + return nil, err + } + var purchase models.ResponsePurchase // Check if the transaction exists @@ -113,10 +126,10 @@ func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, sel url := fmt.Sprintf("http://%s%s%s", seller.IP, PURCHASE_FLAVOUR_PATH, transactionID) resp, err := makeRequest("POST", url, bodyBytes) - defer resp.Body.Close() if err != nil { return nil, err } + defer resp.Body.Close() // Check if the response status code is 200 (OK) if resp.StatusCode != http.StatusOK { @@ -131,18 +144,24 @@ func (g *Gateway) PurchaseFlavour(ctx context.Context, transactionID string, sel } // SearchFlavour 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(selector nodecorev1alpha1.FlavourSelector) ([]*nodecorev1alpha1.Flavour, error) { - // Marshal the selector into JSON bytes - s := parseutil.ParseFlavourSelector(selector) +func (g *Gateway) DiscoverFlavours(selector *nodecorev1alpha1.FlavourSelector) ([]*nodecorev1alpha1.Flavour, error) { + err := checkLiqoReadiness(g.LiqoReady) + if err != nil { + return nil, err + } - // Create the Flavour CR from the first flavour in the array of Flavour + var s *models.Selector var flavoursCR []*nodecorev1alpha1.Flavour + if selector == nil { + s = parseutil.ParseFlavourSelector(selector) + } + providers := getters.GetLocalProviders(context.Background(), g.client) // Send the POST request to all the servers in the list - for _, ADDRESS := range providers { - flavour, err := searchFlavour(s, ADDRESS) + for _, provider := range providers { + flavour, err := discover(s, provider) if err != nil { klog.Errorf("Error when searching Flavour: %s", err) return nil, err @@ -153,3 +172,18 @@ func (g *Gateway) DiscoverFlavours(selector nodecorev1alpha1.FlavourSelector) ([ klog.Infof("Found %d flavours", len(flavoursCR)) return flavoursCR, nil } + +func discover(s *models.Selector, provider string) (*nodecorev1alpha1.Flavour, error) { + if s != nil { + return searchFlavourWithSelector(s, provider) + } + return searchFlavour(provider) +} + +func checkLiqoReadiness(b bool) error { + if !b { + klog.Errorf("Liqo is not ready, please check or wait for the Liqo installation") + return fmt.Errorf("Liqo is not ready, please check or wait for the Liqo installation") + } + return nil +} diff --git a/pkg/rear-controller/gateway/gateway.go b/pkg/rear-controller/gateway/gateway.go index 31eca84..f100a19 100644 --- a/pkg/rear-controller/gateway/gateway.go +++ b/pkg/rear-controller/gateway/gateway.go @@ -16,16 +16,21 @@ package gateway import ( "context" + "fmt" "net/http" "time" "github.com/gorilla/mux" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "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/consts" "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/tools" ) @@ -36,6 +41,7 @@ import ( //+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=core,resources=configmaps,verbs=get;list;watch +//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch const ( LIST_FLAVOURS_PATH = "/api/listflavours" @@ -54,25 +60,46 @@ type Gateway struct { // client is the Kubernetes client client client.Client + + // Readyness of the Gateway. It is set when liqo is installed + LiqoReady bool + + // The Liqo ClusterID + ClusterID string } func NewGateway(c client.Client) *Gateway { return &Gateway{ client: c, Transactions: make(map[string]models.Transaction), + LiqoReady: false, + ClusterID: "", } } -// StartHttpServer starts a new HTTP server -func (g *Gateway) StartHttpServer() { +// Start starts a new HTTP server +func (g *Gateway) Start(ctx context.Context) error { + klog.Info("Getting FLUIDOS Node identity...") + + nodeIdentity := getters.GetNodeIdentity(ctx, g.client) + if nodeIdentity == nil { + klog.Info("Error getting FLUIDOS Node identity") + return fmt.Errorf("Error getting FLUIDOS Node identity") + } + + g.RegisterNodeIdentity(nodeIdentity) + router := mux.NewRouter() // middleware for debugging purposes // router.Use(loggingMiddleware) + // middleware for readiness + router.Use(g.readinessMiddleware) + // Gateway endpoints router.HandleFunc(LIST_FLAVOURS_PATH, g.getFlavours).Methods("GET") - router.HandleFunc(LIST_FLAVOUR_BY_ID_PATH+"{flavourID}", g.getFlavourByID).Methods("GET") + //router.HandleFunc(LIST_FLAVOUR_BY_ID_PATH+"{flavourID}", g.getFlavourByID).Methods("GET") router.HandleFunc(LIST_FLAVOURS_BY_SELECTOR_PATH, g.getFlavoursBySelector).Methods("POST") router.HandleFunc(RESERVE_FLAVOUR_PATH+"{flavourID}", g.reserveFlavour).Methods("POST") router.HandleFunc(PURCHASE_FLAVOUR_PATH+"{transactionID}", g.purchaseFlavour).Methods("POST") @@ -85,8 +112,11 @@ func (g *Gateway) StartHttpServer() { // Start server HTTP klog.Infof("Starting HTTP server on port %s", flags.HTTP_PORT) - klog.Fatal(srv.ListenAndServe()) + return srv.ListenAndServe() +} +func (g *Gateway) RegisterNodeIdentity(nodeIdentity *nodecorev1alpha1.NodeIdentity) { + g.ID = nodeIdentity } // Only for debugging purposes @@ -97,6 +127,17 @@ func (g *Gateway) StartHttpServer() { }) } */ +func (g *Gateway) readinessMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !g.LiqoReady { + klog.Infof("Liqo not ready yet") + w.WriteHeader(http.StatusServiceUnavailable) + return + } + next.ServeHTTP(w, r) + }) +} + func (g *Gateway) CacheRefresher(interval time.Duration) func(ctx context.Context) error { return func(ctx context.Context) error { return wait.PollUntilContextCancel(ctx, interval, false, g.refreshCache) @@ -115,3 +156,36 @@ func (g *Gateway) refreshCache(ctx context.Context) (bool, error) { } return false, nil } + +func (g *Gateway) LiqoChecker(interval time.Duration) func(ctx context.Context) error { + return func(ctx context.Context) error { + return wait.PollUntilContextCancel(ctx, interval, false, g.checkLiqoReadiness) + } +} + +func (g *Gateway) checkLiqoReadiness(ctx context.Context) (bool, error) { + klog.Infof("Checking Liqo readiness") + if g.LiqoReady && g.ClusterID != "" { + return true, nil + } + + var cm corev1.ConfigMap + err := g.client.Get(ctx, types.NamespacedName{Name: consts.LIQO_CLUSTERID_CONFIGMAP_NAME, Namespace: consts.LIQO_NAMESPACE}, &cm) + if err != nil { + if client.IgnoreNotFound(err) != nil { + klog.Errorf("Error when retrieving Liqo ConfigMap: %s", err) + } + klog.Infof("Liqo not ready yet. ConfigMap not found") + return false, nil + } + + if cm.Data["CLUSTER_ID"] != "" && cm.Data["CLUSTER_NAME"] != "" { + klog.Infof("Liqo is ready") + g.LiqoReady = true + g.ClusterID = cm.Data["CLUSTER_ID"] + return true, nil + } + + klog.Infof("Liqo not ready yet") + return false, nil +} diff --git a/pkg/rear-controller/gateway/provider.go b/pkg/rear-controller/gateway/provider.go index 04e9249..917123e 100644 --- a/pkg/rear-controller/gateway/provider.go +++ b/pkg/rear-controller/gateway/provider.go @@ -43,6 +43,8 @@ import ( func (g *Gateway) getFlavours(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") + klog.Infof("Processing request for getting all Flavours...") + flavours, err := services.GetAllFlavours(g.client) if err != nil { klog.Errorf("Error getting all the Flavour CRs: %s", err) @@ -50,20 +52,43 @@ func (g *Gateway) getFlavours(w http.ResponseWriter, r *http.Request) { return } - klog.Infof("Listing all the Flavour CRs: %d", len(flavours)) + klog.Infof("Found %d Flavours in the cluster", len(flavours)) + + // Filtering only the available flavours + for i, f := range flavours { + if !f.Spec.OptionalFields.Availability { + flavours = append(flavours[:i], flavours[i+1:]...) + } + } - // Export the result as a list of Flavour struct (no CRs) - var flavoursParsed []models.Flavour + klog.Infof("Available Flavours: %d", len(flavours)) + if len(flavours) == 0 { + klog.Infof("No available Flavours found") + http.Error(w, "No Flavours found", http.StatusNotFound) + return + } + + // Select the flavour with the max CPU + max := resource.MustParse("0") + var selected nodecorev1alpha1.Flavour for _, f := range flavours { - flavoursParsed = append(flavoursParsed, parseutil.ParseFlavour(f)) + if f.Spec.Characteristics.Cpu.Cmp(max) == 1 { + max = f.Spec.Characteristics.Cpu + selected = f + } } - // Encode the FlavourList as JSON and write it to the response writer - encodeResponse(w, flavoursParsed) + 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) } // getFlavourByID gets the flavour CR from the cluster that matches the flavourID -func (g *Gateway) getFlavourByID(w http.ResponseWriter, r *http.Request) { +/* func (g *Gateway) getFlavourByID(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") // Get the flavourID from the URL @@ -89,13 +114,13 @@ func (g *Gateway) getFlavourByID(w http.ResponseWriter, r *http.Request) { // Encode the FlavourList as JSON and write it to the response writer encodeResponse(w, flavourParsed) -} +} */ // getFlavourBySelectorHandler gets the flavour CRs from the cluster that match the selector func (g *Gateway) getFlavoursBySelector(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - klog.Infof("Getting Flavours by selector...") + klog.Infof("Processing request for getting Flavours by selector...") // Read the request body body, err := io.ReadAll(r.Body) @@ -119,9 +144,9 @@ func (g *Gateway) getFlavoursBySelector(w http.ResponseWriter, r *http.Request) return } - klog.Infof("Listing all the Flavour CRs: %d", len(flavours)) + klog.Infof("Found %d Flavours in the cluster", len(flavours)) - // Filter flavours from array maintaining only the Spec.Available == true + // Filtering only the available flavours for i, f := range flavours { if !f.Spec.OptionalFields.Availability { flavours = append(flavours[:i], flavours[i+1:]...) @@ -172,7 +197,7 @@ func (g *Gateway) getFlavoursBySelector(w http.ResponseWriter, r *http.Request) klog.Infof("Flavour parsed: %v", parsed) - // Encode the FlavourList as JSON and write it to the response writer + // Encode the Flavour as JSON and write it to the response writer encodeResponse(w, parsed) } diff --git a/pkg/rear-controller/gateway/services.go b/pkg/rear-controller/gateway/services.go index 7520f5f..4dbe5a0 100644 --- a/pkg/rear-controller/gateway/services.go +++ b/pkg/rear-controller/gateway/services.go @@ -27,8 +27,7 @@ import ( "github.com/fluidos-project/node/pkg/utils/resourceforge" ) -// TODO: This function does not work without a selector, it should do a GET request to the seller -func searchFlavour(selector models.Selector, addr string) (*nodecorev1alpha1.Flavour, error) { +func searchFlavourWithSelector(selector *models.Selector, addr string) (*nodecorev1alpha1.Flavour, error) { var flavour models.Flavour // Marshal the selector into JSON bytes @@ -60,10 +59,39 @@ func searchFlavour(selector models.Selector, addr string) (*nodecorev1alpha1.Fla return flavourCR, nil } +func searchFlavour(addr string) (*nodecorev1alpha1.Flavour, error) { + var flavour models.Flavour + + url := fmt.Sprintf("http://%s%s", addr, LIST_FLAVOURS_PATH) + + resp, err := makeRequest("GET", url, nil) + if err != nil { + return nil, err + } + + // Check if the response status code is 200 (OK) + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("received non-OK response status code: %d", resp.StatusCode) + } + + if err := json.NewDecoder(resp.Body).Decode(&flavour); err != nil { + klog.Errorf("Error decoding the response body: %s", err) + return nil, err + } + + flavourCR := resourceforge.ForgeFlavourFromObj(flavour) + + return flavourCR, nil +} + func makeRequest(method, url string, body *bytes.Buffer) (*http.Response, error) { httpClient := &http.Client{} + if body == nil { + body = bytes.NewBuffer([]byte{}) + } + req, err := http.NewRequest(method, url, body) if err != nil { klog.Errorf("Error creating the request: %s", err) diff --git a/pkg/rear-controller/gateway/utils.go b/pkg/rear-controller/gateway/utils.go index a1d1827..c96b8b0 100644 --- a/pkg/rear-controller/gateway/utils.go +++ b/pkg/rear-controller/gateway/utils.go @@ -31,14 +31,14 @@ import ( ) // buildSelector builds a selector from a request body -func buildSelector(body []byte) (models.Selector, error) { +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 + return &models.Selector{}, err } - return selector, nil + return &selector, nil } // getTransaction returns a transaction from the transactions map @@ -89,9 +89,9 @@ func encodeResponse(w http.ResponseWriter, data interface{}) { } const ( - authTokenSecretNamePrefix = "remote-token-" + //authTokenSecretNamePrefix = "remote-token-" - tokenKey = "token" + //tokenKey = "token" liqoNamespace = "liqo" ) diff --git a/pkg/rear-controller/grpc/liqo-resource-manager.go b/pkg/rear-controller/grpc/liqo-resource-manager.go index d5372d2..0d9e206 100644 --- a/pkg/rear-controller/grpc/liqo-resource-manager.go +++ b/pkg/rear-controller/grpc/liqo-resource-manager.go @@ -46,21 +46,20 @@ func NewGrpcServer(cl client.Client) *grpcServer { } } -func (s *grpcServer) Start() { +func (s *grpcServer) Start(ctx context.Context) error { grpcUrl := ":" + flags.GRPC_PORT // gRPC Configuration klog.Info("Configuring gRPC Server") lis, err := net.Listen("tcp", grpcUrl) if err != nil { - log.Fatalf("gRPC failed to listen: %v", err) + klog.Infof("gRPC failed to listen: %v", err) + return fmt.Errorf("gRPC failed to listen: %v", err) } klog.Infof("gRPC Server Listening on %s", grpcUrl) // gRPC Server start listener - if err := s.Server.Serve(lis); err != nil { - log.Fatalf("gRPC failed to serve: %v", err) - } + return s.Server.Serve(lis) } /* func (s *grpcServer) RegisterContractHandler(ch connector.ContractHandler) { @@ -92,7 +91,7 @@ func (s *grpcServer) Subscribe(req *resourcemonitors.Empty, srv resourcemonitors fmt.Println("Subscribe") - s.NotifyChange(context.Background(), &resourcemonitors.ClusterIdentity{ClusterID: resourcemonitors.AllClusterIDs}) + _ = s.NotifyChange(context.Background(), &resourcemonitors.ClusterIdentity{ClusterID: resourcemonitors.AllClusterIDs}) for { select { @@ -101,6 +100,7 @@ func (s *grpcServer) Subscribe(req *resourcemonitors.Empty, srv resourcemonitors return nil } } + } func (s *grpcServer) NotifyChange(ctx context.Context, req *resourcemonitors.ClusterIdentity) error { @@ -108,7 +108,7 @@ func (s *grpcServer) NotifyChange(ctx context.Context, req *resourcemonitors.Clu if s.stream == nil { return fmt.Errorf("you must first subscribe a controller manager to notify a change") } else { - s.stream.Send(req) + _ = s.stream.Send(req) } return nil } @@ -128,5 +128,5 @@ func (s *grpcServer) GetOfferResourcesByClusterID(clusterID string) (*corev1.Res } func (s *grpcServer) UpdatePeeringOffer(clusterID string) { - s.NotifyChange(context.Background(), &resourcemonitors.ClusterIdentity{ClusterID: clusterID}) + _ = s.NotifyChange(context.Background(), &resourcemonitors.ClusterIdentity{ClusterID: clusterID}) } diff --git a/pkg/rear-controller/grpc/service.go b/pkg/rear-controller/grpc/service.go index 25d1a83..0179aa9 100644 --- a/pkg/rear-controller/grpc/service.go +++ b/pkg/rear-controller/grpc/service.go @@ -59,7 +59,7 @@ func multipleContractLogic(contracts []reservationv1alpha1.Contract) *corev1.Res } // This function adds the resources of a contract to the existing resourceList -func addResources(resources corev1.ResourceList, partition reservationv1alpha1.Partition) *corev1.ResourceList { +func addResources(resources corev1.ResourceList, partition *reservationv1alpha1.Partition) *corev1.ResourceList { for key, value := range *mapQuantityToResourceList(partition) { if prevRes, ok := resources[key]; !ok { resources[key] = value @@ -71,7 +71,7 @@ func addResources(resources corev1.ResourceList, partition reservationv1alpha1.P return &resources } -func mapQuantityToResourceList(partition reservationv1alpha1.Partition) *corev1.ResourceList { +func mapQuantityToResourceList(partition *reservationv1alpha1.Partition) *corev1.ResourceList { return &corev1.ResourceList{ corev1.ResourceCPU: partition.Cpu, corev1.ResourceMemory: partition.Memory, diff --git a/pkg/rear-manager/solver_controller.go b/pkg/rear-manager/solver_controller.go index 5a610f7..3dc666a 100644 --- a/pkg/rear-manager/solver_controller.go +++ b/pkg/rear-manager/solver_controller.go @@ -215,6 +215,7 @@ func (r *SolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr klog.Infof("ReserveAndBuy %s", reserveAndBuyStatus) switch reserveAndBuyStatus { case nodecorev1alpha1.PhaseIdle: + var partition *reservationv1alpha1.Partition klog.Infof("Creating the Reservation %s", req.NamespacedName.Name) // Create the Reservation var pc advertisementv1alpha1.PeeringCandidate @@ -226,8 +227,10 @@ func (r *SolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, err } - // Forge the Partition - partition := resourceforge.ForgePartition(solver.Spec.Selector) + if solver.Spec.Selector == nil { + // Forge the Partition + partition = resourceforge.ForgePartition(solver.Spec.Selector) + } // Get the NodeIdentity nodeIdentity := getters.GetNodeIdentity(ctx, r.Client) @@ -313,6 +316,7 @@ func (r *SolverReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr if solver.Spec.EnstablishPeering { if reserveAndBuyStatus == nodecorev1alpha1.PhaseSolved { // Peering phase to be implemented + klog.Infof("Solver %s Solved : Peering phase to be implemented", req.NamespacedName.Name) } } else { klog.Infof("Solver %s Solved : No need to enstablish a peering", req.NamespacedName.Name) @@ -357,7 +361,7 @@ func (r *SolverReconciler) searchPeeringCandidates(ctx context.Context, solver * // Filter the list of PeeringCandidates based on the Flavour Selector for _, p := range filtered { - res := common.FilterPeeringCandidate(&selector, &p) + res := common.FilterPeeringCandidate(selector, &p) if res { result = append(result, p) } diff --git a/pkg/utils/common/common.go b/pkg/utils/common/common.go index 82d62f7..d10f540 100644 --- a/pkg/utils/common/common.go +++ b/pkg/utils/common/common.go @@ -28,7 +28,7 @@ import ( ) // FilterFlavoursBySelector returns the Flavour CRs in the cluster that match the selector -func FilterFlavoursBySelector(flavours []nodecorev1alpha1.Flavour, selector models.Selector) ([]nodecorev1alpha1.Flavour, error) { +func FilterFlavoursBySelector(flavours []nodecorev1alpha1.Flavour, selector *models.Selector) ([]nodecorev1alpha1.Flavour, error) { var flavoursSelected []nodecorev1alpha1.Flavour // Get the Flavours that match the selector @@ -46,7 +46,7 @@ func FilterFlavoursBySelector(flavours []nodecorev1alpha1.Flavour, selector mode } // filterFlavour filters the Flavour CRs in the cluster that match the selector -func FilterFlavour(selector models.Selector, f nodecorev1alpha1.Flavour) bool { +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) @@ -54,27 +54,27 @@ func FilterFlavour(selector models.Selector, f nodecorev1alpha1.Flavour) bool { } if selector.MatchSelector != nil { - if selector.MatchSelector.Cpu == 0 && f.Spec.Characteristics.Cpu.CmpInt64(int64(selector.MatchSelector.Cpu)) != 0 { + 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 == 0 && f.Spec.Characteristics.Memory.CmpInt64(int64(selector.MatchSelector.Memory)) != 0 { + 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.EphemeralStorage == 0 && f.Spec.Characteristics.EphemeralStorage.CmpInt64(int64(selector.MatchSelector.EphemeralStorage)) < 0 { + 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 == 0 && f.Spec.Characteristics.PersistentStorage.CmpInt64(int64(selector.MatchSelector.Storage)) < 0 { + 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 == 0 && f.Spec.Characteristics.Gpu.CmpInt64(int64(selector.MatchSelector.Gpu)) < 0 { + 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) return false } @@ -82,47 +82,47 @@ func FilterFlavour(selector models.Selector, f nodecorev1alpha1.Flavour) bool { if selector.RangeSelector != nil && selector.MatchSelector == nil { - if selector.RangeSelector.MinCpu != 0 && f.Spec.Characteristics.Cpu.CmpInt64(int64(selector.RangeSelector.MinCpu)) < 0 { + 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 != 0 && f.Spec.Characteristics.Memory.CmpInt64(int64(selector.RangeSelector.MinMemory)) < 0 { + 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.MinEph != 0 && f.Spec.Characteristics.EphemeralStorage.CmpInt64(int64(selector.RangeSelector.MinEph)) < 0 { + 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 != 0 && f.Spec.Characteristics.PersistentStorage.CmpInt64(int64(selector.RangeSelector.MinStorage)) < 0 { + 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 } - if selector.RangeSelector.MinGpu != 0 && f.Spec.Characteristics.Gpu.CmpInt64(int64(selector.RangeSelector.MinGpu)) < 0 { + if selector.RangeSelector.MinGpu.CmpInt64(0) != 0 && f.Spec.Characteristics.Gpu.Cmp(selector.RangeSelector.MinGpu) < 0 { return false } - if selector.RangeSelector.MaxCpu != 0 && f.Spec.Characteristics.Cpu.CmpInt64(int64(selector.RangeSelector.MaxCpu)) > 0 { + if selector.RangeSelector.MaxCpu.CmpInt64(0) != 0 && f.Spec.Characteristics.Cpu.Cmp(selector.RangeSelector.MaxCpu) > 0 { return false } - if selector.RangeSelector.MaxMemory != 0 && f.Spec.Characteristics.Memory.CmpInt64(int64(selector.RangeSelector.MaxMemory)) > 0 { + if selector.RangeSelector.MaxMemory.CmpInt64(0) != 0 && f.Spec.Characteristics.Memory.Cmp(selector.RangeSelector.MaxMemory) > 0 { return false } - if selector.RangeSelector.MaxEph != 0 && f.Spec.Characteristics.EphemeralStorage.CmpInt64(int64(selector.RangeSelector.MaxEph)) > 0 { + if selector.RangeSelector.MaxEph.CmpInt64(0) != 0 && f.Spec.Characteristics.EphemeralStorage.Cmp(selector.RangeSelector.MaxEph) > 0 { return false } - if selector.RangeSelector.MaxStorage != 0 && f.Spec.Characteristics.PersistentStorage.CmpInt64(int64(selector.RangeSelector.MaxStorage)) > 0 { + if selector.RangeSelector.MaxStorage.CmpInt64(0) != 0 && f.Spec.Characteristics.PersistentStorage.Cmp(selector.RangeSelector.MaxStorage) > 0 { return false } - if selector.RangeSelector.MaxGpu != 0 && f.Spec.Characteristics.Gpu.CmpInt64(int64(selector.RangeSelector.MaxGpu)) > 0 { + if selector.RangeSelector.MaxGpu.CmpInt64(0) != 0 && f.Spec.Characteristics.Gpu.Cmp(selector.RangeSelector.MaxGpu) > 0 { return false } } @@ -132,13 +132,13 @@ func FilterFlavour(selector models.Selector, f nodecorev1alpha1.Flavour) bool { // 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) + s := parseutil.ParseFlavourSelector(selector) return FilterFlavour(s, pc.Spec.Flavour) } // 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 { +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") diff --git a/pkg/utils/consts/consts.go b/pkg/utils/consts/consts.go index c5d861e..99fd8bb 100644 --- a/pkg/utils/consts/consts.go +++ b/pkg/utils/consts/consts.go @@ -17,4 +17,6 @@ package consts const ( NETWORK_CONFIG_MAP_NAME = "fluidos-network-manager-config" NODE_IDENTITY_CONFIG_MAP_NAME = "fluidos-network-manager-identity" + LIQO_CLUSTERID_CONFIGMAP_NAME = "liqo-clusterid-configmap" + LIQO_NAMESPACE = "liqo" ) diff --git a/pkg/utils/flags/flags.go b/pkg/utils/flags/flags.go index 2f2b0f9..32eddd6 100644 --- a/pkg/utils/flags/flags.go +++ b/pkg/utils/flags/flags.go @@ -28,6 +28,7 @@ var ( EXPIRATION_TRANSACTION = 20 * time.Second EXPIRATION_CONTRACT = 365 * 24 * time.Hour REFRESH_CACHE_INTERVAL = 20 * time.Second + LIQO_CHECK_INTERVAL = 20 * time.Second ) var ( diff --git a/pkg/utils/models/gateway.go b/pkg/utils/models/gateway.go index 32df4ff..364a03b 100644 --- a/pkg/utils/models/gateway.go +++ b/pkg/utils/models/gateway.go @@ -30,5 +30,5 @@ type ReserveRequest struct { FlavourID string `json:"flavourID"` Buyer NodeIdentity `json:"buyerID"` ClusterID string `json:"clusterID"` - Partition Partition `json:"partition"` + Partition *Partition `json:"partition,omitempty"` } diff --git a/pkg/utils/models/models.go b/pkg/utils/models/models.go index a32a617..a741faf 100644 --- a/pkg/utils/models/models.go +++ b/pkg/utils/models/models.go @@ -16,6 +16,8 @@ package models import ( "time" + + "k8s.io/apimachinery/pkg/api/resource" ) // Flavour represents a Flavour object with its characteristics and policies. @@ -33,12 +35,12 @@ type Flavour struct { // Characteristics represents the characteristics of a Flavour, such as CPU and RAM. type Characteristics struct { - CPU int `json:"cpu,omitempty"` - Memory int `json:"memory,omitempty"` - PersistentStorage int `json:"storage,omitempty"` - EphemeralStorage int `json:"ephemeralStorage,omitempty"` - GPU int `json:"gpu,omitempty"` - Architecture string `json:"architecture,omitempty"` + CPU resource.Quantity `json:"cpu,omitempty"` + Memory resource.Quantity `json:"memory,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. @@ -49,10 +51,10 @@ type Policy struct { // Partitionable represents the partitioning properties of a Flavour, such as the minimum and incremental values of CPU and RAM. type Partitionable struct { - CPUMinimum int `json:"cpuMinimum"` - MemoryMinimum int `json:"memoryMinimum"` - CPUStep int `json:"cpuStep"` - MemoryStep int `json:"memoryStep"` + CPUMinimum resource.Quantity `json:"cpuMinimum"` + MemoryMinimum resource.Quantity `json:"memoryMinimum"` + CPUStep resource.Quantity `json:"cpuStep"` + MemoryStep resource.Quantity `json:"memoryStep"` } // Aggregatable represents the aggregation properties of a Flavour, such as the minimum instance count. @@ -90,23 +92,23 @@ type Selector struct { // MatchSelector represents the criteria for selecting Flavours through a strict match. type MatchSelector struct { - Cpu int `json:"cpu,omitempty"` - Memory int `json:"memory,omitempty"` - Storage int `json:"storage,omitempty"` - EphemeralStorage int `json:"ephemeralStorage,omitempty"` - Gpu int `json:"gpu,omitempty"` + Cpu resource.Quantity `json:"cpu,omitempty"` + Memory resource.Quantity `json:"memory,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 int `json:"minCpu,omitempty"` - MinMemory int `json:"minMemory,omitempty"` - MinStorage int `json:"minStorage,omitempty"` - MinEph int `json:"minEph,omitempty"` - MinGpu int `json:"minGpu,omitempty"` - MaxCpu int `json:"maxCpu,omitempty"` - MaxMemory int `json:"maxMemory,omitempty"` - MaxStorage int `json:"maxStorage,omitempty"` - MaxEph int `json:"maxEph,omitempty"` - MaxGpu int `json:"maxGpu,omitempty"` + MinCpu resource.Quantity `json:"minCpu,omitempty"` + MinMemory resource.Quantity `json:"minMemory,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"` + MaxStorage resource.Quantity `json:"maxStorage,omitempty"` + MaxEph resource.Quantity `json:"maxEph,omitempty"` + MaxGpu resource.Quantity `json:"maxGpu,omitempty"` } diff --git a/pkg/utils/models/reservation.go b/pkg/utils/models/reservation.go index fcfff55..7b7e5af 100644 --- a/pkg/utils/models/reservation.go +++ b/pkg/utils/models/reservation.go @@ -14,21 +14,23 @@ 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 int `json:"cpu"` - Memory int `json:"memory"` - EphemeralStorage int `json:"ephemeral-storage,omitempty"` - Gpu int `json:"gpu,omitempty"` - Storage int `json:"storage,omitempty"` + Architecture string `json:"architecture"` + Cpu resource.Quantity `json:"cpu"` + Memory resource.Quantity `json:"memory"` + EphemeralStorage resource.Quantity `json:"ephemeral-storage,omitempty"` + Gpu resource.Quantity `json:"gpu,omitempty"` + Storage resource.Quantity `json:"storage,omitempty"` } // Transaction contains information regarding the transaction for a flavour type Transaction struct { TransactionID string `json:"transactionID"` FlavourID string `json:"flavourID"` - Partition Partition `json:"partition"` + Partition *Partition `json:"partition,omitempty"` Buyer NodeIdentity `json:"buyer"` ClusterID string `json:"clusterID"` StartTime string `json:"startTime"` @@ -45,7 +47,7 @@ type Contract struct { SellerCredentials LiqoCredentials `json:"sellerCredentials"` ExpirationTime string `json:"expirationTime,omitempty"` ExtraInformation map[string]string `json:"extraInformation,omitempty"` - Partition Partition `json:"partition,omitempty"` + Partition *Partition `json:"partition,omitempty"` } // LiqoCredentials contains the credentials of a Liqo cluster to enstablish a peering. diff --git a/pkg/utils/parseutil/parseutil.go b/pkg/utils/parseutil/parseutil.go index a453ba3..9d46037 100644 --- a/pkg/utils/parseutil/parseutil.go +++ b/pkg/utils/parseutil/parseutil.go @@ -22,88 +22,58 @@ import ( ) // ParseFlavourSelector parses FlavourSelector into a Selector -func ParseFlavourSelector(selector nodecorev1alpha1.FlavourSelector) (s models.Selector) { +func ParseFlavourSelector(selector *nodecorev1alpha1.FlavourSelector) (s *models.Selector) { s.Architecture = selector.Architecture s.FlavourType = string(selector.FlavourType) if selector.MatchSelector != nil { - cpu, _ := selector.MatchSelector.Cpu.AsInt64() - memory, _ := selector.MatchSelector.Memory.AsInt64() - ephStorage, _ := selector.MatchSelector.EphemeralStorage.AsInt64() - storage, _ := selector.MatchSelector.Storage.AsInt64() - gpu, _ := selector.MatchSelector.Gpu.AsInt64() - s.MatchSelector = &models.MatchSelector{ - Cpu: int(cpu), - Memory: int(memory), - EphemeralStorage: int(ephStorage), - Storage: int(storage), - Gpu: int(gpu), + Cpu: selector.MatchSelector.Cpu, + Memory: selector.MatchSelector.Memory, + EphemeralStorage: selector.MatchSelector.EphemeralStorage, + Storage: selector.MatchSelector.Storage, + Gpu: selector.MatchSelector.Gpu, } } if selector.RangeSelector != nil { - minCpu, _ := selector.RangeSelector.MinCpu.AsInt64() - minMemory, _ := selector.RangeSelector.MinMemory.AsInt64() - minEph, _ := selector.RangeSelector.MinEph.AsInt64() - minStorage, _ := selector.RangeSelector.MinStorage.AsInt64() - minGpu, _ := selector.RangeSelector.MinGpu.AsInt64() - maxCpu, _ := selector.RangeSelector.MaxCpu.AsInt64() - maxMemory, _ := selector.RangeSelector.MaxMemory.AsInt64() - maxEph, _ := selector.RangeSelector.MaxEph.AsInt64() - maxStorage, _ := selector.RangeSelector.MaxStorage.AsInt64() - maxGpu, _ := selector.RangeSelector.MaxGpu.AsInt64() - s.RangeSelector = &models.RangeSelector{ - MinCpu: int(minCpu), - MinMemory: int(minMemory), - MinEph: int(minEph), - MinStorage: int(minStorage), - MinGpu: int(minGpu), - MaxCpu: int(maxCpu), - MaxMemory: int(maxMemory), - MaxEph: int(maxEph), - MaxStorage: int(maxStorage), - MaxGpu: int(maxGpu), + MinCpu: selector.RangeSelector.MinCpu, + MinMemory: selector.RangeSelector.MinMemory, + MinEph: selector.RangeSelector.MinEph, + MinStorage: selector.RangeSelector.MinStorage, + MinGpu: selector.RangeSelector.MinGpu, + MaxCpu: selector.RangeSelector.MaxCpu, + MaxMemory: selector.RangeSelector.MaxMemory, + MaxEph: selector.RangeSelector.MaxEph, + MaxStorage: selector.RangeSelector.MaxStorage, + MaxGpu: selector.RangeSelector.MaxGpu, } } return } -func ParsePartition(partition reservationv1alpha1.Partition) models.Partition { - cpu, _ := partition.Cpu.AsInt64() - memory, _ := partition.Memory.AsInt64() - ephStorage, _ := partition.EphemeralStorage.AsInt64() - storage, _ := partition.Storage.AsInt64() - gpu, _ := partition.Gpu.AsInt64() - - return models.Partition{ - Cpu: int(cpu), - Memory: int(memory), - EphemeralStorage: int(ephStorage), - Storage: int(storage), - Gpu: int(gpu), +func ParsePartition(partition *reservationv1alpha1.Partition) *models.Partition { + return &models.Partition{ + Cpu: partition.Cpu, + Memory: partition.Memory, + EphemeralStorage: partition.EphemeralStorage, + Storage: partition.Storage, + Gpu: partition.Gpu, } } -func ParsePartitionFromObj(partition models.Partition) reservationv1alpha1.Partition { - p := reservationv1alpha1.Partition{ - Architecture: partition.Architecture, - Cpu: *resource.NewQuantity(int64(partition.Cpu), resource.DecimalSI), - Memory: *resource.NewQuantity(int64(partition.Memory), resource.BinarySI), - } - if partition.EphemeralStorage != 0 { - p.EphemeralStorage = *resource.NewQuantity(int64(partition.EphemeralStorage), resource.BinarySI) - } - if partition.Storage != 0 { - p.Storage = *resource.NewQuantity(int64(partition.Storage), resource.BinarySI) +func ParsePartitionFromObj(partition *models.Partition) *reservationv1alpha1.Partition { + return &reservationv1alpha1.Partition{ + Architecture: partition.Architecture, + Cpu: partition.Cpu, + Memory: partition.Memory, + Gpu: partition.Gpu, + Storage: partition.Storage, + EphemeralStorage: partition.EphemeralStorage, } - if partition.Gpu != 0 { - p.Gpu = *resource.NewQuantity(int64(partition.Gpu), resource.DecimalSI) - } - return p } func ParseNodeIdentity(node nodecorev1alpha1.NodeIdentity) models.NodeIdentity { @@ -116,29 +86,26 @@ func ParseNodeIdentity(node nodecorev1alpha1.NodeIdentity) models.NodeIdentity { // ParseFlavourObject creates a Flavour Object from a Flavour CR func ParseFlavour(flavour nodecorev1alpha1.Flavour) models.Flavour { - cpu, _ := flavour.Spec.Characteristics.Cpu.AsInt64() - ram, _ := flavour.Spec.Characteristics.Memory.AsInt64() - cpuMin, _ := flavour.Spec.Policy.Partitionable.CpuMin.AsInt64() - memoryMin, _ := flavour.Spec.Policy.Partitionable.MemoryMin.AsInt64() - cpuStep, _ := flavour.Spec.Policy.Partitionable.CpuStep.AsInt64() - memoryStep, _ := flavour.Spec.Policy.Partitionable.MemoryStep.AsInt64() - obj := models.Flavour{ + return models.Flavour{ FlavourID: flavour.Name, Type: string(flavour.Spec.Type), ProviderID: flavour.Spec.ProviderID, Characteristics: models.Characteristics{ - CPU: int(cpu), - Memory: int(ram), + CPU: flavour.Spec.Characteristics.Cpu, + Memory: flavour.Spec.Characteristics.Memory, + 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: int(cpuMin), - MemoryMinimum: int(memoryMin), - CPUStep: int(cpuStep), - MemoryStep: int(memoryStep), + CPUMinimum: flavour.Spec.Policy.Partitionable.CpuMin, + MemoryMinimum: flavour.Spec.Policy.Partitionable.MemoryMin, + CPUStep: flavour.Spec.Policy.Partitionable.CpuStep, + MemoryStep: flavour.Spec.Policy.Partitionable.MemoryStep, } } return nil @@ -163,19 +130,6 @@ func ParseFlavour(flavour nodecorev1alpha1.Flavour) models.Flavour { WorkerID: flavour.Spec.OptionalFields.WorkerID, }, } - if ephStorage, ok := flavour.Spec.Characteristics.EphemeralStorage.AsInt64(); ok == true && ephStorage != 0 { - obj.Characteristics.EphemeralStorage = int(ephStorage) - } - if storage, ok := flavour.Spec.Characteristics.PersistentStorage.AsInt64(); ok == true && storage != 0 { - obj.Characteristics.PersistentStorage = int(storage) - } - if gpu, ok := flavour.Spec.Characteristics.Gpu.AsInt64(); ok == true && gpu != 0 { - obj.Characteristics.GPU = int(gpu) - } - if flavour.Spec.Characteristics.Architecture != "" { - obj.Characteristics.Architecture = flavour.Spec.Characteristics.Architecture - } - return obj } // ForgeContractObject creates a Contract Object diff --git a/pkg/utils/resourceforge/forge.go b/pkg/utils/resourceforge/forge.go index cca9411..5defe30 100644 --- a/pkg/utils/resourceforge/forge.go +++ b/pkg/utils/resourceforge/forge.go @@ -17,7 +17,6 @@ package resourceforge import ( "time" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" advertisementv1alpha1 "github.com/fluidos-project/node/apis/advertisement/v1alpha1" @@ -31,14 +30,19 @@ import ( ) // ForgeDiscovery creates a Discovery CR from a FlavourSelector and a solverID -func ForgeDiscovery(selector nodecorev1alpha1.FlavourSelector, solverID string) *advertisementv1alpha1.Discovery { +func ForgeDiscovery(selector *nodecorev1alpha1.FlavourSelector, solverID string) *advertisementv1alpha1.Discovery { return &advertisementv1alpha1.Discovery{ ObjectMeta: metav1.ObjectMeta{ Name: namings.ForgeDiscoveryName(solverID), Namespace: flags.FLUIDOS_NAMESPACE, }, Spec: advertisementv1alpha1.DiscoverySpec{ - Selector: selector, + Selector: func() *nodecorev1alpha1.FlavourSelector { + if selector != nil { + return selector + } + return nil + }(), SolverID: solverID, Subscribe: false, }, @@ -72,9 +76,9 @@ func ForgePeeringCandidate(flavourPeeringCandidate *nodecorev1alpha1.Flavour, so } // ForgeReservation creates a Reservation CR from a PeeringCandidate -func ForgeReservation(peeringCandidate advertisementv1alpha1.PeeringCandidate, partition reservationv1alpha1.Partition, ni nodecorev1alpha1.NodeIdentity) *reservationv1alpha1.Reservation { +func ForgeReservation(peeringCandidate advertisementv1alpha1.PeeringCandidate, partition *reservationv1alpha1.Partition, ni nodecorev1alpha1.NodeIdentity) *reservationv1alpha1.Reservation { solverID := peeringCandidate.Spec.SolverID - return &reservationv1alpha1.Reservation{ + reservation := &reservationv1alpha1.Reservation{ ObjectMeta: metav1.ObjectMeta{ Name: namings.ForgeReservationName(solverID), Namespace: flags.FLUIDOS_NAMESPACE, @@ -91,11 +95,14 @@ func ForgeReservation(peeringCandidate advertisementv1alpha1.PeeringCandidate, p Name: peeringCandidate.Name, Namespace: peeringCandidate.Namespace, }, - Reserve: true, - Purchase: true, - Partition: partition, + Reserve: true, + Purchase: true, }, } + if partition != nil { + reservation.Spec.Partition = partition + } + return reservation } // ForgeContract creates a Contract CR @@ -116,8 +123,13 @@ func ForgeContract(flavour nodecorev1alpha1.Flavour, transaction models.Transact Seller: flavour.Spec.Owner, SellerCredentials: *lc, TransactionID: transaction.TransactionID, - Partition: parseutil.ParsePartitionFromObj(transaction.Partition), - ExpirationTime: time.Now().Add(flags.EXPIRATION_CONTRACT).Format(time.RFC3339), + Partition: func() *reservationv1alpha1.Partition { + if transaction.Partition != nil { + return parseutil.ParsePartitionFromObj(transaction.Partition) + } + return nil + }(), + ExpirationTime: time.Now().Add(flags.EXPIRATION_CONTRACT).Format(time.RFC3339), }, Status: reservationv1alpha1.ContractStatus{ Phase: nodecorev1alpha1.PhaseStatus{ @@ -139,10 +151,12 @@ func ForgeFlavourFromMetrics(node models.NodeInfo, ni nodecorev1alpha1.NodeIdent ProviderID: ni.NodeID, Type: nodecorev1alpha1.K8S, Characteristics: nodecorev1alpha1.Characteristics{ - Architecture: node.Architecture, - Cpu: node.ResourceMetrics.CPUAvailable, - Memory: node.ResourceMetrics.MemoryAvailable, - EphemeralStorage: node.ResourceMetrics.EphemeralStorage, + Architecture: node.Architecture, + Cpu: node.ResourceMetrics.CPUAvailable, + Memory: node.ResourceMetrics.MemoryAvailable, + EphemeralStorage: node.ResourceMetrics.EphemeralStorage, + PersistentStorage: parseutil.ParseQuantityFromString("0"), + Gpu: parseutil.ParseQuantityFromString("0"), }, Policy: nodecorev1alpha1.Policy{ Partitionable: &nodecorev1alpha1.Partitionable{ @@ -197,7 +211,12 @@ func ForgeContractObj(contract *reservationv1alpha1.Contract) models.Contract { Token: contract.Spec.SellerCredentials.Token, Endpoint: contract.Spec.SellerCredentials.Endpoint, }, - Partition: parseutil.ParsePartition(contract.Spec.Partition), + Partition: func() *models.Partition { + if contract.Spec.Partition != nil { + return parseutil.ParsePartition(contract.Spec.Partition) + } + return nil + }(), TransactionID: contract.Spec.TransactionID, ExpirationTime: contract.Spec.ExpirationTime, ExtraInformation: func() map[string]string { @@ -243,8 +262,13 @@ func ForgeContractFromObj(contract models.Contract) *reservationv1alpha1.Contrac Token: contract.SellerCredentials.Token, Endpoint: contract.SellerCredentials.Endpoint, }, - TransactionID: contract.TransactionID, - Partition: parseutil.ParsePartitionFromObj(contract.Partition), + TransactionID: contract.TransactionID, + Partition: func() *reservationv1alpha1.Partition { + if contract.Partition != nil { + return parseutil.ParsePartitionFromObj(contract.Partition) + } + return nil + }(), ExpirationTime: contract.ExpirationTime, ExtraInformation: func() map[string]string { if contract.ExtraInformation != nil { @@ -278,7 +302,12 @@ func ForgeTransactionFromObj(reservation *models.Transaction) *reservationv1alph NodeID: reservation.Buyer.NodeID, }, ClusterID: reservation.ClusterID, - Partition: parseutil.ParsePartitionFromObj(reservation.Partition), + Partition: func() *reservationv1alpha1.Partition { + if reservation.Partition != nil { + return parseutil.ParsePartitionFromObj(reservation.Partition) + } + return nil + }(), }, } } @@ -294,18 +323,22 @@ func ForgeFlavourFromObj(flavour models.Flavour) *nodecorev1alpha1.Flavour { ProviderID: flavour.Owner.NodeID, Type: nodecorev1alpha1.K8S, Characteristics: nodecorev1alpha1.Characteristics{ - Cpu: *resource.NewQuantity(int64(flavour.Characteristics.CPU), resource.DecimalSI), - Memory: *resource.NewQuantity(int64(flavour.Characteristics.Memory), resource.BinarySI), + Cpu: flavour.Characteristics.CPU, + Memory: flavour.Characteristics.Memory, + 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: *resource.NewQuantity(int64(flavour.Policy.Partitionable.CPUMinimum), resource.DecimalSI), - MemoryMin: *resource.NewQuantity(int64(flavour.Policy.Partitionable.MemoryMinimum), resource.BinarySI), - CpuStep: *resource.NewQuantity(int64(flavour.Policy.Partitionable.CPUStep), resource.DecimalSI), - MemoryStep: *resource.NewQuantity(int64(flavour.Policy.Partitionable.MemoryStep), resource.BinarySI), + CpuMin: flavour.Policy.Partitionable.CPUMinimum, + MemoryMin: flavour.Policy.Partitionable.MemoryMinimum, + CpuStep: flavour.Policy.Partitionable.CPUStep, + MemoryStep: flavour.Policy.Partitionable.MemoryStep, } } return nil @@ -332,24 +365,11 @@ func ForgeFlavourFromObj(flavour models.Flavour) *nodecorev1alpha1.Flavour { }, }, } - - if flavour.Characteristics.EphemeralStorage != 0 { - f.Spec.Characteristics.EphemeralStorage = *resource.NewQuantity(int64(flavour.Characteristics.EphemeralStorage), resource.BinarySI) - } - if flavour.Characteristics.PersistentStorage != 0 { - f.Spec.Characteristics.PersistentStorage = *resource.NewQuantity(int64(flavour.Characteristics.PersistentStorage), resource.BinarySI) - } - if flavour.Characteristics.GPU != 0 { - f.Spec.Characteristics.Gpu = *resource.NewQuantity(int64(flavour.Characteristics.GPU), resource.DecimalSI) - } - if flavour.Characteristics.Architecture != "" { - f.Spec.Characteristics.Architecture = flavour.Characteristics.Architecture - } return f } -func ForgePartition(selector nodecorev1alpha1.FlavourSelector) reservationv1alpha1.Partition { - return reservationv1alpha1.Partition{ +func ForgePartition(selector *nodecorev1alpha1.FlavourSelector) *reservationv1alpha1.Partition { + return &reservationv1alpha1.Partition{ Architecture: selector.Architecture, Cpu: selector.RangeSelector.MinCpu, Memory: selector.RangeSelector.MinMemory,