From ca27c28ac8faa0876bcc4c3d726fbda23c5f84e7 Mon Sep 17 00:00:00 2001 From: Francesco Torta <62566275+fra98@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:55:10 +0100 Subject: [PATCH] PodCIDR, ServiceCIDR, ExternalCIDR, ReservedCIDRs as Networks --- cmd/ipam/main.go | 15 +- cmd/liqo-controller-manager/main.go | 12 +- cmd/liqoctl/main.go | 2 + cmd/liqonet/network-manager.go | 8 +- deployments/liqo/README.md | 3 +- .../liqo-controller-manager-ClusterRole.yaml | 8 - .../liqo/files/liqo-ipam-ClusterRole.yaml | 18 ++ .../liqo/templates/liqo-ipam-deployment.yaml | 20 +- .../liqo/templates/liqo-ipam-networks.yaml | 52 ++++ .../liqo/templates/liqo-ipam-service.yaml | 9 +- deployments/liqo/values.yaml | 4 +- pkg/consts/ipam.go | 19 ++ pkg/ipam/fake/ipam.go | 6 + pkg/ipam/flags.go | 6 +- pkg/ipam/ipam.go | 66 ++++- pkg/ipam/ipam.pb.go | 225 ++++++++++++++---- pkg/ipam/ipam.proto | 11 +- pkg/ipam/ipam_grpc.pb.go | 49 +++- pkg/ipam/ipam_test.go | 8 +- pkg/ipam/options.go | 2 + pkg/leaderelection/leaderelection.go | 88 ++++++- .../configuration_controller.go | 17 +- .../ip-controller/ip_controller.go | 38 ++- .../network-controller/network_controller.go | 164 +++++++++---- pkg/liqoctl/rest/configuration/create.go | 35 +-- pkg/liqoctl/rest/configuration/utils.go | 49 +++- pkg/utils/getters/k8sGetters.go | 27 +++ pkg/utils/ipam/doc.go | 16 ++ pkg/utils/ipam/networks.go | 152 ++++++++++++ test/integration/integration_suite_test.go | 9 +- 30 files changed, 941 insertions(+), 197 deletions(-) create mode 100644 deployments/liqo/templates/liqo-ipam-networks.yaml create mode 100644 pkg/utils/ipam/doc.go create mode 100644 pkg/utils/ipam/networks.go diff --git a/cmd/ipam/main.go b/cmd/ipam/main.go index 5b56f15cab..13c5c38b09 100644 --- a/cmd/ipam/main.go +++ b/cmd/ipam/main.go @@ -30,6 +30,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log" @@ -37,7 +38,6 @@ import ( "github.com/liqotech/liqo/pkg/consts" liqoipam "github.com/liqotech/liqo/pkg/ipam" "github.com/liqotech/liqo/pkg/leaderelection" - liqonetutils "github.com/liqotech/liqo/pkg/liqonet/utils" flagsutils "github.com/liqotech/liqo/pkg/utils/flags" "github.com/liqotech/liqo/pkg/utils/restcfg" ) @@ -55,6 +55,8 @@ var ( // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;create;update;delete // +kubebuilder:rbac:groups=core,resources=events,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch; +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;update;patch // +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 @@ -130,12 +132,14 @@ func run(_ *cobra.Command, _ []string) error { leaderelectionOpts := &leaderelection.Opts{ PodName: os.Getenv("POD_NAME"), Namespace: os.Getenv("POD_NAMESPACE"), + DeploymentName: ptr.To(os.Getenv("DEPLOYMENT_NAME")), LeaderElectorName: leaderElectorName, LeaseDuration: options.LeaseDuration, RenewDeadline: options.LeaseRenewDeadline, RetryPeriod: options.LeaseRetryPeriod, InitCallback: startIPAMServer, StopCallback: stopIPAMServer, + LabelLeader: options.LabelLeader, } localClient := kubernetes.NewForConfigOrDie(cfg) @@ -158,28 +162,33 @@ func initializeIPAM(ipam *liqoipam.IPAM, opts *liqoipam.Options, dynClient dynam return fmt.Errorf("IPAM pointer is nil. Initialize it before calling this function") } - if err := ipam.Init(liqoipam.Pools, dynClient, consts.IpamPort); err != nil { + if err := ipam.Init(liqoipam.Pools, dynClient); err != nil { return err } + // Configure PodCIDR if err := ipam.SetPodCIDR(opts.PodCIDR.String()); err != nil { return err } + + // Configure ServiceCIDR if err := ipam.SetServiceCIDR(opts.ServiceCIDR.String()); err != nil { return err } + // Configure additional network pools. for _, pool := range opts.AdditionalPools.StringList.StringList { if err := ipam.AddNetworkPool(pool); err != nil { return err } } + // Configure reserved subnets. if err := ipam.SetReservedSubnets(opts.ReservedPools.StringList.StringList); err != nil { return err } - if _, err := ipam.GetExternalCIDR(liqonetutils.GetMask(options.PodCIDR.String())); err != nil { + if err := ipam.Serve(consts.IpamPort); err != nil { return err } diff --git a/cmd/liqo-controller-manager/main.go b/cmd/liqo-controller-manager/main.go index 3c20c76753..67f6804b45 100644 --- a/cmd/liqo-controller-manager/main.go +++ b/cmd/liqo-controller-manager/main.go @@ -675,21 +675,13 @@ func main() { } if !*disableInternalNetwork { - networkReconciler := &networkctrl.NetworkReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - IpamClient: ipamClient, - } + networkReconciler := networkctrl.NewNetworkReconciler(mgr.GetClient(), mgr.GetScheme(), ipamClient) if err = networkReconciler.SetupWithManager(mgr, *networkWorkers); err != nil { klog.Errorf("Unable to start the networkReconciler", err) os.Exit(1) } - ipReconciler := &ipctrl.IPReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - IpamClient: ipamClient, - } + ipReconciler := ipctrl.NewIPReconciler(mgr.GetClient(), mgr.GetScheme(), ipamClient) if err = ipReconciler.SetupWithManager(ctx, mgr, *ipWorkers); err != nil { klog.Errorf("Unable to start the ipReconciler", err) os.Exit(1) diff --git a/cmd/liqoctl/main.go b/cmd/liqoctl/main.go index e3f62d9bff..4fe3f9b840 100644 --- a/cmd/liqoctl/main.go +++ b/cmd/liqoctl/main.go @@ -25,6 +25,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" discoveryv1alpha1 "github.com/liqotech/liqo/apis/discovery/v1alpha1" + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" netv1alpha1 "github.com/liqotech/liqo/apis/net/v1alpha1" networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" offloadingv1alpha1 "github.com/liqotech/liqo/apis/offloading/v1alpha1" @@ -40,6 +41,7 @@ func init() { utilruntime.Must(sharingv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(virtualkubeletv1alpha1.AddToScheme(scheme.Scheme)) utilruntime.Must(networkingv1alpha1.AddToScheme(scheme.Scheme)) + utilruntime.Must(ipamv1alpha1.AddToScheme(scheme.Scheme)) } func main() { diff --git a/cmd/liqonet/network-manager.go b/cmd/liqonet/network-manager.go index 96efac84b8..1bc2b889d6 100644 --- a/cmd/liqonet/network-manager.go +++ b/cmd/liqonet/network-manager.go @@ -30,7 +30,7 @@ import ( "github.com/liqotech/liqo/internal/liqonet/network-manager/netcfgcreator" "github.com/liqotech/liqo/internal/liqonet/network-manager/tunnelendpointcreator" - liqoconst "github.com/liqotech/liqo/pkg/consts" + "github.com/liqotech/liqo/pkg/consts" liqoipam "github.com/liqotech/liqo/pkg/ipam" liqonetutils "github.com/liqotech/liqo/pkg/liqonet/utils" "github.com/liqotech/liqo/pkg/utils/args" @@ -132,7 +132,7 @@ func runNetworkManager(commonFlags *liqonetCommonFlags, managerFlags *networkMan func initializeIPAM(dynClient dynamic.Interface, managerFlags *networkManagerFlags) (*liqoipam.IPAM, error) { ipam := liqoipam.NewIPAM() - if err := ipam.Init(liqoipam.Pools, dynClient, liqoconst.IpamPort); err != nil { + if err := ipam.Init(liqoipam.Pools, dynClient); err != nil { return nil, err } @@ -153,5 +153,9 @@ func initializeIPAM(dynClient dynamic.Interface, managerFlags *networkManagerFla return nil, err } + if err := ipam.Serve(consts.IpamPort); err != nil { + return nil, err + } + return ipam, nil } diff --git a/deployments/liqo/README.md b/deployments/liqo/README.md index c47183282d..ad96095cc2 100644 --- a/deployments/liqo/README.md +++ b/deployments/liqo/README.md @@ -102,6 +102,7 @@ | ipam.additionalPools | list | `[]` | Set of additional network pools to perform the automatic address mapping in Liqo. Network pools are used to map a cluster network into another one in order to prevent conflicts. Default set of network pools is: [10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12] | | ipam.external.enabled | bool | `false` | Use an external IPAM to allocate the IP addresses for the pods. Enabling it will disable the internal IPAM. | | ipam.external.url | string | `""` | The URL of the external IPAM. | +| ipam.externalCIDR | string | `""` | The subnet used for the external CIDR. If empty, the default value (10.70.0.0/16) is used. | | ipam.internal.enabled | bool | `true` | Use the default Liqo IPAM. | | ipam.internal.image.name | string | `"ghcr.io/liqotech/ipam"` | Image repository for the IPAM pod. | | ipam.internal.image.version | string | `""` | Custom version for the IPAM image. If not specified, the global tag is used. | @@ -110,7 +111,7 @@ | ipam.internal.pod.labels | object | `{}` | Labels for the IPAM pod. | | ipam.internal.pod.resources | object | `{"limits":{},"requests":{}}` | Resource requests and limits (https://kubernetes.io/docs/user-guide/compute-resources/) for the proxy pod. | | ipam.internal.replicas | int | `1` | The number of IPAM instances to run, which can be increased for active/passive high availability. | -| ipam.legacy | bool | `true` | | +| ipam.legacy | bool | `false` | | | ipam.podCIDR | string | `""` | The subnet used by the pods in your cluster, in CIDR notation (e.g., 10.0.0.0/16). | | ipam.reservedSubnets | list | `[]` | List of IP subnets that do not have to be used by Liqo. Liqo can perform automatic IP address remapping when a remote cluster is peering with you, e.g., in case IP address spaces (e.g., PodCIDR) overlaps. In order to prevent IP conflicting between locally used private subnets in your infrastructure and private subnets belonging to remote clusters you need tell liqo the subnets used in your cluster. E.g if your cluster nodes belong to the 192.168.2.0/24 subnet, then you should add that subnet to the reservedSubnets. PodCIDR and serviceCIDR used in the local cluster are automatically added to the reserved list. | | ipam.serviceCIDR | string | `""` | The subnet used by the services in you cluster, in CIDR notation (e.g., 172.16.0.0/16). | 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..e85019552a 100644 --- a/deployments/liqo/files/liqo-ipam-ClusterRole.yaml +++ b/deployments/liqo/files/liqo-ipam-ClusterRole.yaml @@ -1,4 +1,12 @@ rules: +- apiGroups: + - apps + resources: + - deployments + verbs: + - get + - list + - watch - apiGroups: - coordination.k8s.io resources: @@ -20,6 +28,16 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - patch + - update + - watch - apiGroups: - net.liqo.io resources: diff --git a/deployments/liqo/templates/liqo-ipam-deployment.yaml b/deployments/liqo/templates/liqo-ipam-deployment.yaml index 70146d7b64..807e8fbc6c 100644 --- a/deployments/liqo/templates/liqo-ipam-deployment.yaml +++ b/deployments/liqo/templates/liqo-ipam-deployment.yaml @@ -1,8 +1,8 @@ ---- -{{- $ipamConfig := (merge (dict "name" "ipam" "module" "ipam" "version" .Values.ipam.internal.image.version) .) -}} - {{- if and (.Values.networking.internal) (not .Values.ipam.external.enabled) (not .Values.ipam.legacy ) }} +{{- $ipamConfig := (merge (dict "name" "ipam" "module" "ipam" "version" .Values.ipam.internal.image.version) .) -}} +{{- $ha := (gt .Values.ipam.internal.replicas 1.0) -}} + apiVersion: apps/v1 kind: Deployment metadata: @@ -39,9 +39,21 @@ spec: ports: - name: ipam-api containerPort: 6000 + {{- if not $ha }} + livenessProbe: + grpc: + port: 6000 + initialDelaySeconds: 1 + readinessProbe: + grpc: + port: 6000 + {{- end }} args: - --pod-cidr={{ .Values.ipam.podCIDR }} - --service-cidr={{ .Values.ipam.serviceCIDR }} + {{- if $ha }} + - --lease-enabled=true + {{- end }} {{- if .Values.ipam.reservedSubnets }} {{- $d := dict "commandName" "--reserved-pools" "list" .Values.ipam.reservedSubnets }} {{- include "liqo.concatenateList" $d | nindent 12 }} @@ -65,6 +77,8 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + - name: DEPLOYMENT_NAME + value: {{ include "liqo.prefixedName" $ipamConfig }} resources: {{- toYaml .Values.ipam.internal.pod.resources | nindent 12 }} {{- if ((.Values.common).nodeSelector) }} nodeSelector: diff --git a/deployments/liqo/templates/liqo-ipam-networks.yaml b/deployments/liqo/templates/liqo-ipam-networks.yaml new file mode 100644 index 0000000000..10705bad60 --- /dev/null +++ b/deployments/liqo/templates/liqo-ipam-networks.yaml @@ -0,0 +1,52 @@ +{{- $ipamConfig := (merge (dict "name" "ipam" "module" "ipam") .) -}} +--- +apiVersion: ipam.liqo.io/v1alpha1 +kind: Network +metadata: + name: pod-cidr + labels: + {{- include "liqo.labels" $ipamConfig | nindent 4 }} + ipam.liqo.io/network-type: pod-cidr + ipam.liqo.io/network-not-remapped: "true" +spec: + cidr: {{ .Values.ipam.podCIDR }} +--- +apiVersion: ipam.liqo.io/v1alpha1 +kind: Network +metadata: + name: service-cidr + labels: + {{- include "liqo.labels" $ipamConfig | nindent 4 }} + ipam.liqo.io/network-type: service-cidr + ipam.liqo.io/network-not-remapped: "true" +spec: + cidr: {{ .Values.ipam.serviceCIDR }} +--- +apiVersion: ipam.liqo.io/v1alpha1 +kind: Network +metadata: + name: external-cidr + labels: + {{- include "liqo.labels" $ipamConfig | nindent 4 }} + ipam.liqo.io/network-type: external-cidr +spec: + {{- if .Values.ipam.externalCIDR }} + cidr: {{ .Values.ipam.externalCIDR }} + {{- else }} + cidr: 10.70.0.0/16 + {{- end }} +--- +{{- range $i, $value := .Values.ipam.reservedSubnets }} +apiVersion: ipam.liqo.io/v1alpha1 +kind: Network +metadata: + name: reserved-{{ add $i 1 }} + labels: + {{- include "liqo.labels" $ipamConfig | nindent 4 }} + ipam.liqo.io/network-type: reserved + ipam.liqo.io/network-not-remapped: "true" +spec: + cidr: {{ $value }} +--- +{{- end }} + diff --git a/deployments/liqo/templates/liqo-ipam-service.yaml b/deployments/liqo/templates/liqo-ipam-service.yaml index 6d1f24d037..8a5893f3f5 100644 --- a/deployments/liqo/templates/liqo-ipam-service.yaml +++ b/deployments/liqo/templates/liqo-ipam-service.yaml @@ -1,8 +1,8 @@ ---- -{{- $ipamConfig := (merge (dict "name" "ipam" "module" "ipam") .) -}} - {{- if and (.Values.networking.internal) (not .Values.ipam.external.enabled) (not .Values.ipam.legacy ) }} +{{- $ipamConfig := (merge (dict "name" "ipam" "module" "ipam") .) -}} +{{- $ha := (gt .Values.ipam.internal.replicas 1.0) -}} + apiVersion: v1 kind: Service metadata: @@ -18,5 +18,8 @@ spec: protocol: TCP selector: {{- include "liqo.selectorLabels" $ipamConfig | nindent 4 }} + {{- if $ha }} + leaderelection.liqo.io/leader: "true" + {{- end }} {{- end }} diff --git a/deployments/liqo/values.yaml b/deployments/liqo/values.yaml index faebbe624b..e6b8b6ae8b 100644 --- a/deployments/liqo/values.yaml +++ b/deployments/liqo/values.yaml @@ -173,7 +173,7 @@ controllerManager: enableNodeFailureController: false ipam: - legacy: true + legacy: false external: # -- Use an external IPAM to allocate the IP addresses for the pods. Enabling it will disable the internal IPAM. enabled: false @@ -204,6 +204,8 @@ ipam: podCIDR: "" # -- The subnet used by the services in you cluster, in CIDR notation (e.g., 172.16.0.0/16). serviceCIDR: "" + # -- The subnet used for the external CIDR. If empty, the default value (10.70.0.0/16) is used. + externalCIDR: "" # -- List of IP subnets that do not have to be used by Liqo. # Liqo can perform automatic IP address remapping when a remote cluster is peering with you, e.g., in case IP address spaces (e.g., PodCIDR) overlaps. # In order to prevent IP conflicting between locally used private subnets in your infrastructure and private subnets belonging to remote clusters diff --git a/pkg/consts/ipam.go b/pkg/consts/ipam.go index aa6845b521..4b3c998769 100644 --- a/pkg/consts/ipam.go +++ b/pkg/consts/ipam.go @@ -14,7 +14,26 @@ package consts +// NetworkType indicates the type of Network. +type NetworkType string + const ( // IpamPort is the port used by the IPAM gRPC server. IpamPort = 6000 + + // NetworkNotRemappedLabelKey is the label key used to mark a Network that does not need CIDR remapping. + NetworkNotRemappedLabelKey = "ipam.liqo.io/network-not-remapped" + // NetworkNotRemappedLabelValue is the label value used to mark a Network that does not need CIDR remapping. + NetworkNotRemappedLabelValue = "true" + + // NetworkTypeLabelKey is the label key used to indicate the type of a Network. + NetworkTypeLabelKey = "ipam.liqo.io/network-type" + // NetworkTypePodCIDR is the constant representing a network of type podCIDR. + NetworkTypePodCIDR NetworkType = "pod-cidr" + // NetworkTypeServiceCIDR is the constant representing a network of type serviceCIDR. + NetworkTypeServiceCIDR NetworkType = "service-cidr" + // NetworkTypeExternalCIDR is the constant representing a network of type externalCIDR. + NetworkTypeExternalCIDR NetworkType = "external-cidr" + // NetworkTypeReserved is the constant representing a network of type reserved subnet. + NetworkTypeReserved NetworkType = "reserved" ) diff --git a/pkg/ipam/fake/ipam.go b/pkg/ipam/fake/ipam.go index 017d214900..acfd51b2cf 100644 --- a/pkg/ipam/fake/ipam.go +++ b/pkg/ipam/fake/ipam.go @@ -115,3 +115,9 @@ func (mock *IPAMClient) MapNetworkCIDR(_ context.Context, req *ipam.MapCIDRReque func (mock *IPAMClient) UnmapNetworkCIDR(_ context.Context, _ *ipam.UnmapCIDRRequest, _ ...grpc.CallOption) (*ipam.UnmapCIDRResponse, error) { return &ipam.UnmapCIDRResponse{}, nil } + +// GetOrSetExternalCIDR mocks the corresponding IPAMClient function. +func (mock *IPAMClient) GetOrSetExternalCIDR(_ context.Context, req *ipam.GetOrSetExtCIDRRequest, + _ ...grpc.CallOption) (*ipam.GetOrSetExtCIDRResponse, error) { + return &ipam.GetOrSetExtCIDRResponse{RemappedExtCIDR: req.DesiredExtCIDR}, nil +} diff --git a/pkg/ipam/flags.go b/pkg/ipam/flags.go index 8a7675bcf2..8ba763ad0a 100644 --- a/pkg/ipam/flags.go +++ b/pkg/ipam/flags.go @@ -46,6 +46,8 @@ const ( FlagNameLeaseRenewDeadline FlagName = "lease-renew-interval" // FlagNameLeaseRetryPeriod is the duration the LeaderElector clients should wait between tries of actions. FlagNameLeaseRetryPeriod FlagName = "lease-retry-period" + // FlagNameLabelLeader is the flag to enable the label of the leader node. + FlagNameLabelLeader FlagName = "label-leader" ) // RequiredFlags contains the list of the mandatory flags. @@ -63,7 +65,7 @@ func InitFlags(flagset *pflag.FlagSet, o *Options) { flagset.Var(&o.AdditionalPools, FlagNameAdditionalPools.String(), "Network pools used to map a cluster network into another one in order to prevent conflicts, in addition to standard private CIDRs.") - flagset.BoolVar(&o.LeaseEnabled, FlagNameLeaseEnabled.String(), true, + flagset.BoolVar(&o.LeaseEnabled, FlagNameLeaseEnabled.String(), false, "Enable the lease for the IPAM pods. Disabling it will disable IPAM high-availability.") flagset.DurationVar(&o.LeaseDuration, FlagNameLeaseDuration.String(), 15*time.Second, "The duration that non-leader candidates will wait to force acquire leadership.") @@ -71,6 +73,8 @@ func InitFlags(flagset *pflag.FlagSet, o *Options) { "The duration that the acting master will retry refreshing leadership before giving up.") flagset.DurationVar(&o.LeaseRetryPeriod, FlagNameLeaseRetryPeriod.String(), 5*time.Second, "The duration the LeaderElector clients should wait between tries of actions.") + flagset.BoolVar(&o.LabelLeader, FlagNameLabelLeader.String(), true, + "Label the leader node.") } // MarkFlagsRequired marks the flags as required. diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go index 77fa9e8f95..f3eca6d1ff 100644 --- a/pkg/ipam/ipam.go +++ b/pkg/ipam/ipam.go @@ -26,6 +26,8 @@ import ( goipam "github.com/metal-stack/go-ipam" "go4.org/netipx" grpc "google.golang.org/grpc" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" "k8s.io/client-go/dynamic" "k8s.io/klog/v2" @@ -67,6 +69,7 @@ type Ipam interface { this function must not reserve it. If the remote cluster has not remapped a local subnet, then CIDR value should be equal to "None". */ AddLocalSubnetsPerCluster(podCIDR, externalCIDR, clusterID string) error + // GetExternalCIDR chooses and returns the local cluster's ExternalCIDR. GetExternalCIDR(mask uint8) (string, error) // SetPodCIDR sets the cluster PodCIDR. SetPodCIDR(podCIDR string) error @@ -104,7 +107,7 @@ var Pools = []string{ const emptyCIDR = "" // Init uses the Ipam resource to retrieve and allocate reserved networks. -func (liqoIPAM *IPAM) Init(pools []string, dynClient dynamic.Interface, listeningPort int) error { +func (liqoIPAM *IPAM) Init(pools []string, dynClient dynamic.Interface) error { var err error // Set up storage liqoIPAM.ipamStorage, err = NewIPAMStorage(dynClient) @@ -130,17 +133,26 @@ func (liqoIPAM *IPAM) Init(pools []string, dynClient dynamic.Interface, listenin return fmt.Errorf("cannot set pools: %w", err) } } - if listeningPort > 0 { - err = liqoIPAM.initRPCServer(listeningPort) - if err != nil { - return fmt.Errorf("cannot start gRPC server: %w", err) - } - } liqoIPAM.natMappingInflater = natmappinginflater.NewInflater(dynClient) return nil } +// Serve starts the gRPC server. +func (liqoIPAM *IPAM) Serve(listeningPort int) error { + if listeningPort <= 0 { + return fmt.Errorf("IPAM gRPC server not started: invalid listening port %d", listeningPort) + } + + if err := liqoIPAM.initRPCServer(listeningPort); err != nil { + return fmt.Errorf("cannot start gRPC server: %w", err) + } + + klog.Infof("IPAM gRPC server listening on port %d", listeningPort) + + return nil +} + // Terminate function stops the gRPC server. func (liqoIPAM *IPAM) Terminate() { // Stop GRPC server @@ -156,13 +168,22 @@ func (liqoIPAM *IPAM) initRPCServer(port int) error { return err } liqoIPAM.grpcServer = grpc.NewServer() + + // Register health service + hs := health.NewServer() + grpc_health_v1.RegisterHealthServer(liqoIPAM.grpcServer, hs) + + // Register IPAM service RegisterIpamServer(liqoIPAM.grpcServer, liqoIPAM) + + // Start serving go func() { err := liqoIPAM.grpcServer.Serve(lis) if err != nil { klog.Error(err) } }() + return nil } @@ -378,9 +399,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 +409,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 { @@ -398,6 +418,28 @@ func (liqoIPAM *IPAM) UnmapNetworkCIDR(_ context.Context, unmapCIDRRequest *Unma return &UnmapCIDRResponse{}, nil } +// GetOrSetExternalCIDR get or set the external CIDR (eventually remapped) for the cluster. +func (liqoIPAM *IPAM) GetOrSetExternalCIDR(_ context.Context, getOrSetExtCIDRRequest *GetOrSetExtCIDRRequest) (*GetOrSetExtCIDRResponse, error) { + // Get cluster externalCIDR if already set + externalCIDR := liqoIPAM.ipamStorage.getExternalCIDR() + if externalCIDR != "" { + return &GetOrSetExtCIDRResponse{RemappedExtCIDR: externalCIDR}, nil + } + + // ExternalCIDR is not set: allocate a new network (eventually remapped if conflicts are found) + externalCIDR, err := liqoIPAM.getOrRemapNetwork(getOrSetExtCIDRRequest.GetDesiredExtCIDR()) + if err != nil { + return &GetOrSetExtCIDRResponse{}, fmt.Errorf("cannot map external CIDR %s: %w", getOrSetExtCIDRRequest.GetDesiredExtCIDR(), err) + } + + // Update ipamstorage with the new external CIDR + if err := liqoIPAM.ipamStorage.updateExternalCIDR(externalCIDR); err != nil { + _ = liqoIPAM.FreeReservedSubnet(externalCIDR) + return &GetOrSetExtCIDRResponse{}, fmt.Errorf("cannot update external CIDR in the ipam storage: %w", err) + } + return &GetOrSetExtCIDRResponse{RemappedExtCIDR: externalCIDR}, nil +} + // getOrRemapNetwork first tries to acquire the received network. // If conflicts are found, a new mapped network is returned. func (liqoIPAM *IPAM) getOrRemapNetwork(network string) (string, error) { @@ -1160,7 +1202,7 @@ func (liqoIPAM *IPAM) mapEndpointIPInternal(clusterID, ip string) (string, error // MapEndpointIP receives a service endpoint IP and a cluster identifier and, // if the endpoint IP does not belong to cluster PodCIDR, maps // the endpoint IP to a new IP taken from the remote ExternalCIDR of the remote cluster. -func (liqoIPAM *IPAM) MapEndpointIP(ctx context.Context, mapRequest *MapRequest) (*MapResponse, error) { +func (liqoIPAM *IPAM) MapEndpointIP(_ context.Context, mapRequest *MapRequest) (*MapResponse, error) { mappedIP, err := liqoIPAM.mapEndpointIPInternal(mapRequest.GetClusterID(), mapRequest.GetIp()) if err != nil { return &MapResponse{}, fmt.Errorf("cannot map endpoint IP to ExternalCIDR of cluster %s, %w", diff --git a/pkg/ipam/ipam.pb.go b/pkg/ipam/ipam.pb.go index 8992704d44..348228f8c0 100644 --- a/pkg/ipam/ipam.pb.go +++ b/pkg/ipam/ipam.pb.go @@ -591,6 +591,100 @@ func (x *BelongsResponse) GetBelongs() bool { return false } +type GetOrSetExtCIDRRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DesiredExtCIDR string `protobuf:"bytes,1,opt,name=desiredExtCIDR,proto3" json:"desiredExtCIDR,omitempty"` +} + +func (x *GetOrSetExtCIDRRequest) Reset() { + *x = GetOrSetExtCIDRRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_ipam_ipam_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrSetExtCIDRRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrSetExtCIDRRequest) ProtoMessage() {} + +func (x *GetOrSetExtCIDRRequest) ProtoReflect() protoreflect.Message { + mi := &file_pkg_ipam_ipam_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrSetExtCIDRRequest.ProtoReflect.Descriptor instead. +func (*GetOrSetExtCIDRRequest) Descriptor() ([]byte, []int) { + return file_pkg_ipam_ipam_proto_rawDescGZIP(), []int{12} +} + +func (x *GetOrSetExtCIDRRequest) GetDesiredExtCIDR() string { + if x != nil { + return x.DesiredExtCIDR + } + return "" +} + +type GetOrSetExtCIDRResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RemappedExtCIDR string `protobuf:"bytes,1,opt,name=remappedExtCIDR,proto3" json:"remappedExtCIDR,omitempty"` +} + +func (x *GetOrSetExtCIDRResponse) Reset() { + *x = GetOrSetExtCIDRResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_ipam_ipam_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetOrSetExtCIDRResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetOrSetExtCIDRResponse) ProtoMessage() {} + +func (x *GetOrSetExtCIDRResponse) ProtoReflect() protoreflect.Message { + mi := &file_pkg_ipam_ipam_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetOrSetExtCIDRResponse.ProtoReflect.Descriptor instead. +func (*GetOrSetExtCIDRResponse) Descriptor() ([]byte, []int) { + return file_pkg_ipam_ipam_proto_rawDescGZIP(), []int{13} +} + +func (x *GetOrSetExtCIDRResponse) GetRemappedExtCIDR() string { + if x != nil { + return x.RemappedExtCIDR + } + return "" +} + var File_pkg_ipam_ipam_proto protoreflect.FileDescriptor var file_pkg_ipam_ipam_proto_rawDesc = []byte{ @@ -626,29 +720,42 @@ var file_pkg_ipam_ipam_proto_rawDesc = []byte{ 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0x2b, 0x0a, 0x0f, 0x42, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x62, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x32, 0xc8, 0x02, - 0x0a, 0x04, 0x69, 0x70, 0x61, 0x6d, 0x12, 0x2a, 0x0a, 0x0d, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x49, 0x50, 0x12, 0x0b, 0x2e, 0x4d, 0x61, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x4d, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x30, 0x0a, 0x0f, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x49, 0x50, 0x12, 0x0d, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0e, 0x4d, 0x61, 0x70, 0x4e, 0x65, 0x74, 0x77, 0x6f, - 0x72, 0x6b, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0f, 0x2e, 0x4d, 0x61, 0x70, 0x43, 0x49, 0x44, 0x52, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x4d, 0x61, 0x70, 0x43, 0x49, 0x44, - 0x52, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x6d, - 0x61, 0x70, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x49, 0x44, 0x52, 0x12, 0x11, 0x2e, - 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x43, 0x49, 0x44, 0x52, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x12, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x43, 0x49, 0x44, 0x52, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x6d, 0x65, 0x50, - 0x6f, 0x64, 0x49, 0x50, 0x12, 0x14, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x6d, 0x65, 0x50, 0x6f, - 0x64, 0x49, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, - 0x48, 0x6f, 0x6d, 0x65, 0x50, 0x6f, 0x64, 0x49, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x35, 0x0a, 0x10, 0x42, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x50, 0x6f, - 0x64, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0f, 0x2e, 0x42, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x42, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x69, 0x70, - 0x61, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x62, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x22, 0x40, 0x0a, + 0x16, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x53, 0x65, 0x74, 0x45, 0x78, 0x74, 0x43, 0x49, 0x44, 0x52, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x64, 0x65, 0x73, 0x69, 0x72, + 0x65, 0x64, 0x45, 0x78, 0x74, 0x43, 0x49, 0x44, 0x52, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x64, 0x65, 0x73, 0x69, 0x72, 0x65, 0x64, 0x45, 0x78, 0x74, 0x43, 0x49, 0x44, 0x52, 0x22, + 0x43, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x53, 0x65, 0x74, 0x45, 0x78, 0x74, 0x43, 0x49, + 0x44, 0x52, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x72, 0x65, + 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x45, 0x78, 0x74, 0x43, 0x49, 0x44, 0x52, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x72, 0x65, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x64, 0x45, 0x78, 0x74, + 0x43, 0x49, 0x44, 0x52, 0x32, 0x93, 0x03, 0x0a, 0x04, 0x69, 0x70, 0x61, 0x6d, 0x12, 0x2a, 0x0a, + 0x0d, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x49, 0x50, 0x12, 0x0b, + 0x2e, 0x4d, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x4d, 0x61, + 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x0f, 0x55, 0x6e, 0x6d, + 0x61, 0x70, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x49, 0x50, 0x12, 0x0d, 0x2e, 0x55, + 0x6e, 0x6d, 0x61, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x55, 0x6e, + 0x6d, 0x61, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x0e, 0x4d, + 0x61, 0x70, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0f, 0x2e, + 0x4d, 0x61, 0x70, 0x43, 0x49, 0x44, 0x52, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, + 0x2e, 0x4d, 0x61, 0x70, 0x43, 0x49, 0x44, 0x52, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x39, 0x0a, 0x10, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x43, 0x49, 0x44, 0x52, 0x12, 0x11, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x43, 0x49, 0x44, 0x52, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x55, 0x6e, 0x6d, 0x61, 0x70, 0x43, + 0x49, 0x44, 0x52, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x0c, 0x47, + 0x65, 0x74, 0x48, 0x6f, 0x6d, 0x65, 0x50, 0x6f, 0x64, 0x49, 0x50, 0x12, 0x14, 0x2e, 0x47, 0x65, + 0x74, 0x48, 0x6f, 0x6d, 0x65, 0x50, 0x6f, 0x64, 0x49, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x15, 0x2e, 0x47, 0x65, 0x74, 0x48, 0x6f, 0x6d, 0x65, 0x50, 0x6f, 0x64, 0x49, 0x50, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x10, 0x42, 0x65, 0x6c, 0x6f, + 0x6e, 0x67, 0x73, 0x54, 0x6f, 0x50, 0x6f, 0x64, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0f, 0x2e, 0x42, + 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, + 0x42, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x49, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x53, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x43, 0x49, 0x44, 0x52, 0x12, 0x17, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x53, + 0x65, 0x74, 0x45, 0x78, 0x74, 0x43, 0x49, 0x44, 0x52, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x18, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x72, 0x53, 0x65, 0x74, 0x45, 0x78, 0x74, 0x43, 0x49, + 0x44, 0x52, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, + 0x69, 0x70, 0x61, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -663,20 +770,22 @@ func file_pkg_ipam_ipam_proto_rawDescGZIP() []byte { return file_pkg_ipam_ipam_proto_rawDescData } -var file_pkg_ipam_ipam_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_pkg_ipam_ipam_proto_msgTypes = make([]protoimpl.MessageInfo, 14) var file_pkg_ipam_ipam_proto_goTypes = []interface{}{ - (*MapRequest)(nil), // 0: MapRequest - (*MapResponse)(nil), // 1: MapResponse - (*UnmapRequest)(nil), // 2: UnmapRequest - (*UnmapResponse)(nil), // 3: UnmapResponse - (*MapCIDRRequest)(nil), // 4: MapCIDRRequest - (*MapCIDRResponse)(nil), // 5: MapCIDRResponse - (*UnmapCIDRRequest)(nil), // 6: UnmapCIDRRequest - (*UnmapCIDRResponse)(nil), // 7: UnmapCIDRResponse - (*GetHomePodIPRequest)(nil), // 8: GetHomePodIPRequest - (*GetHomePodIPResponse)(nil), // 9: GetHomePodIPResponse - (*BelongsRequest)(nil), // 10: BelongsRequest - (*BelongsResponse)(nil), // 11: BelongsResponse + (*MapRequest)(nil), // 0: MapRequest + (*MapResponse)(nil), // 1: MapResponse + (*UnmapRequest)(nil), // 2: UnmapRequest + (*UnmapResponse)(nil), // 3: UnmapResponse + (*MapCIDRRequest)(nil), // 4: MapCIDRRequest + (*MapCIDRResponse)(nil), // 5: MapCIDRResponse + (*UnmapCIDRRequest)(nil), // 6: UnmapCIDRRequest + (*UnmapCIDRResponse)(nil), // 7: UnmapCIDRResponse + (*GetHomePodIPRequest)(nil), // 8: GetHomePodIPRequest + (*GetHomePodIPResponse)(nil), // 9: GetHomePodIPResponse + (*BelongsRequest)(nil), // 10: BelongsRequest + (*BelongsResponse)(nil), // 11: BelongsResponse + (*GetOrSetExtCIDRRequest)(nil), // 12: GetOrSetExtCIDRRequest + (*GetOrSetExtCIDRResponse)(nil), // 13: GetOrSetExtCIDRResponse } var file_pkg_ipam_ipam_proto_depIdxs = []int32{ 0, // 0: ipam.MapEndpointIP:input_type -> MapRequest @@ -685,14 +794,16 @@ var file_pkg_ipam_ipam_proto_depIdxs = []int32{ 6, // 3: ipam.UnmapNetworkCIDR:input_type -> UnmapCIDRRequest 8, // 4: ipam.GetHomePodIP:input_type -> GetHomePodIPRequest 10, // 5: ipam.BelongsToPodCIDR:input_type -> BelongsRequest - 1, // 6: ipam.MapEndpointIP:output_type -> MapResponse - 3, // 7: ipam.UnmapEndpointIP:output_type -> UnmapResponse - 5, // 8: ipam.MapNetworkCIDR:output_type -> MapCIDRResponse - 7, // 9: ipam.UnmapNetworkCIDR:output_type -> UnmapCIDRResponse - 9, // 10: ipam.GetHomePodIP:output_type -> GetHomePodIPResponse - 11, // 11: ipam.BelongsToPodCIDR:output_type -> BelongsResponse - 6, // [6:12] is the sub-list for method output_type - 0, // [0:6] is the sub-list for method input_type + 12, // 6: ipam.GetOrSetExternalCIDR:input_type -> GetOrSetExtCIDRRequest + 1, // 7: ipam.MapEndpointIP:output_type -> MapResponse + 3, // 8: ipam.UnmapEndpointIP:output_type -> UnmapResponse + 5, // 9: ipam.MapNetworkCIDR:output_type -> MapCIDRResponse + 7, // 10: ipam.UnmapNetworkCIDR:output_type -> UnmapCIDRResponse + 9, // 11: ipam.GetHomePodIP:output_type -> GetHomePodIPResponse + 11, // 12: ipam.BelongsToPodCIDR:output_type -> BelongsResponse + 13, // 13: ipam.GetOrSetExternalCIDR:output_type -> GetOrSetExtCIDRResponse + 7, // [7:14] is the sub-list for method output_type + 0, // [0:7] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -848,6 +959,30 @@ func file_pkg_ipam_ipam_proto_init() { return nil } } + file_pkg_ipam_ipam_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrSetExtCIDRRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_ipam_ipam_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetOrSetExtCIDRResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -855,7 +990,7 @@ func file_pkg_ipam_ipam_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_pkg_ipam_ipam_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 14, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/ipam/ipam.proto b/pkg/ipam/ipam.proto index 924e97f5d5..f6221386f6 100644 --- a/pkg/ipam/ipam.proto +++ b/pkg/ipam/ipam.proto @@ -8,6 +8,7 @@ service ipam { rpc UnmapNetworkCIDR (UnmapCIDRRequest) returns (UnmapCIDRResponse); rpc GetHomePodIP (GetHomePodIPRequest) returns (GetHomePodIPResponse); rpc BelongsToPodCIDR (BelongsRequest) returns (BelongsResponse); + rpc GetOrSetExternalCIDR(GetOrSetExtCIDRRequest) returns (GetOrSetExtCIDRResponse); } message MapRequest { @@ -55,4 +56,12 @@ message BelongsRequest { message BelongsResponse { bool belongs = 1; -} \ No newline at end of file +} + +message GetOrSetExtCIDRRequest { + string desiredExtCIDR = 1; +} + +message GetOrSetExtCIDRResponse { + string remappedExtCIDR = 1; +} diff --git a/pkg/ipam/ipam_grpc.pb.go b/pkg/ipam/ipam_grpc.pb.go index e2e85df2f3..a7ccfe7e62 100644 --- a/pkg/ipam/ipam_grpc.pb.go +++ b/pkg/ipam/ipam_grpc.pb.go @@ -20,12 +20,13 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - Ipam_MapEndpointIP_FullMethodName = "/ipam/MapEndpointIP" - Ipam_UnmapEndpointIP_FullMethodName = "/ipam/UnmapEndpointIP" - Ipam_MapNetworkCIDR_FullMethodName = "/ipam/MapNetworkCIDR" - Ipam_UnmapNetworkCIDR_FullMethodName = "/ipam/UnmapNetworkCIDR" - Ipam_GetHomePodIP_FullMethodName = "/ipam/GetHomePodIP" - Ipam_BelongsToPodCIDR_FullMethodName = "/ipam/BelongsToPodCIDR" + Ipam_MapEndpointIP_FullMethodName = "/ipam/MapEndpointIP" + Ipam_UnmapEndpointIP_FullMethodName = "/ipam/UnmapEndpointIP" + Ipam_MapNetworkCIDR_FullMethodName = "/ipam/MapNetworkCIDR" + Ipam_UnmapNetworkCIDR_FullMethodName = "/ipam/UnmapNetworkCIDR" + Ipam_GetHomePodIP_FullMethodName = "/ipam/GetHomePodIP" + Ipam_BelongsToPodCIDR_FullMethodName = "/ipam/BelongsToPodCIDR" + Ipam_GetOrSetExternalCIDR_FullMethodName = "/ipam/GetOrSetExternalCIDR" ) // IpamClient is the client API for Ipam service. @@ -38,6 +39,7 @@ type IpamClient interface { UnmapNetworkCIDR(ctx context.Context, in *UnmapCIDRRequest, opts ...grpc.CallOption) (*UnmapCIDRResponse, error) GetHomePodIP(ctx context.Context, in *GetHomePodIPRequest, opts ...grpc.CallOption) (*GetHomePodIPResponse, error) BelongsToPodCIDR(ctx context.Context, in *BelongsRequest, opts ...grpc.CallOption) (*BelongsResponse, error) + GetOrSetExternalCIDR(ctx context.Context, in *GetOrSetExtCIDRRequest, opts ...grpc.CallOption) (*GetOrSetExtCIDRResponse, error) } type ipamClient struct { @@ -102,6 +104,15 @@ func (c *ipamClient) BelongsToPodCIDR(ctx context.Context, in *BelongsRequest, o return out, nil } +func (c *ipamClient) GetOrSetExternalCIDR(ctx context.Context, in *GetOrSetExtCIDRRequest, opts ...grpc.CallOption) (*GetOrSetExtCIDRResponse, error) { + out := new(GetOrSetExtCIDRResponse) + err := c.cc.Invoke(ctx, Ipam_GetOrSetExternalCIDR_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // IpamServer is the server API for Ipam service. // All implementations must embed UnimplementedIpamServer // for forward compatibility @@ -112,6 +123,7 @@ type IpamServer interface { UnmapNetworkCIDR(context.Context, *UnmapCIDRRequest) (*UnmapCIDRResponse, error) GetHomePodIP(context.Context, *GetHomePodIPRequest) (*GetHomePodIPResponse, error) BelongsToPodCIDR(context.Context, *BelongsRequest) (*BelongsResponse, error) + GetOrSetExternalCIDR(context.Context, *GetOrSetExtCIDRRequest) (*GetOrSetExtCIDRResponse, error) mustEmbedUnimplementedIpamServer() } @@ -137,6 +149,9 @@ func (UnimplementedIpamServer) GetHomePodIP(context.Context, *GetHomePodIPReques func (UnimplementedIpamServer) BelongsToPodCIDR(context.Context, *BelongsRequest) (*BelongsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method BelongsToPodCIDR not implemented") } +func (UnimplementedIpamServer) GetOrSetExternalCIDR(context.Context, *GetOrSetExtCIDRRequest) (*GetOrSetExtCIDRResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOrSetExternalCIDR not implemented") +} func (UnimplementedIpamServer) mustEmbedUnimplementedIpamServer() {} // UnsafeIpamServer may be embedded to opt out of forward compatibility for this service. @@ -258,6 +273,24 @@ func _Ipam_BelongsToPodCIDR_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _Ipam_GetOrSetExternalCIDR_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrSetExtCIDRRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(IpamServer).GetOrSetExternalCIDR(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Ipam_GetOrSetExternalCIDR_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(IpamServer).GetOrSetExternalCIDR(ctx, req.(*GetOrSetExtCIDRRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Ipam_ServiceDesc is the grpc.ServiceDesc for Ipam service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -289,6 +322,10 @@ var Ipam_ServiceDesc = grpc.ServiceDesc{ MethodName: "BelongsToPodCIDR", Handler: _Ipam_BelongsToPodCIDR_Handler, }, + { + MethodName: "GetOrSetExternalCIDR", + Handler: _Ipam_GetOrSetExternalCIDR_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "pkg/ipam/ipam.proto", diff --git a/pkg/ipam/ipam_test.go b/pkg/ipam/ipam_test.go index 60e6ee8adb..302bc3e6de 100644 --- a/pkg/ipam/ipam_test.go +++ b/pkg/ipam/ipam_test.go @@ -135,7 +135,9 @@ var _ = Describe("Ipam", func() { Expect(err).To(BeNil()) n, err := rand.Int(rand.Reader, big.NewInt(10000)) Expect(err).To(BeNil()) - err = ipam.Init(Pools, dynClient, 2000+int(n.Int64())) + err = ipam.Init(Pools, dynClient) + Expect(err).To(BeNil()) + err = ipam.Serve(2000 + int(n.Int64())) Expect(err).To(BeNil()) }) AfterEach(func() { @@ -621,7 +623,9 @@ var _ = Describe("Ipam", func() { ipam = NewIPAM() n, err := rand.Int(rand.Reader, big.NewInt(2000)) Expect(err).To(BeNil()) - err = ipam.Init(Pools, dynClient, 2000+int(n.Int64())) + err = ipam.Init(Pools, dynClient) + Expect(err).To(BeNil()) + err = ipam.Serve(2000 + int(n.Int64())) Expect(err).To(BeNil()) // Another cluster asks for the same networks diff --git a/pkg/ipam/options.go b/pkg/ipam/options.go index 90db722d04..de518c9fb0 100644 --- a/pkg/ipam/options.go +++ b/pkg/ipam/options.go @@ -32,6 +32,8 @@ type Options struct { LeaseDuration time.Duration LeaseRenewDeadline time.Duration LeaseRetryPeriod time.Duration + + LabelLeader bool } // NewOptions returns a new Options struct. diff --git a/pkg/leaderelection/leaderelection.go b/pkg/leaderelection/leaderelection.go index 0dced611d5..d91c1ff3eb 100644 --- a/pkg/leaderelection/leaderelection.go +++ b/pkg/leaderelection/leaderelection.go @@ -17,47 +17,63 @@ package leaderelection import ( "context" "fmt" + "os" + "strings" "sync" "time" + appsv1 "k8s.io/api/apps/v1" coordv1 "k8s.io/api/coordination/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" coordinationv1 "k8s.io/client-go/kubernetes/typed/coordination/v1" "k8s.io/client-go/rest" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" ) +const leaderLabel = "leaderelection.liqo.io/leader" + var ( - lock sync.RWMutex - leading = false + lock sync.RWMutex + leading = false + addToSchemeFunctions = []func(*runtime.Scheme) error{ + clientgoscheme.AddToScheme, + coordv1.AddToScheme, + } ) // Opts contains the options to configure the leader election mechanism. type Opts struct { PodName string Namespace string + DeploymentName *string LeaderElectorName string LeaseDuration time.Duration RenewDeadline time.Duration RetryPeriod time.Duration InitCallback func() StopCallback func() + LabelLeader bool } // Init initializes the leader election mechanism. func Init(opts *Opts, rc *rest.Config, eb record.EventBroadcaster) (*leaderelection.LeaderElector, error) { + // Adds the APIs to the scheme. scheme := runtime.NewScheme() - err := coordv1.AddToScheme(scheme) - if err != nil { - klog.Error(err) - return nil, err + for _, addToScheme := range addToSchemeFunctions { + if err := addToScheme(scheme); err != nil { + return nil, fmt.Errorf("unable to add scheme: %w", err) + } } + leaderelector, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{ Lock: &resourcelock.LeaseLock{ LeaseMeta: metav1.ObjectMeta{ @@ -75,6 +91,12 @@ func Init(opts *Opts, rc *rest.Config, eb record.EventBroadcaster) (*leaderelect lock.Lock() defer lock.Unlock() klog.Infof("Leader election: this pod is the leader") + if opts.LabelLeader { + if err := handleLeaderLabel(ctx, rc, scheme, opts); err != nil { + klog.Errorf("Leader election: unable to handle labeling of leader: %s", err) + os.Exit(1) + } + } leading = true if opts.InitCallback != nil { opts.InitCallback() @@ -122,3 +144,57 @@ func IsLeader() bool { defer lock.RUnlock() return leading } + +// handleLeaderLabel labels the current pod as leader and unlabels eventual old leader. +func handleLeaderLabel(ctx context.Context, rc *rest.Config, scheme *runtime.Scheme, opts *Opts) error { + klog.Infof("Leader election: labeling this pod as leader and unlabeling eventual old leader") + if opts.DeploymentName == nil { + return fmt.Errorf("deployment name not specified") + } + + cl, err := client.New(rc, client.Options{Scheme: scheme}) + if err != nil { + return fmt.Errorf("unable to create client: %w", err) + } + + var deployment appsv1.Deployment + if err := cl.Get(ctx, client.ObjectKey{ + Namespace: opts.Namespace, + Name: *opts.DeploymentName, + }, &deployment); err != nil { + return fmt.Errorf("unable to get deployment: %w", err) + } + + podsFromDepSelector := client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(deployment.Spec.Selector.MatchLabels)} + var podList corev1.PodList + if err := cl.List(ctx, &podList, client.InNamespace(deployment.Namespace), podsFromDepSelector); err != nil { + return fmt.Errorf("unable to list pods of deployment %s/%s: %w", deployment.Namespace, deployment.Name, err) + } + + for i := range podList.Items { + pod := &podList.Items[i] + if pod.Name == opts.PodName { + // Label pod if it is the new leader. + if pod.Labels == nil { + pod.Labels = map[string]string{} + } + pod.Labels[leaderLabel] = "true" + if err := cl.Update(ctx, pod); err != nil { + return fmt.Errorf("unable to label pod %s/%s: %w", pod.Namespace, pod.Name, err) + } + klog.Infof("Leader election: pod %s/%s labeled as leader", pod.Namespace, pod.Name) + } else { + // Unlabel pod if it is the old leader. + value, ok := pod.Labels[leaderLabel] + if ok && !strings.EqualFold(value, "false") { + delete(pod.Labels, leaderLabel) + if err := cl.Update(ctx, pod); err != nil { + return fmt.Errorf("unable to remove label from pod %s/%s: %w", pod.Namespace, pod.Name, err) + } + klog.Infof("Leader election: pod %s/%s unlabeled as leader", pod.Namespace, pod.Name) + } + } + } + + return 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/ip-controller/ip_controller.go b/pkg/liqo-controller-manager/ip-controller/ip_controller.go index 5ec49faae8..e448804ede 100644 --- a/pkg/liqo-controller-manager/ip-controller/ip_controller.go +++ b/pkg/liqo-controller-manager/ip-controller/ip_controller.go @@ -37,6 +37,7 @@ import ( virtualkubeletv1alpha1 "github.com/liqotech/liqo/apis/virtualkubelet/v1alpha1" "github.com/liqotech/liqo/pkg/ipam" "github.com/liqotech/liqo/pkg/utils/getters" + ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" ) const ( @@ -46,8 +47,21 @@ const ( // IPReconciler reconciles a IP object. type IPReconciler struct { client.Client - Scheme *runtime.Scheme - IpamClient ipam.IpamClient + Scheme *runtime.Scheme + + ipamClient ipam.IpamClient + externalCIDRSet bool +} + +// NewIPReconciler returns a new IPReconciler. +func NewIPReconciler(cl client.Client, s *runtime.Scheme, ipamClient ipam.IpamClient) *IPReconciler { + return &IPReconciler{ + Client: cl, + Scheme: s, + + ipamClient: ipamClient, + externalCIDRSet: false, + } } // +kubebuilder:rbac:groups=ipam.liqo.io,resources=ips,verbs=get;list;watch;create;update;patch;delete @@ -72,6 +86,20 @@ func (r *IPReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Re return ctrl.Result{}, err } + if !r.externalCIDRSet { + // Retrieve the externalCIDR of the local cluster + _, err := ipamutils.RetrieveExternalCIDR(ctx, r.Client) + if apierrors.IsNotFound(err) { + klog.Errorf("ExternalCIDR is not set yet. Configure it to correctly handle IP mappings") + return ctrl.Result{}, err + } else if err != nil { + klog.Errorf("error while retrieving externalCIDR: %v", err) + return ctrl.Result{}, err + } + // The external CIDR is set, we do not need to check it again in successive reconciliations. + r.externalCIDRSet = true + } + desiredIP = ip.Spec.IP // Get the clusterIDs of all remote clusters @@ -180,7 +208,7 @@ func (r *IPReconciler) forgeIPMappings(ctx context.Context, clusterIDs []string, // multiple times by checking if the IP for that remote cluster is already set. _, found := ip.Status.IPMappings[*remoteClusterID] if !found { - remappedIP, err := getRemappedIP(ctx, r.IpamClient, *remoteClusterID, desiredIP) + remappedIP, err := getRemappedIP(ctx, r.ipamClient, *remoteClusterID, desiredIP) if err != nil { return false, err } @@ -194,7 +222,7 @@ func (r *IPReconciler) forgeIPMappings(ctx context.Context, clusterIDs []string, if !slices.Contains(clusterIDs, entry) { // We ignore eventual errors from the IPAM because the entries in the NatMappaings and IpamStorage for that cluster // may have been already removed. - _ = deleteRemappedIP(ctx, r.IpamClient, entry, desiredIP) + _ = deleteRemappedIP(ctx, r.ipamClient, entry, desiredIP) delete(ip.Status.IPMappings, entry) needUpdate = true } @@ -207,7 +235,7 @@ func (r *IPReconciler) forgeIPMappings(ctx context.Context, clusterIDs []string, func (r *IPReconciler) handleDelete(ctx context.Context, clusterIDs []string, desiredIP networkingv1alpha1.IP, ip *ipamv1alpha1.IP) error { for i := range clusterIDs { remoteClusterID := &clusterIDs[i] - if err := deleteRemappedIP(ctx, r.IpamClient, *remoteClusterID, desiredIP); err != nil { + if err := deleteRemappedIP(ctx, r.ipamClient, *remoteClusterID, desiredIP); err != nil { return err } delete(ip.Status.IPMappings, *remoteClusterID) diff --git a/pkg/liqo-controller-manager/network-controller/network_controller.go b/pkg/liqo-controller-manager/network-controller/network_controller.go index ceb2c26bb7..d32248738b 100644 --- a/pkg/liqo-controller-manager/network-controller/network_controller.go +++ b/pkg/liqo-controller-manager/network-controller/network_controller.go @@ -29,6 +29,7 @@ import ( ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" "github.com/liqotech/liqo/pkg/ipam" + ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" ) const ( @@ -38,8 +39,19 @@ const ( // NetworkReconciler reconciles a Network object. type NetworkReconciler struct { client.Client - Scheme *runtime.Scheme - IpamClient ipam.IpamClient + Scheme *runtime.Scheme + + ipamClient ipam.IpamClient +} + +// NewNetworkReconciler returns a new NetworkReconciler. +func NewNetworkReconciler(cl client.Client, s *runtime.Scheme, ipamClient ipam.IpamClient) *NetworkReconciler { + return &NetworkReconciler{ + Client: cl, + Scheme: s, + + ipamClient: ipamClient, + } } // +kubebuilder:rbac:groups=ipam.liqo.io,resources=networks,verbs=get;list;watch;create;update;patch;delete @@ -50,7 +62,6 @@ type NetworkReconciler struct { // Reconcile Network objects. func (r *NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var nw ipamv1alpha1.Network - var desiredCIDR, remappedCIDR networkingv1alpha1.CIDR // Fetch the Network instance if err := r.Get(ctx, req.NamespacedName, &nw); err != nil { @@ -62,80 +73,153 @@ func (r *NetworkReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct return ctrl.Result{}, err } - desiredCIDR = nw.Spec.CIDR + switch { + case ipamutils.NetworkNotRemapped(&nw): + if err := r.handleNetworkNotRemappedStatus(ctx, &nw); err != nil { + return ctrl.Result{}, err + } + case ipamutils.IsExternalCIDR(&nw): + if err := r.handleNetworkExternalCIDRStatus(ctx, &nw); err != nil { + return ctrl.Result{}, err + } + default: + if err := r.handleNetworkStatus(ctx, &nw); err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager monitors Network resources. +func (r *NetworkReconciler) SetupWithManager(mgr ctrl.Manager, workers int) error { + return ctrl.NewControllerManagedBy(mgr). + For(&ipamv1alpha1.Network{}). + WithOptions(controller.Options{MaxConcurrentReconciles: workers}). + Complete(r) +} + +// handleNetworkNotRemapped handles the status of a Network resource that does not need CIDR remapping. +func (r *NetworkReconciler) handleNetworkNotRemappedStatus(ctx context.Context, nw *ipamv1alpha1.Network) error { + nw.Status.CIDR = nw.Spec.CIDR // set the status to the desired CIDR. + if err := r.Status().Update(ctx, nw); err != nil { + klog.Errorf("error while updating Network %q status: %v", client.ObjectKeyFromObject(nw), err) + return err + } + klog.V(4).Infof("updated Network %q status (spec: %s -> status: %s)", client.ObjectKeyFromObject(nw), nw.Spec.CIDR, nw.Status.CIDR) + return nil +} + +// handleNetworkExternalCIDR handles the status of a Network resource of type ExternalCIDR. +func (r *NetworkReconciler) handleNetworkExternalCIDRStatus(ctx context.Context, nw *ipamv1alpha1.Network) error { if nw.GetDeletionTimestamp().IsZero() { - if !controllerutil.ContainsFinalizer(&nw, ipamNetworkFinalizer) { + // Update Network status if it is not set yet. + // The external CIDR can't change after it is set, so we avoid to call it + // multiple times by checking if the status is already set. + if nw.Status.CIDR == "" { + desiredCIDR := nw.Spec.CIDR + remappedCIDR, err := getOrSetExternalCIDR(ctx, r.ipamClient, desiredCIDR) + if err != nil { + return err + } + + // Update status + nw.Status.CIDR = remappedCIDR + if err := r.Client.Status().Update(ctx, nw); err != nil { + klog.Errorf("error while updating Network %q status: %v", client.ObjectKeyFromObject(nw), err) + return err + } + klog.Infof("updated Network %q status (spec: %s -> status: %s)", client.ObjectKeyFromObject(nw), desiredCIDR, remappedCIDR) + } + } + + return nil +} + +// getExternalCIDR returns the remapped external CIDR for the given CIDR. +func getOrSetExternalCIDR(ctx context.Context, ipamClient ipam.IpamClient, desiredCIDR networkingv1alpha1.CIDR) (networkingv1alpha1.CIDR, error) { + switch ipamClient.(type) { + case nil: + // IPAM is not enabled, use original CIDR from spec + return desiredCIDR, nil + default: + // interact with the IPAM to retrieve the correct mapping. + response, err := ipamClient.GetOrSetExternalCIDR(ctx, &ipam.GetOrSetExtCIDRRequest{DesiredExtCIDR: desiredCIDR.String()}) + if err != nil { + klog.Errorf("IPAM: error while mapping network external CIDR %s: %v", desiredCIDR, err) + return "", err + } + klog.Infof("IPAM: mapped network external CIDR %s to %s", desiredCIDR, response.RemappedExtCIDR) + return networkingv1alpha1.CIDR(response.RemappedExtCIDR), nil + } +} + +// handleNetworkStatus handles the status of a Network resource. +func (r *NetworkReconciler) handleNetworkStatus(ctx context.Context, nw *ipamv1alpha1.Network) error { + if nw.GetDeletionTimestamp().IsZero() { + if !controllerutil.ContainsFinalizer(nw, ipamNetworkFinalizer) { // Add finalizer to prevent deletion without unmapping the Network. - controllerutil.AddFinalizer(&nw, ipamNetworkFinalizer) + controllerutil.AddFinalizer(nw, ipamNetworkFinalizer) // Update the Network object - if err := r.Update(ctx, &nw); err != nil { - klog.Errorf("error while adding finalizers to Network %q: %v", req.NamespacedName, err) - return ctrl.Result{}, err + if err := r.Update(ctx, nw); err != nil { + klog.Errorf("error while adding finalizers to Network %q: %v", client.ObjectKeyFromObject(nw), err) + return err } - klog.Infof("finalizer %q correctly added to Network %q", ipamNetworkFinalizer, req.NamespacedName) + klog.Infof("finalizer %q correctly added to Network %q", ipamNetworkFinalizer, client.ObjectKeyFromObject(nw)) // We return immediately and wait for the next reconcile to eventually update the status. - return ctrl.Result{}, nil + return nil } // Update Network status if it is not set yet // The IPAM MapNetworkCIDR() function is not idempotent, so we avoid to call it // multiple times by checking if the status is already set. if nw.Status.CIDR == "" { - var err error - remappedCIDR, err = getRemappedCIDR(ctx, r.IpamClient, desiredCIDR) + desiredCIDR := nw.Spec.CIDR + remappedCIDR, err := getRemappedCIDR(ctx, r.ipamClient, desiredCIDR) if err != nil { - return ctrl.Result{}, err + return err } // Update status nw.Status.CIDR = remappedCIDR - if err := r.Client.Status().Update(ctx, &nw); err != nil { - klog.Errorf("error while updating Network %q status: %v", req.NamespacedName, err) - return ctrl.Result{}, err + if err := r.Client.Status().Update(ctx, nw); err != nil { + klog.Errorf("error while updating Network %q status: %v", client.ObjectKeyFromObject(nw), err) + return err } - klog.Infof("updated Network %q status (spec: %s -> status: %s)", req.NamespacedName, desiredCIDR, remappedCIDR) + klog.Infof("updated Network %q status (spec: %s -> status: %s)", client.ObjectKeyFromObject(nw), desiredCIDR, remappedCIDR) } - } else if controllerutil.ContainsFinalizer(&nw, ipamNetworkFinalizer) { + } else if controllerutil.ContainsFinalizer(nw, ipamNetworkFinalizer) { // The resource is being deleted and the finalizer is still present. Call the IPAM to unmap the network CIDR. - remappedCIDR = nw.Status.CIDR + remappedCIDR := nw.Status.CIDR if remappedCIDR != "" { if _, _, err := net.ParseCIDR(remappedCIDR.String()); err != nil { - klog.Errorf("Unable to unmap CIDR %s of Network %q (inavlid format): %v", remappedCIDR, req.NamespacedName, err) - return ctrl.Result{}, err + klog.Errorf("Unable to unmap CIDR %s of Network %q (inavlid format): %v", remappedCIDR, client.ObjectKeyFromObject(nw), err) + return err } - if err := deleteRemappedCIDR(ctx, r.IpamClient, remappedCIDR); err != nil { - return ctrl.Result{}, err + if err := deleteRemappedCIDR(ctx, r.ipamClient, remappedCIDR); err != nil { + return err } } // Remove status and finalizer, and update the object. nw.Status.CIDR = "" - controllerutil.RemoveFinalizer(&nw, ipamNetworkFinalizer) + controllerutil.RemoveFinalizer(nw, ipamNetworkFinalizer) - if err := r.Update(ctx, &nw); err != nil { - klog.Errorf("error while removing finalizer from Network %q: %v", req.NamespacedName, err) - return ctrl.Result{}, err + if err := r.Update(ctx, nw); err != nil { + klog.Errorf("error while removing finalizer from Network %q: %v", client.ObjectKeyFromObject(nw), err) + return err } - klog.Infof("finalizer correctly removed from Network %q", req.NamespacedName) + klog.Infof("finalizer correctly removed from Network %q", client.ObjectKeyFromObject(nw)) } - return ctrl.Result{}, nil -} - -// SetupWithManager monitors Network resources. -func (r *NetworkReconciler) SetupWithManager(mgr ctrl.Manager, workers int) error { - return ctrl.NewControllerManagedBy(mgr). - For(&ipamv1alpha1.Network{}). - WithOptions(controller.Options{MaxConcurrentReconciles: workers}). - Complete(r) + return nil } -// 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 +237,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/liqoctl/rest/configuration/create.go b/pkg/liqoctl/rest/configuration/create.go index bb80df8166..cb4153eb81 100644 --- a/pkg/liqoctl/rest/configuration/create.go +++ b/pkg/liqoctl/rest/configuration/create.go @@ -21,7 +21,6 @@ import ( "time" "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" @@ -29,7 +28,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" - liqoconsts "github.com/liqotech/liqo/pkg/consts" "github.com/liqotech/liqo/pkg/liqoctl/completion" "github.com/liqotech/liqo/pkg/liqoctl/output" "github.com/liqotech/liqo/pkg/liqoctl/rest" @@ -90,7 +88,7 @@ func (o *Options) Create(ctx context.Context, options *rest.CreateOptions) *cobr func (o *Options) handleCreate(ctx context.Context) error { opts := o.createOptions - conf := forgeConfiguration(o.createOptions.Name, o.createOptions.Namespace, + conf := ForgeConfiguration(o.createOptions.Name, o.createOptions.Namespace, o.RemoteClusterID, o.PodCIDR.String(), o.ExternalCIDR.String()) if opts.OutputFormat != "" { @@ -100,7 +98,7 @@ func (o *Options) handleCreate(ctx context.Context) error { s := opts.Printer.StartSpinner("Creating configuration") _, err := controllerutil.CreateOrUpdate(ctx, opts.CRClient, conf, func() error { - mutateConfiguration(conf, o.RemoteClusterID, o.PodCIDR.String(), o.ExternalCIDR.String()) + MutateConfiguration(conf, o.RemoteClusterID, o.PodCIDR.String(), o.ExternalCIDR.String()) return nil }) if err != nil { @@ -133,35 +131,6 @@ func (o *Options) handleCreate(ctx context.Context) error { return nil } -func forgeConfiguration(name, namespace, remoteClusterID, podCIDR, externalCIDR string) *networkingv1alpha1.Configuration { - conf := &networkingv1alpha1.Configuration{ - TypeMeta: metav1.TypeMeta{ - Kind: networkingv1alpha1.ConfigurationKind, - APIVersion: networkingv1alpha1.GroupVersion.String(), - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: map[string]string{ - liqoconsts.RemoteClusterID: remoteClusterID, - }, - }, - } - mutateConfiguration(conf, remoteClusterID, podCIDR, externalCIDR) - return conf -} - -func mutateConfiguration(conf *networkingv1alpha1.Configuration, remoteClusterID, podCIDR, externalCIDR string) { - conf.Kind = networkingv1alpha1.ConfigurationKind - conf.APIVersion = networkingv1alpha1.GroupVersion.String() - if conf.Labels == nil { - conf.Labels = make(map[string]string) - } - conf.Labels[liqoconsts.RemoteClusterID] = remoteClusterID - conf.Spec.Remote.CIDR.Pod = networkingv1alpha1.CIDR(podCIDR) - conf.Spec.Remote.CIDR.External = networkingv1alpha1.CIDR(externalCIDR) -} - // output implements the logic to output the generated Configuration resource. func (o *Options) output(conf *networkingv1alpha1.Configuration) error { var outputFormat string diff --git a/pkg/liqoctl/rest/configuration/utils.go b/pkg/liqoctl/rest/configuration/utils.go index b9f2ad6cf6..2b7604da95 100644 --- a/pkg/liqoctl/rest/configuration/utils.go +++ b/pkg/liqoctl/rest/configuration/utils.go @@ -20,7 +20,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -28,10 +27,41 @@ import ( networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" liqoconsts "github.com/liqotech/liqo/pkg/consts" liqoutils "github.com/liqotech/liqo/pkg/utils" - liqogetters "github.com/liqotech/liqo/pkg/utils/getters" + ipamutils "github.com/liqotech/liqo/pkg/utils/ipam" ) -// ForgeConfigurationForRemoteCluster forges a configuration of the local cluster to be applied to a remote cluster. +// ForgeConfiguration forges a Configuration resource of a remote cluster. +func ForgeConfiguration(name, namespace, remoteClusterID, podCIDR, externalCIDR string) *networkingv1alpha1.Configuration { + conf := &networkingv1alpha1.Configuration{ + TypeMeta: metav1.TypeMeta{ + Kind: networkingv1alpha1.ConfigurationKind, + APIVersion: networkingv1alpha1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + liqoconsts.RemoteClusterID: remoteClusterID, + }, + }, + } + MutateConfiguration(conf, remoteClusterID, podCIDR, externalCIDR) + return conf +} + +// MutateConfiguration mutates a Configuration resource of a remote cluster. +func MutateConfiguration(conf *networkingv1alpha1.Configuration, remoteClusterID, podCIDR, externalCIDR string) { + conf.Kind = networkingv1alpha1.ConfigurationKind + conf.APIVersion = networkingv1alpha1.GroupVersion.String() + if conf.Labels == nil { + conf.Labels = make(map[string]string) + } + conf.Labels[liqoconsts.RemoteClusterID] = remoteClusterID + conf.Spec.Remote.CIDR.Pod = networkingv1alpha1.CIDR(podCIDR) + conf.Spec.Remote.CIDR.External = networkingv1alpha1.CIDR(externalCIDR) +} + +// ForgeConfigurationForRemoteCluster forges a Configuration of the local cluster to be applied to a remote cluster. // It retrieves the local configuration settings starting from the cluster identity and the IPAM storage. func ForgeConfigurationForRemoteCluster(ctx context.Context, cl client.Client, namespace, liqoNamespace string) (*networkingv1alpha1.Configuration, error) { @@ -40,9 +70,14 @@ func ForgeConfigurationForRemoteCluster(ctx context.Context, cl client.Client, return nil, fmt.Errorf("unable to get cluster identity: %w", err) } - ipamStorage, err := liqogetters.GetIPAMStorageByLabel(ctx, cl, labels.NewSelector()) + podCIDR, err := ipamutils.RetrievePodCIDR(ctx, cl) + if err != nil { + return nil, fmt.Errorf("unable to retrieve pod CIDR: %w", err) + } + + externalCIDR, err := ipamutils.RetrieveExternalCIDR(ctx, cl) if err != nil { - return nil, fmt.Errorf("unable to get IPAM storage: %w", err) + return nil, fmt.Errorf("unable to retrieve external CIDR: %w", err) } cnf := &networkingv1alpha1.Configuration{ @@ -59,8 +94,8 @@ func ForgeConfigurationForRemoteCluster(ctx context.Context, cl client.Client, Spec: networkingv1alpha1.ConfigurationSpec{ Remote: networkingv1alpha1.ClusterConfig{ CIDR: 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/utils/getters/k8sGetters.go b/pkg/utils/getters/k8sGetters.go index 771a01c947..17299f08fa 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) } } + +// GetUniqueNetworkByLabel retrieves the Network resource with the given label selector. +// It returns error if multiple resources are found. +func GetUniqueNetworkByLabel(ctx context.Context, cl client.Client, lSelector labels.Selector) (*ipamv1alpha1.Network, error) { + networks, err := GetNetworksByLabel(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) + } +} + +// GetNetworksByLabel retrieves the Network resources with the given labelSelector. +func GetNetworksByLabel(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..ced4353ee5 --- /dev/null +++ b/pkg/utils/ipam/networks.go @@ -0,0 +1,152 @@ +// 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" + "fmt" + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + ipamv1alpha1 "github.com/liqotech/liqo/apis/ipam/v1alpha1" + networkingv1alpha1 "github.com/liqotech/liqo/apis/networking/v1alpha1" + "github.com/liqotech/liqo/pkg/consts" + liqogetters "github.com/liqotech/liqo/pkg/utils/getters" +) + +// GetPodCIDR retrieves the podCIDR of the local cluster. +func GetPodCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + consts.NetworkTypeLabelKey: string(consts.NetworkTypePodCIDR), + })) + if err != nil { + return "", err + } + + return nw.Spec.CIDR.String(), nil +} + +// GetServiceCIDR retrieves the serviceCIDR of the local cluster. +func GetServiceCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + consts.NetworkTypeLabelKey: string(consts.NetworkTypeServiceCIDR), + })) + if err != nil { + return "", err + } + + return nw.Spec.CIDR.String(), nil +} + +// GetExternalCIDR retrieves the externalCIDR of the local cluster. +func GetExternalCIDR(ctx context.Context, cl client.Client) (string, error) { + nw, err := liqogetters.GetUniqueNetworkByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + consts.NetworkTypeLabelKey: string(consts.NetworkTypeExternalCIDR), + })) + if err != nil { + return "", err + } + + if nw.Status.CIDR == "" { + return "", fmt.Errorf("the external CIDR is not yet configured: missing status on the Network resource") + } + + return nw.Status.CIDR.String(), nil +} + +// GetReservedSubnets retrieves the reserved subnets of the local cluster. +func GetReservedSubnets(ctx context.Context, cl client.Client) ([]string, error) { + var reservedSubnets []string + + networks, err := liqogetters.GetNetworksByLabel(ctx, cl, labels.SelectorFromSet(map[string]string{ + consts.NetworkTypeLabelKey: string(consts.NetworkTypeReserved), + })) + if err != nil { + return nil, err + } + + for i := range networks.Items { + reservedSubnets = append(reservedSubnets, networks.Items[i].Spec.CIDR.String()) + } + + return reservedSubnets, nil +} + +// NetworkNotRemapped returns whether the given Network does not need CIDR remapping. +func NetworkNotRemapped(nw *ipamv1alpha1.Network) bool { + value, ok := nw.Labels[consts.NetworkNotRemappedLabelKey] + return ok && !strings.EqualFold(value, "false") +} + +// IsPodCIDR returns whether the given Network is of type PodCIDR. +func IsPodCIDR(nw *ipamv1alpha1.Network) bool { + nwType, ok := nw.Labels[consts.NetworkTypeLabelKey] + return ok && nwType == string(consts.NetworkTypePodCIDR) +} + +// IsServiceCIDR returns whether the given Network is of type ServiceCIDR. +func IsServiceCIDR(nw *ipamv1alpha1.Network) bool { + nwType, ok := nw.Labels[consts.NetworkTypeLabelKey] + return ok && nwType == string(consts.NetworkTypeServiceCIDR) +} + +// IsExternalCIDR returns whether the given Network is of type ExternalCIDR. +func IsExternalCIDR(nw *ipamv1alpha1.Network) bool { + nwType, ok := nw.Labels[consts.NetworkTypeLabelKey] + return ok && nwType == string(consts.NetworkTypeExternalCIDR) +} + +// IsReservedNetwork returns whether the given Network is of type Reserved. +func IsReservedNetwork(nw *ipamv1alpha1.Network) bool { + nwType, ok := nw.Labels[consts.NetworkTypeLabelKey] + return ok && nwType == string(consts.NetworkTypeReserved) +} + +// CreateNetwork creates a Network resource with the given name and CIDR. +// NeedRemapping indicates whether the Network needs CIDR remapping from IPAM. +// NetworkType indicates the type of the Network (leave empty to not set the type). +func CreateNetwork(ctx context.Context, cl client.Client, name, namespace, cidr string, needRemapping bool, networkType *consts.NetworkType) error { + network := &ipamv1alpha1.Network{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + + if _, err := controllerutil.CreateOrUpdate(ctx, cl, network, func() error { + if network.Labels == nil { + network.Labels = map[string]string{} + } + if !needRemapping { + network.Labels[consts.NetworkNotRemappedLabelKey] = consts.NetworkNotRemappedLabelValue + } + if networkType != nil { + network.Labels[consts.NetworkTypeLabelKey] = string(*networkType) + } + + network.Spec = ipamv1alpha1.NetworkSpec{ + CIDR: networkingv1alpha1.CIDR(cidr), + } + return nil + }); err != nil { + return err + } + + return nil +} diff --git a/test/integration/integration_suite_test.go b/test/integration/integration_suite_test.go index 4255cf2ddc..f3908f7c7d 100644 --- a/test/integration/integration_suite_test.go +++ b/test/integration/integration_suite_test.go @@ -178,7 +178,7 @@ func initIpam() error { if err != nil { return err } - err = ipam.Init(liqoipam.Pools, dynClient, 2000+int(n.Int64())) + err = ipam.Init(liqoipam.Pools, dynClient) if err != nil { return err } @@ -187,10 +187,17 @@ func initIpam() error { if err != nil { return err } + + // TODO: update with new GetOrSetExternalCIDR method homeExternalCIDR, err = ipam.GetExternalCIDR(uint8(24)) if err != nil { return err } + + if err := ipam.Serve(2000 + int(n.Int64())); err != nil { + return err + } + // Assign networks to clusterID1 _, _, err = ipam.GetSubnetsPerCluster(remotePodCIDR, remoteExternalCIDR, clusterID1) if err != nil {