Skip to content

Commit

Permalink
IPAM: IP support for creating associated SVC
Browse files Browse the repository at this point in the history
  • Loading branch information
fra98 committed Sep 18, 2023
1 parent d848fc1 commit f3c293a
Show file tree
Hide file tree
Showing 8 changed files with 549 additions and 2 deletions.
11 changes: 11 additions & 0 deletions apis/ipam/v1alpha1/ip_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package v1alpha1

import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
Expand All @@ -33,10 +34,20 @@ var (
IPGroupResource = schema.GroupResource{Group: GroupVersion.Group, Resource: IPResource}
)

// ServiceTemplate contains the template to create the associated service (and endpointslice) for the IP endopoint.
type ServiceTemplate struct {
// Template Spec of the Service.
Spec v1.ServiceSpec `json:"spec,omitempty"`
}

// IPSpec defines a local IP.
type IPSpec struct {
// IP is the local IP.
IP string `json:"ip"`
// ServiceTemplate contains the template to create the associated service (and endpointslice) for the IP endopoint.
// If empty the creation of the service is disabled (default).
// +kubebuilder:validation:Optional
ServiceTemplate *ServiceTemplate `json:"serviceTemplate,omitempty"`
}

// IPStatus defines remapped IPs.
Expand Down
23 changes: 22 additions & 1 deletion apis/ipam/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

346 changes: 346 additions & 0 deletions deployments/liqo/crds/ipam.liqo.io_ips.yaml

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- ""
resources:
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- discovery.k8s.io
resources:
Expand Down
11 changes: 11 additions & 0 deletions pkg/liqo-controller-manager/ip-controller/ip_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"slices"

v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -38,6 +39,8 @@ import (

const (
ipamIPFinalizer = "ip.ipam.liqo.io"
// LabelVKSkipUnmapIP is the label used to tell the VK to skip the unmapping of the IP as already managed by the IP controller.
LabelVKSkipUnmapIP = "liqo.io/vk-skip-unmap-ip"
)

// IPReconciler reconciles a IP object.
Expand All @@ -51,6 +54,8 @@ type IPReconciler struct {
// +kubebuilder:rbac:groups=ipam.liqo.io,resources=ips/status,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=ipam.liqo.io,resources=ips/finalizers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=virtualkubelet.liqo.io,resources=virtualnodes,verbs=get;list;watch
// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=discovery.k8s.io,resources=endpointslices,verbs=get;list;watch;create;update;patch;delete

// Reconcile Ip objects.
func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
Expand Down Expand Up @@ -107,6 +112,11 @@ func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re
}
klog.Infof("updated IP %q status", req.NamespacedName)
}

