Skip to content

Commit

Permalink
VirtualNode: namespace selector
Browse files Browse the repository at this point in the history
  • Loading branch information
cheina97 authored and adamjensenbot committed Sep 8, 2023
1 parent da097c7 commit 1500ed8
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 6 deletions.
1 change: 1 addition & 0 deletions cmd/liqoctl/cmd/offload.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func newOffloadNamespaceCommand(ctx context.Context, f *factory.Factory) *cobra.

f.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("pod-offloading-strategy", completion.Enumeration(podOffloadingStrategy.Allowed)))
f.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("namespace-mapping-strategy", completion.Enumeration(namespaceMappingStrategy.Allowed)))
f.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("selector", completion.LabelsSelector(ctx, f, completion.NoLimit)))
f.Printer.CheckErr(cmd.RegisterFlagCompletionFunc("output", completion.Enumeration(outputFormat.Allowed)))

return cmd
Expand Down
10 changes: 10 additions & 0 deletions deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,16 @@ rules:
- get
- patch
- update
- apiGroups:
- virtualkubelet.liqo.io
resources:
- virtualnode
verbs:
- get
- list
- patch
- update
- watch
- apiGroups:
- virtualkubelet.liqo.io
resources:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/liqotech/liqo/internal/crdReplicator/reflection"
liqoconst "github.com/liqotech/liqo/pkg/consts"
"github.com/liqotech/liqo/pkg/utils/getters"
virtualnodeutils "github.com/liqotech/liqo/pkg/utils/virtualnode"
)

func (r *NamespaceOffloadingReconciler) enforceClusterSelector(ctx context.Context, nsoff *offv1alpha1.NamespaceOffloading,
Expand Down Expand Up @@ -102,10 +103,10 @@ func matchVirtualNodeSelectorTerms(ctx context.Context, cl client.Client, virtua
if len(selector.NodeSelectorTerms) == 0 {
return true, nil
}

n, err := getters.GetNodeFromVirtualNode(ctx, cl, virtualNode)
var n *corev1.Node
n, err := virtualnodeutils.ForgeFakeNodeFromVirtualNode(ctx, cl, virtualNode)
if err != nil {
return false, fmt.Errorf("failed to retrieve node %s from VirtualNode %s: %w", n.Name, virtualNode.Name, err)
return false, fmt.Errorf("failed to forge fake node from VirtualNode: %w", err)
}
return k8shelper.MatchNodeSelectorTerms(n, selector)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package nsoffctrl
import (
"context"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
Expand Down Expand Up @@ -56,6 +57,7 @@ const (
// +kubebuilder:rbac:groups=offloading.liqo.io,resources=namespaceoffloadings/status,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=offloading.liqo.io,resources=namespaceoffloadings/finalizers,verbs=get;update;patch
// +kubebuilder:rbac:groups=virtualkubelet.liqo.io,resources=namespacemaps,verbs=get;list;watch;patch;update
// +kubebuilder:rbac:groups=virtualkubelet.liqo.io,resources=virtualnode, verbs=get;list;watch;patch;update
// +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;update;patch
// +kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch
// +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -133,6 +135,8 @@ func (r *NamespaceOffloadingReconciler) SetupWithManager(mgr ctrl.Manager) error
return ctrl.NewControllerManagedBy(mgr).
For(&offv1alpha1.NamespaceOffloading{}, builder.WithPredicates(filter)).
Watches(&mapsv1alpha1.NamespaceMap{}, r.namespaceMapHandlers()).
Watches(&mapsv1alpha1.VirtualNode{}, r.enqueueAll()).
Watches(&corev1.Node{}, r.enqueueAll()).
Complete(r)
}

Expand Down Expand Up @@ -173,3 +177,22 @@ func (r *NamespaceOffloadingReconciler) namespaceMapHandlers() handler.EventHand
},
}
}

func (r *NamespaceOffloadingReconciler) enqueueAll() handler.EventHandler {
return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request {
var nsolist offv1alpha1.NamespaceOffloadingList
if err := r.Client.List(ctx, &nsolist); err != nil {
klog.Errorf("Failed to retrieve NamespaceOffloadingList: %v", err)
return nil
}
reqs := make([]reconcile.Request, len(nsolist.Items))
for i := range nsolist.Items {
reqs[i] = reconcile.Request{NamespacedName: types.NamespacedName{
Name: nsolist.Items[i].Name,
Namespace: nsolist.Items[i].Namespace,
}}
}
return reqs
},
)
}
45 changes: 45 additions & 0 deletions pkg/liqoctl/completion/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package completion

