diff --git a/apis/ipam/v1alpha1/network_types.go b/apis/ipam/v1alpha1/network_types.go index 62c0aa9750..9d5804038a 100644 --- a/apis/ipam/v1alpha1/network_types.go +++ b/apis/ipam/v1alpha1/network_types.go @@ -21,6 +21,11 @@ import ( v1alpha1networking "github.com/liqotech/liqo/apis/networking/v1alpha1" ) +const ( + // NetworkLocalLabel is the label used to mark a Network intended to use locally to the cluster. + NetworkLocalLabel = "ipam.liqo.io/network-local" +) + var ( // NetworkKind is the kind name used to register the Network CRD. NetworkKind = "Network" diff --git a/cmd/ipam/main.go b/cmd/ipam/main.go index 5b56f15cab..f8133165a1 100644 --- a/cmd/ipam/main.go +++ b/cmd/ipam/main.go @@ -16,12 +16,14 @@ package main import ( + "context" "flag" "fmt" "os" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" @@ -31,9 +33,13 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/klog/v2" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" "github.com/liqotech/liqo/pkg/consts" liqoipam "github.com/liqotech/liqo/pkg/ipam" "github.com/liqotech/liqo/pkg/leaderelection" @@ -42,12 +48,15 @@ import ( "github.com/liqotech/liqo/pkg/utils/restcfg" ) -const leaderElectorName = "liqo-ipam-leader-election" +const ( + leaderElectorName = "liqo-ipam-leader-election" +) var ( addToSchemeFunctions = []func(*runtime.Scheme) error{ clientgoscheme.AddToScheme, netv1alpha1.AddToScheme, + ipamv1alpha1.AddToScheme, } options = liqoipam.NewOptions() @@ -57,6 +66,7 @@ var ( // +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=net.liqo.io,resources=ipamstorages,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups=net.liqo.io,resources=natmappings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks,verbs=get;list;watch;create;update;patch;delete func main() { var cmd = cobra.Command{ @@ -84,6 +94,7 @@ func main() { func run(_ *cobra.Command, _ []string) error { var err error scheme := runtime.NewScheme() + ctx := ctrl.SetupSignalHandler() // Adds the APIs to the scheme. for _, addToScheme := range addToSchemeFunctions { @@ -101,12 +112,18 @@ func run(_ *cobra.Command, _ []string) error { // Get dynamic client. dynClient := dynamic.NewForConfigOrDie(cfg) + // Get controller-runtime client + cl, err := client.New(cfg, client.Options{Scheme: scheme}) + if err != nil { + return fmt.Errorf("unable to create client: %w", err) + } + // Setup IPAM. ipam := liqoipam.NewIPAM() startIPAMServer := func() { // Initialize and start IPAM server. - if err = initializeIPAM(ipam, options, dynClient); err != nil { + if err = initializeIPAM(ctx, ipam, options, dynClient, cl); err != nil { klog.Errorf("Failed to initialize IPAM: %s", err) os.Exit(1) } @@ -116,8 +133,6 @@ func run(_ *cobra.Command, _ []string) error { ipam.Terminate() } - ctx := ctrl.SetupSignalHandler() - // If the lease is disabled, start IPAM server without leader election mechanism (i.e., do not support IPAM high-availability). if !options.LeaseEnabled { startIPAMServer() @@ -153,7 +168,7 @@ func run(_ *cobra.Command, _ []string) error { return nil } -func initializeIPAM(ipam *liqoipam.IPAM, opts *liqoipam.Options, dynClient dynamic.Interface) error { +func initializeIPAM(ctx context.Context, ipam *liqoipam.IPAM, opts *liqoipam.Options, dynClient dynamic.Interface, cl client.Client) error { if ipam == nil { return fmt.Errorf("IPAM pointer is nil. Initialize it before calling this function") } @@ -165,21 +180,65 @@ func initializeIPAM(ipam *liqoipam.IPAM, opts *liqoipam.Options, dynClient dynam if err := ipam.SetPodCIDR(opts.PodCIDR.String()); err != nil { return err } + if err := createReservedNetwork(ctx, cl, "local-pod", consts.NetworkTypePodCIDR, opts.PodCIDR.String()); err != nil { + return err + } + if err := ipam.SetServiceCIDR(opts.ServiceCIDR.String()); err != nil { return err } + if err := createReservedNetwork(ctx, cl, "local-service", consts.NetworkTypeServiceCIDR, opts.ServiceCIDR.String()); err != nil { + return err + } for _, pool := range opts.AdditionalPools.StringList.StringList { if err := ipam.AddNetworkPool(pool); err != nil { return err } + if err := createReservedNetwork(ctx, cl, fmt.Sprintf("pool-%s", pool), consts.NetworkTypePool, pool); err != nil { + return err + } } if err := ipam.SetReservedSubnets(opts.ReservedPools.StringList.StringList); err != nil { return err } + for _, pool := range opts.ReservedPools.StringList.StringList { + if err := createReservedNetwork(ctx, cl, fmt.Sprintf("reserved-%s", pool), consts.NetworkTypeReservedSubnet, pool); err != nil { + return err + } + } + + externalCIDR, err := ipam.GetExternalCIDR(liqonetutils.GetMask(options.PodCIDR.String())) + if err != nil { + return err + } + if err := createReservedNetwork(ctx, cl, "local-external", consts.NetworkTypeExternalCIDR, externalCIDR); err != nil { + return err + } + + return nil +} + +func createReservedNetwork(ctx context.Context, cl client.Client, name, networkType, cidr string) error { + network := &ipamv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: os.Getenv("POD_NAMESPACE"), + }, + } - if _, err := ipam.GetExternalCIDR(liqonetutils.GetMask(options.PodCIDR.String())); err != nil { + if _, err := controllerutil.CreateOrUpdate(ctx, cl, network, func() error { + if network.Labels == nil { + network.Labels = map[string]string{} + } + network.Labels[ipamv1alpha1.NetworkLocalLabel] = networkType + + network.Spec = ipamv1alpha1.NetworkSpec{ + CIDR: networkingv1alpha1.CIDR(cidr), + } + return nil + }); err != nil { return err } diff --git a/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml b/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml index 7c0f86845a..caea1add1c 100644 --- a/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-controller-manager-ClusterRole.yaml @@ -359,14 +359,6 @@ rules: - patch - update - watch -- apiGroups: - - net.liqo.io - resources: - - ipamstorages - verbs: - - get - - list - - watch - apiGroups: - net.liqo.io resources: diff --git a/deployments/liqo/files/liqo-ipam-ClusterRole.yaml b/deployments/liqo/files/liqo-ipam-ClusterRole.yaml index 701cfb1d32..8147b7a425 100644 --- a/deployments/liqo/files/liqo-ipam-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-ipam-ClusterRole.yaml @@ -20,6 +20,18 @@ rules: - patch - update - watch +- apiGroups: + - ipam.liqo.io + resources: + - networks + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - net.liqo.io resources: diff --git a/pkg/consts/ipam.go b/pkg/consts/ipam.go index aa6845b521..8bcc30c7af 100644 --- a/pkg/consts/ipam.go +++ b/pkg/consts/ipam.go @@ -17,4 +17,14 @@ package consts const ( // IpamPort is the port used by the IPAM gRPC server. IpamPort = 6000 + // NetworkTypePodCIDR is the constant representing a network of type podCIDR. + NetworkTypePodCIDR = "pod-cidr" + // NetworkTypeServiceCIDR is the constant representing a network of type serviceCIDR. + NetworkTypeServiceCIDR = "service-cidr" + // NetworkTypeExternalCIDR is the constant representing a network of type externalCIDR. + NetworkTypeExternalCIDR = "external-cidr" + // NetworkTypePool is the constant representing a network of type pool. + NetworkTypePool = "pool" + // NetworkTypeReservedSubnet is the constant representing a network of type reserved subnet. + NetworkTypeReservedSubnet = "reserved-subnet" ) diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go index 77fa9e8f95..40b2aebe39 100644 --- a/pkg/ipam/ipam.go +++ b/pkg/ipam/ipam.go @@ -378,9 +378,8 @@ func (liqoIPAM *IPAM) clusterSubnetEqualToPool(pool string) (string, error) { return mappedNetwork, nil } -// MapNetworkCIDR receives a network CIDR and a cluster identifier and, -// return the network CIDR to use for the remote cluster, remapped if -// necessary. +// MapNetworkCIDR receives a network CIDR and return the network CIDR to use for the remote cluster, +// remapped if necessary. func (liqoIPAM *IPAM) MapNetworkCIDR(_ context.Context, mapCIDRRequest *MapCIDRRequest) (*MapCIDRResponse, error) { mappedCIDR, err := liqoIPAM.getOrRemapNetwork(mapCIDRRequest.GetCidr()) if err != nil { @@ -389,7 +388,7 @@ func (liqoIPAM *IPAM) MapNetworkCIDR(_ context.Context, mapCIDRRequest *MapCIDRR return &MapCIDRResponse{Cidr: mappedCIDR}, nil } -// UnmapNetworkCIDR set the network CIDR as unused for a specific cluster. +// UnmapNetworkCIDR set the network CIDR as unused. func (liqoIPAM *IPAM) UnmapNetworkCIDR(_ context.Context, unmapCIDRRequest *UnmapCIDRRequest) (*UnmapCIDRResponse, error) { err := liqoIPAM.FreeReservedSubnet(unmapCIDRRequest.GetCidr()) if err != nil { diff --git a/pkg/liqo-controller-manager/external-network/configuration-controller/configuration_controller.go b/pkg/liqo-controller-manager/external-network/configuration-controller/configuration_controller.go index 5155d3c07b..a2f34a6852 100644 --- a/pkg/liqo-controller-manager/external-network/configuration-controller/configuration_controller.go +++ b/pkg/liqo-controller-manager/external-network/configuration-controller/configuration_controller.go @@ -19,7 +19,6 @@ import ( "fmt" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" @@ -29,7 +28,7 @@ import ( ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" "github.com/liqotech/liqo/pkg/utils/events" - liqogetters "github.com/liqotech/liqo/pkg/utils/getters" + ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" ) // ConfigurationReconciler manage Configuration lifecycle. @@ -57,7 +56,6 @@ func NewConfigurationReconciler(cl client.Client, s *runtime.Scheme, er record.E // +kubebuilder:rbac:groups=networking.liqo.io,resources=configurations/status,verbs=get;list;watch;update;patch // +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks,verbs=get;list;watch;create // +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks/status,verbs=get;list;watch -// +kubebuilder:rbac:groups=net.liqo.io,resources=ipamstorages,verbs=get;list;watch // Reconcile manage Configurations, remapping cidrs with Networks resources. func (r *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -99,14 +97,19 @@ func (r *ConfigurationReconciler) Reconcile(ctx context.Context, req ctrl.Reques func (r *ConfigurationReconciler) defaultLocalNetwork(ctx context.Context, cfg *networkingv1alpha1.Configuration) error { if r.localCIDR == nil { - ipamStorage, err := liqogetters.GetIPAMStorageByLabel(ctx, r.Client, labels.NewSelector()) + podCIDR, err := ipamutils.RetrievePodCIDR(ctx, r.Client) if err != nil { - return fmt.Errorf("unable to get IPAM storage: %w", err) + return fmt.Errorf("unable to retrieve the podCIDR: %w", err) + } + + externalCIDR, err := ipamutils.RetrieveExternalCIDR(ctx, r.Client) + if err != nil { + return fmt.Errorf("unable to retrieve the externalCIDR: %w", err) } r.localCIDR = &networkingv1alpha1.ClusterConfigCIDR{ - Pod: networkingv1alpha1.CIDR(ipamStorage.Spec.PodCIDR), - External: networkingv1alpha1.CIDR(ipamStorage.Spec.ExternalCIDR), + Pod: networkingv1alpha1.CIDR(podCIDR), + External: networkingv1alpha1.CIDR(externalCIDR), } } diff --git a/pkg/liqo-controller-manager/network-controller/network_controller.go b/pkg/liqo-controller-manager/network-controller/network_controller.go index ceb2c26bb7..64b3bd4e4b 100644 --- a/pkg/liqo-controller-manager/network-controller/network_controller.go +++ b/pkg/liqo-controller-manager/network-controller/network_controller.go @@ -64,6 +64,17 @@ func (r *NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct desiredCIDR = nw.Spec.CIDR + _, localNetwork := nw.Labels[ipamv1alpha1.NetworkLocalLabel] + if localNetwork { + nw.Status.CIDR = desiredCIDR + if err := r.Status().Update(ctx, &nw); err != nil { + klog.Errorf("error while updating Network %q status: %v", req.NamespacedName, err) + return ctrl.Result{}, err + } + klog.Infof("updated Network %q status (spec: %s -> status: %s)", req.NamespacedName, nw.Spec.CIDR, nw.Status.CIDR) + return ctrl.Result{}, nil + } + if nw.GetDeletionTimestamp().IsZero() { if !controllerutil.ContainsFinalizer(&nw, ipamNetworkFinalizer) { // Add finalizer to prevent deletion without unmapping the Network. @@ -135,7 +146,7 @@ func (r *NetworkReconciler) SetupWithManager(mgr ctrl.Manager, workers int) erro Complete(r) } -// getRemappedCIDR returns the remapped CIDR for the given CIDR and remote clusterID. +// getRemappedCIDR returns the remapped CIDR for the given CIDR. func getRemappedCIDR(ctx context.Context, ipamClient ipam.IpamClient, desiredCIDR networkingv1alpha1.CIDR) (networkingv1alpha1.CIDR, error) { switch ipamClient.(type) { case nil: @@ -153,7 +164,7 @@ func getRemappedCIDR(ctx context.Context, ipamClient ipam.IpamClient, desiredCID } } -// deleteRemappedCIDR unmaps the CIDR for the given remote clusterID. +// deleteRemappedCIDR unmaps the given CIDR. func deleteRemappedCIDR(ctx context.Context, ipamClient ipam.IpamClient, remappedCIDR networkingv1alpha1.CIDR) error { switch ipamClient.(type) { case nil: diff --git a/pkg/utils/getters/k8sGetters.go b/pkg/utils/getters/k8sGetters.go index 771a01c947..3034ba783f 100644 --- a/pkg/utils/getters/k8sGetters.go +++ b/pkg/utils/getters/k8sGetters.go @@ -397,3 +397,30 @@ func GetGatewayClientByClusterID(ctx context.Context, cl client.Client, return nil, fmt.Errorf("multiple GatewayClients found for ForeignCluster %s", clusterID) } } + +// RetrieveUniqueNetworkByLabel retrieves the Network resource with the given label selector. +// It returns error if multiple resources are found. +func RetrieveUniqueNetworkByLabel(ctx context.Context, cl client.Client, lSelector labels.Selector) (*ipamv1alpha1.Network, error) { + networks, err := RetrieveNetworksByLabel(ctx, cl, lSelector) + if err != nil { + return nil, err + } + + switch len(networks.Items) { + case 0: + return nil, kerrors.NewNotFound(ipamv1alpha1.NetworkGroupResource, ipamv1alpha1.NetworkResource) + case 1: + return &networks.Items[0], nil + default: + return nil, fmt.Errorf("multiple Network resources found for label selector %q", lSelector) + } +} + +// RetrieveNetworksByLabel retrieves the Network resources with the given labelSelector. +func RetrieveNetworksByLabel(ctx context.Context, cl client.Client, lSelector labels.Selector) (*ipamv1alpha1.NetworkList, error) { + var networks ipamv1alpha1.NetworkList + if err := cl.List(ctx, &networks, &client.ListOptions{LabelSelector: lSelector}); err != nil { + return nil, err + } + return &networks, nil +} diff --git a/pkg/utils/ipam/doc.go b/pkg/utils/ipam/doc.go new file mode 100644 index 0000000000..14ee963514 --- /dev/null +++ b/pkg/utils/ipam/doc.go @@ -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 ipam contains utility functions to deal with resources of the IPAM API. +package ipam diff --git a/pkg/utils/ipam/networks.go b/pkg/utils/ipam/networks.go new file mode 100644 index 0000000000..9dcaecc5fb --- /dev/null +++ b/pkg/utils/ipam/networks.go @@ -0,0 +1,98 @@ +// 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 ipam + +import ( + "context" + + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" + "github.com/liqotech/liqo/pkg/consts" + liqogetters "github.com/liqotech/liqo/pkg/utils/getters" +) + +// RetrievePodCIDR retrieves the podCIDR of the local cluster. +func RetrievePodCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := liqogetters.RetrieveUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + ipamv1alpha1.NetworkLocalLabel: consts.NetworkTypePodCIDR, + })) + if err != nil { + return "", err + } + + return nw.Spec.CIDR.String(), nil +} + +// RetrieveServiceCIDR retrieves the serviceCIDR of the local cluster. +func RetrieveServiceCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := liqogetters.RetrieveUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + ipamv1alpha1.NetworkLocalLabel: consts.NetworkTypeServiceCIDR, + })) + if err != nil { + return "", err + } + + return nw.Spec.CIDR.String(), nil +} + +// RetrieveExternalCIDR retrieves the externalCIDR of the local cluster. +func RetrieveExternalCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := liqogetters.RetrieveUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + ipamv1alpha1.NetworkLocalLabel: consts.NetworkTypeExternalCIDR, + })) + if err != nil { + return "", err + } + + return nw.Spec.CIDR.String(), nil +} + +// RetrieveReservedSubnets retrieves the reserved subnets of the local cluster. +func RetrieveReservedSubnets(ctx context.Context, cl client.Client) ([]string, error) { + var reservedSubnets []string + + networks, err := liqogetters.RetrieveNetworksByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + ipamv1alpha1.NetworkLocalLabel: consts.NetworkTypeReservedSubnet, + })) + if err != nil { + return nil, err + } + + for i := range networks.Items { + reservedSubnets = append(reservedSubnets, networks.Items[i].Spec.CIDR.String()) + } + + return reservedSubnets, nil +} + +// RetrieveAdditionalPools retrieves the additional pools of the local cluster. +func RetrieveAdditionalPools(ctx context.Context, cl client.Client) ([]string, error) { + var additionalPools []string + + networks, err := liqogetters.RetrieveNetworksByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + ipamv1alpha1.NetworkLocalLabel: consts.NetworkTypePool, + })) + if err != nil { + return nil, err + } + + for i := range networks.Items { + additionalPools = append(additionalPools, networks.Items[i].Spec.CIDR.String()) + } + + return additionalPools, nil +}