// Create service and associated endpointslice if the template is defined
if err := r.handleAssociatedService(ctx, &ip); err != nil {
return ctrl.Result{}, err
}
} else if controllerutil.ContainsFinalizer(&ip, ipamIPFinalizer) {
// the resource is being deleted, but the finalizer is present:
// - unmap the remapped IPs
Expand Down Expand Up @@ -145,6 +155,7 @@ func (r *IPReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, w

return ctrl.NewControllerManagedBy(mgr).
For(&ipamv1alpha1.IP{}).
Owns(&v1.Service{}).
Watches(&v1alpha1.VirtualNode{}, handler.EnqueueRequestsFromMapFunc(enqueuer)).
WithOptions(controller.Options{MaxConcurrentReconciles: workers}).
Complete(r)
Expand Down
133 changes: 133 additions & 0 deletions pkg/liqo-controller-manager/ip-controller/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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 ipctrl

import (
"context"

v1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1"
)

// handleAssociatedService creates, updates or deletes the service associated to the IP.
func (r *IPReconciler) handleAssociatedService(ctx context.Context, ip *ipamv1alpha1.IP) error {
// Service associated to the IP
svc := v1.Service{ObjectMeta: metav1.ObjectMeta{
Name: ip.Name,
Namespace: ip.Namespace,
}}
svcMutateFn := func() error {
svc.Spec = ip.Spec.ServiceTemplate.Spec
return controllerutil.SetControllerReference(ip, &svc, r.Scheme)
}

// EndpointSlice associated to the Service
eps := discoveryv1.EndpointSlice{ObjectMeta: metav1.ObjectMeta{
Name: ip.Name,
Namespace: ip.Namespace,
}}
epsMutateFn := func() error {
eps.ObjectMeta.Labels = labels.Merge(eps.GetLabels(),
map[string]string{
discoveryv1.LabelServiceName: ip.Name,
LabelVKSkipUnmapIP: "true",
},
)
eps.AddressType = discoveryv1.AddressTypeIPv4
eps.Endpoints = []discoveryv1.Endpoint{
{
Addresses: []string{ip.Spec.IP},
Conditions: discoveryv1.EndpointConditions{
Ready: pointer.Bool(true),
},
// NodeName: pointer.String("external"), // must not be nil, otherwise the endpoint is not reflected by the VK
},
}
eps.Ports = []discoveryv1.EndpointPort{
{
Name: &ip.Spec.ServiceTemplate.Spec.Ports[0].Name,
Protocol: &ip.Spec.ServiceTemplate.Spec.Ports[0].Protocol,
Port: &ip.Spec.ServiceTemplate.Spec.Ports[0].Port,
AppProtocol: ip.Spec.ServiceTemplate.Spec.Ports[0].AppProtocol,
},
}
return controllerutil.SetControllerReference(ip, &eps, r.Scheme)
}

// Create service and endpointslice if the template is defined
if ip.Spec.ServiceTemplate != nil {
if err := ensureResource(ctx, r.Client, &svc, svcMutateFn); err != nil {
return err
}
if err := ensureResource(ctx, r.Client, &eps, epsMutateFn); err != nil {
return err
}
} else {
// Service spec is not defined, delete the associated service and endpointslices if previously created
if err := ensureResourceAbsence(ctx, r.Client, &svc); err != nil {
return err
}
if err := ensureResourceAbsence(ctx, r.Client, &eps); err != nil {
return err
}
}

return nil
}

// ensureResource ensures that the given resource exists.
// It either creates or update the resource.
func ensureResource(ctx context.Context, r client.Client, obj client.Object, mutateFn controllerutil.MutateFn) error {
resourceKind := obj.GetObjectKind().GroupVersionKind().Kind
op, err := controllerutil.CreateOrUpdate(ctx, r, obj, mutateFn)
if err != nil {
klog.Errorf("error while creating/updating %s %q (operation: %s): %v", resourceKind, obj.GetName(), op, err)
return err
}
klog.Infof("%s %q correctly enforced (operation: %s)", resourceKind, obj.GetName(), op)
return nil
}

// ensureResourceAbsence ensures that the given resource does not exist.
// If the resource does not exist, it does nothing.
func ensureResourceAbsence(ctx context.Context, r client.Client, obj client.Object) error {
resourceKind := obj.GetObjectKind().GroupVersionKind().Kind
err := r.Get(ctx, types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()}, obj)
switch {
case err != nil && !apierrors.IsNotFound(err):
klog.Errorf("error while getting %s %q: %v", resourceKind, obj.GetName(), err)
return err
case apierrors.IsNotFound(err):
// The resource does not exist, do nothing.
klog.V(6).Infof("%s %q does not exist. Nothing to do", resourceKind, obj.GetName())
default:
if err := r.Delete(ctx, obj); err != nil {
klog.Errorf("error while deleting %s %q: %v", resourceKind, obj.GetName(), err)
return err
}
klog.Infof("%s %q correctly deleted", resourceKind, obj.GetName())
}
return nil
}
4 changes: 3 additions & 1 deletion pkg/virtualKubelet/forge/endpointslices.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ func IsEndpointSliceManagedByReflection(obj metav1.Object) bool {
func EndpointToBeReflected(endpoint *discoveryv1.Endpoint, localNodeClient corev1listers.NodeLister) bool {
if endpoint.NodeName == nil {
klog.Warning("Endpoint without nodeName")
return false
// If the nodeName is not set, the endpoint is probably external to the cluster. We reflect it as
// is it certainly not scheduled on the virtual node
return true
}
epNode, err := localNodeClient.Get(*endpoint.NodeName)
if err != nil {
Expand Down
11 changes: 11 additions & 0 deletions pkg/virtualKubelet/reflection/exposition/endpointslice.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,17 @@ func (ner *NamespacedEndpointSliceReflector) Handle(ctx context.Context, name st
// The local endpointslice does no longer exist. Ensure it is also absent from the remote cluster.
if !localExists {
// Release the address translations

// TODO:: delete
// If the skip unmap label if present, do not unmap the endpoint as it is already done by other controllers.
// _, skipUnmap := local.GetLabels()[ipctrl.LabelVKSkipUnmapIP]
// if !skipUnmap {
// klog.Infof("SKIP UNMAP") // TODO:: delete
// if err := ner.UnmapEndpointIPs(ctx, name); err != nil {
// return err
// }
// }

if err := ner.UnmapEndpointIPs(ctx, name); err != nil {
return err
}
Expand Down

0 comments on commit f3c293a

Please sign in to comment.