import (
"context"
"fmt"
"strings"

"github.com/spf13/cobra"
Expand All @@ -28,6 +29,7 @@ import (
identitymanager "github.com/liqotech/liqo/pkg/identityManager"
"github.com/liqotech/liqo/pkg/liqoctl/factory"
"github.com/liqotech/liqo/pkg/utils/slice"
utilsvirtualnode "github.com/liqotech/liqo/pkg/utils/virtualnode"
)

// NoLimit is a constant to specify that autocompletion is not limited depending on the number of arguments.
Expand Down Expand Up @@ -143,6 +145,49 @@ func VirtualNodes(ctx context.Context, f *factory.Factory, argsLimit int) FnType
return common(ctx, f, argsLimit, retriever)
}

// LabelsSelector returns a function to autocomplete selector labels.
func LabelsSelector(ctx context.Context, f *factory.Factory, argsLimit int) FnType {
retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) {
// labelsCounter contains a 'key=value' string as key and the number of times it appears as value.
labelsCounter := map[string]int{}
var virtualNodes virtualkubeletv1alpha1.VirtualNodeList
if err := f.CRClient.List(ctx, &virtualNodes); err != nil {
return nil, err
}
for i := range virtualNodes.Items {
labelSet, err := utilsvirtualnode.GetLabelSelectors(ctx, f.CRClient, &virtualNodes.Items[i])
if err != nil {
return nil, err
}
for k, v := range labelSet {
addLabelSelector(k, v, labelsCounter)
}
}
return parseLabelSelectors(labelsCounter, len(virtualNodes.Items)), nil
}
return common(ctx, f, argsLimit, retriever)
}

func addLabelSelector(key, value string, labelset map[string]int) {
entry := fmt.Sprintf("%s=%s", key, value)
if _, ok := labelset[entry]; ok {
labelset[entry]++
return
}
labelset[entry] = 1
}

func parseLabelSelectors(labelset map[string]int, max int) []string {
var output []string
for k, v := range labelset {
if v != max {
// this means that the label is not present in all virtualnodes or node, so can be used as selector
output = append(output, k)
}
}
return output
}

// ForeignClusters returns a function to autocomplete ForeignCluster names.
func ForeignClusters(ctx context.Context, f *factory.Factory, argsLimit int) FnType {
retriever := func(ctx context.Context, f *factory.Factory) ([]string, error) {
Expand Down
6 changes: 3 additions & 3 deletions pkg/utils/getters/k8sGetters.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,9 @@ func GetNodeFromVirtualNode(ctx context.Context, cl client.Client, virtualNode *
return &nodes.Items[i], nil
}
}
podRN := string(corev1.ResourcePods)
podGR := corev1.Resource(podRN)
return nil, kerrors.NewNotFound(podGR, nodename)
nodeRN := "nodes"
nodeGR := corev1.Resource(nodeRN)
return nil, kerrors.NewNotFound(nodeGR, nodename)
}

// GetTunnelEndpoint retrieves the tunnelEndpoint resource related to a cluster.
Expand Down
16 changes: 16 additions & 0 deletions pkg/utils/virtualnode/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2019-2023 The Liqo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package virtualnode contains utils functions to manage virtual nodes.
package virtualnode
63 changes: 63 additions & 0 deletions pkg/utils/virtualnode/virtualnode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2019-2023 The Liqo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package virtualnode

import (
"context"
"fmt"
"strconv"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"sigs.k8s.io/controller-runtime/pkg/client"

virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1"
liqoconsts "github.com/liqotech/liqo/pkg/consts"
"github.com/liqotech/liqo/pkg/utils/getters"
)

// ForgeFakeNodeFromVirtualNode creates a fake node from a virtual node.
func ForgeFakeNodeFromVirtualNode(ctx context.Context, cl client.Client, vn *virtualkubeletv1alpha1.VirtualNode) (*corev1.Node, error) {
l, err := GetLabelSelectors(ctx, cl, vn)
if err != nil {
return nil, fmt.Errorf("failed to retrieve label selectors: %w", err)
}
return &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: vn.Name,
Labels: l,
},
}, nil
}

// GetLabelSelectors returns the labels that can be used to target a remote cluster.
// If virtualnode spec.CreateNode is true, the labels are taken from the created node.
// If virtualnode spec.CreateNode is false, the labels are taken from the virtual node spec.Labels and spec.Template .
func GetLabelSelectors(ctx context.Context, cl client.Client, vn *virtualkubeletv1alpha1.VirtualNode) (labels.Set, error) {
n, err := getters.GetNodeFromVirtualNode(ctx, cl, vn)
switch {
case errors.IsNotFound(err):
return labels.Merge(vn.Spec.Labels, labels.Set{
liqoconsts.RemoteClusterID: vn.Spec.ClusterIdentity.ClusterID,
liqoconsts.StorageAvailableLabel: strconv.FormatBool(len(vn.Spec.StorageClasses) == 0),
}), nil
case err != nil:
return nil, fmt.Errorf("failed to retrieve node %s from VirtualNode %s: %w", n.Name, vn.Name, err)
default:
return n.Labels, nil
}
}

0 comments on commit 1500ed8

Please sign in to comment.